自从Node横空出世后,很快有人就用它来开发爬虫,网上也常见Node爬虫教程。然而,很难看到一个通用的、功能丰富的爬虫开源项目,到Github上找了一下找到这个,算是目前能找到的最好的了。
这里将它的文档翻译一下,期待更多的实用案例。
node-crawler
目标打造成Node社区最强大和流行的爬虫/内容抽取工具库,且支持生产环境。
特性:
- 服务端DOM和自动jQuery注入,使用Cheerio(默认)或JSDOM
- 可配置的连接池大小和重试次数
- Control rate limit
- 支持设置请求队列优先级
- forceUTF8模式可让爬虫处理字符集编码探测和转换
- 兼容Node 4.x及以上版本
更新日志:https://github.com/bda-research/node-crawler/blob/master/CHANGELOG.md
上手指南
安装
$ npm install crawler
使用
var Crawler = require("crawler");
var c = new Crawler({
maxConnections : 10,
// 这个回调每个爬取到的页面都会触发
callback : function (error, res, done) {
if(error){
console.log(error);
}else{
var $ = res.$;
// $默认使用Cheerio
// 这是为服务端设计的轻量级jQuery核心实现
console.log($("title").text());
}
done();
}
});
// 爬取一个URL,使用默认的callback
c.queue('http://www.amazon.com');
// 爬取URL列表
c.queue(['http://www.google.com/','http://www.yahoo.com']);
// 爬取页面,自定义callback和参数
c.queue([{
uri: 'http://parishackers.org/',
jQuery: false,
// 覆盖全局的callback
callback: function (error, res, done) {
if(error){
console.log(error);
}else{
console.log('Grabbed', res.body.length, 'bytes');
}
done();
}
}]);
// 在队列中加入一些HTML代码,无需爬取(mostly for tests)
c.queue([{
html: 'This is a test
'
}]);
控制爬取间隔时间
当你在流量网站时,使用 rateLimit
控制间隔时间。
var crawler = require("crawler");
var c = new Crawler({
rateLimit: 1000, // `maxConnections` 会强制为1个
callback: function(err, res, done){
console.log(res.$("title").text());
done();
}
});
c.queue(tasks);//在两次任务间最小时间间隔为 1000 (ms)
配置项指南
你可以将这些配置发给 Crawler()
构造器,让它们成为全局配置,或者自定义 queue()
的请求已覆盖全局配置。
这个配置列表在mikeal的request项目配置的基础上做了扩展,并且会直接发送给 request()
方法。
基本请求配置项:
-
uri
:String
你想爬取的网页链接. -
timeout
:Number
单位是毫秒 (默认 15000). - 支持mikeal的request的所有配置.
回调:
-
callback(error, res, done)
: 请求完成后会被调用-
error
: Error -
res
: http.IncomingMessage 请求的回应,包括$
和options
-
res.statusCode
:Number
HTTP status code. E.G.200
-
res.body
:Buffer
|String
HTTP返回内容,可能是HTML页面、纯文本或XML文档。 -
res.headers
:Object
HTTP请求的返回头 -
res.request
: Request Mikeal的Request
的实例,以取代 http.ClientRequest-
res.request.uri
: urlObject 解析后的URL实体 -
res.request.method
:String
HTTP request method. E.G.GET
-
res.request.headers
:Object
HTTP request headers
-
-
res.options
: Options 配置项 -
$
: jQuery Selector HTML和XML的选择器
-
-
done
:Function
回调中要做的事情做完后需要调用这个
-
计划任务选项:
-
maxConnections
:Number
连接池大小 (默认 10). -
rateLimit
:Number
每条请求之间的间隔时间,单位毫秒 (默认 0). -
priorityRange
:Number
可接受的优先级数值,最小为0 (默认 10). -
priority
:Number
当前请求的优先级 (默认 5).
重试选项:
-
retries
:Number
请求失败后的重试次数 (默认 3), -
retryTimeout
:Number
重试前等待的时间,单位毫秒 (默认 10000),
服务端DOM配置:
-
jQuery
:Boolean
|String
|Object
如果设置为true,使用cheerio
和默认配置来注入爬取内容。或使用解析选项自定义cheerio
. 当返回false时停止注入jQuery选择器。如果在你的项目中存在内存泄漏,使用一个替代的解析器"whacko"来避免。(默认 true)
字符集编码:
-
forceUTF8
:Boolean
如果设置为true,爬虫将从HTTP请求头中获取字符集或从HTML中获取meta tag,并且将它转换到UTF8,不用再担心编码问题了(默认 true) -
incomingEncoding
:String
当设置forceUTF8: true
时可自行设置接收内容的编码 (默认 null),爬虫就不用自己检测编码了。 如,incomingEncoding : 'windows-1255'
. 查看 所有支持的编码
缓存:
-
skipDuplicates
:Boolean
设置为true时,跳过已爬取过的URI,甚至不触发callback()
(默认 false)。 不推荐改动,更好的做法是在爬虫之外使用seenreq进行处理。
其它:
-
rotateUA
:Boolean
当为true时,userAgent
应该是数组,并进行轮换 (默认 false) -
userAgent
:String
|Array
, 如果rotateUA
为 false, 但userAgent
是一个数组, 爬虫将使用第一个值。 -
referer
:String
当为真时设置HTTP的 referer header
Class:Crawler
Event: 'schedule'
-
options
Options
当一个任务被加到计划时触发.
crawler.on('schedule',function(options){
options.proxy = "http://proxy:port";
});
Event: 'limiterChange'
-
options
Options -
limiter
String
当limiter改变时触发.
Event: 'request'
-
options
Options
当爬虫准备好发送请求时触发.
如果你想在发出请求之前的最后阶段改变配置,可以监听这个事件。
crawler.on('request',function(options){
options.qs.timestamp = new Date().getTime();
});
Event: 'drain'
当队列为空时触发。
crawler.on('drain',function(){
// 执行一些操作,如,释放数据库连接。
db.end(); // 关闭MySQL连接。
});
crawler.queue(uri|options)
-
uri
String
-
options
Options
将任务加入队列并等待执行。
crawler.queueSize
Number
队列数量,该属性为只读。
处理瓶颈
使用limiter控制爬取频率。所有提交到limiter的任务都需要遵守rateLimit
和maxConnections
的限制。rateLimit
是两个任务之间的最小间隔,maxConnections
是最大的并发数。limiters之间是互相独立的。一个通常的用例是为不同的代理设置不同的limiter。另外值得一提的是,当rateLimit
设置为非0的值时,maxConnections
的值将被强制为1.
var crawler = require('crawler');
var c = new Crawler({
rateLimit: 2000,
maxConnections: 1,
callback: function(error, res, done) {
if(error) {
console.log(error)
} else {
var $ = res.$;
console.log($('title').text())
}
done();
}
})
// 如果你想以2000毫秒的间隔执行任务
c.queue('http://www.somewebsite.com/page/1')
c.queue('http://www.somewebsite.com/page/2')
c.queue('http://www.somewebsite.com/page/3')
// 如果你想为设置代理,并为每个代理设置2000毫秒的间隔
c.queue({
uri:'http://www.somewebsite.com/page/1',
limiter:'proxy_1',
proxy:'proxy_1'
})
c.queue({
uri:'http://www.somewebsite.com/page/2',
limiter:'proxy_2',
proxy:'proxy_2'
})
c.queue({
uri:'http://www.somewebsite.com/page/3',
limiter:'proxy_3',
proxy:'proxy_3'
})
c.queue({
uri:'http://www.somewebsite.com/page/4',
limiter:'proxy_1',
proxy:'proxy_1'
})
Work with Cheerio or JSDOM
爬虫默认使用Cheerio,并将JSDOM作为可选的替代。JSDOM更稳定,如果你想使用JSDOM,你需要引入该依赖require('jsdom')
,并配置爬虫。
Working with Cheerio
jQuery: true //(default)
//OR
jQuery: 'cheerio'
//OR
jQuery: {
name: 'cheerio',
options: {
normalizeWhitespace: true,
xmlMode: true
}
}
这些解析配置从htmlparser2里继承而来。你可以使用所有可用的配置。默认的配置为:
{
normalizeWhitespace: false,
xmlMode: false,
decodeEntities: true
}
需要所有的配置项和它们的效果,查看 这里 以及
htmlparser2的配置项。来源
Work with JSDOM
要使用JSDOM,你需要先在项目目录下npm install jsdom
,然后配置爬虫。
var jsdom = require('jsdom');
var Crawler = require('crawler');
var c = new Crawler({
jQuery: jsdom
});
如何测试
安装并运行Httpbin
爬虫使用本地的httpbin来测试。你可以从PyPI安装httpbin并将其作为WSGI应用来允许。比如,使用Gunicorn:
$ pip install httpbin
# launch httpbin as a daemon with 6 worker on localhost
$ gunicorn httpbin:app -b 127.0.0.1:8000 -w 6 --daemon
# Finally
$ npm install && npm test
使用Docker
在安装 Docker 之后, 你可以执行:
# Builds the local test environment
$ docker build -t node-crawler .
# Runs tests
$ docker run node-crawler sh -c "gunicorn httpbin:app -b 127.0.0.1:8000 -w 6 --daemon && cd /usr/local/lib/node_modules/crawler && npm install && npm test"
# You can also ssh into the container for easier debugging
$ docker run -i -t node-crawler bash
一些比较困难的待办事项
- 使用zombie来处理有复杂ajax的页面
- Refactoring the code to be more maintainable重构代码以方便维护
- 通过Sizzle测试 (JSDOM bug? https://github.com/tmpvar/jsdom/issues#issue/81)
- 支持Promise
- 支持Commander
- 支持中间件