作为一名 web 开发人员,时不时爬爬别人家的网站还是很有趣的。

其实,爬一个网站的数据也是爬取者和被爬取者的一种攻防较量:一般情况下,被爬取者总是会想方设法地阻止爬取者爬取自家网站数据。

于是,一些网站就会采取一些措施来阻止非正常访问:

  • 某某关键接口只能每分钟调用若干次;
  • 某某 IP 访问网站太频繁,直接拒绝掉该 IP 发过来的请求。

当然,这些措施都只是治标不治本,不能从根本上杜绝自己网站数据被爬。

从爬取者的角度来看,要突破层层限制,拿到目标网站的数据,还是要做一些事情的:

  • 如果要爬取的目标网页需要登录才能访问到,那么可以采用 phantomjs 来简化掉 session 的处理;
  • 在爬取的过程中,如果发现服务器从某个时刻开始一直拒绝掉自己的请求,那么就要怀疑自己的 IP 是否被屏蔽掉了,或者某个接口是否访问太频繁了;
  • 对于有 IP 限制策略的网站,尽量模拟正常用户访问,频率不要太快,最好做多个节点来爬取。

等等,有点偏题了!下面进入正轨:

在初次写爬虫代码的时候,很容易遇到解析出来的数据是乱码的问题:

面对这些乱码,如何解决呢?

注意 HTTP 响应的头部

留意一下 HTTP 响应的头部是否有用 gzip 压缩过:

1
Content-Encoding:gzip

如果有这种字眼,那么响应正文部分就是使用 gzip 压缩过的,在拿到这种压缩过的数据之后,要先解压。

Node.js 中,提供了 zlib 模块,用于处理 gzip 相关的操作。对于解压 gzip ,可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// `res` 是响应对象,http.IncommingMessage 类型的
var buffers = [];// 暂存 gzip 解压过后的 buffer
var size = 0;// 记录整个响应体解压后的数据大小
var gunzipStream = zlib.createGunzip();
res.pipe(gunzipStream);
gunzipStream.on('data', function (buffer) {
buffers.push(buffer);
size += buffer.length;
});
gunzipStream.on('error', function (error) {
// 发生了错误,处理下吧!
});
gunzipStream.on('end', function () {
var unzipedBuffer = Buffer.concat(buffers, size); // unzipedBuffer 就是解压过后的数据
});

注意文本编码

拿着最终 gzip 解压出来的数据,开心的去进行后续处理,结果继续乱码,为什么会这样?

Node.js 里面默认字符串是 utf-8 编码的,如果 gzip 解压出来的数据不是 utf-8 编码的话,那么把这堆 buffer 数据转换成字符串的时候就可能产生乱码。 到目前为止,Node.js 内置支持的解码方式很有限,只能依靠一些第三方模块进行某些文本解码。

怎么办呢?

留意一下响应头当中的 Content-Type 部分,如果 charset 是非 utf-8 的话,那就要考虑继续对数据进行解码了。

对于中文网站,可能会使用 GBK 或者 GB2312 进行编码,对于此种场景,需要用到第三方的解码工具,此处选用了 iconv-lite 。解码过程如下:

1
var finallyResponseText = require('iconv-lite').decode(unzipedBuffer, 'gbk');

这样,就拿到了最终想要的文本数据。