Node.js Request+Cheerio实现一个小爬虫-基础功能实现1:内容抓取
Node.js Request+Cheerio实现一个小爬虫-基础功能实现2:文件写入
Node.js Request+Cheerio实现一个小爬虫-基础功能实现3:流程控制及并发控制
Node.js Request+Cheerio实现一个小爬虫-番外篇:代理设置
前一篇文章最后遇到了流程控制的问题。虽然我们可以用emmit或者是用计数器的方法来进行控制,但是这种方法局限性比较强一些。因此在这里可以用到Node的流程控制Aysnc模块。
Aysnc的 GitHub主页
这里是一些example还有中文注释,写得十分详细。
我们的设想是在抓取完全部请求之后,对抓取的结果进行排序后写入文件。所以在这里可以使用map
函数。上面例子中对于map
的解释。(当时就只想着用map
了,其实用each
也是可以的)
map: 对集合中的每一个元素,执行某个异步操作,得到结果。所有的结果将汇总到最终的callback里。与each的区别是,each只关心操作不管最后的值,而map关心的最后产生的值。
map(coll, iteratee, callbackopt)
官方文档中给出的参数:
Name | Type | Description |
---|---|---|
coll | Array / Iterable / Object | A collection to iterate over. |
iteratee | function | A function to apply to each item in coll. The iteratee is passed a callback(err, transformed) which must be called once it has completed with an error (which can be null) and a transformed item. Invoked with (item, callback). |
callback | function <optional> | A callback which is called when all iteratee functions have finished, or an error occurs. Results is an Array of the transformed items from the coll. Invoked with (err, results). |
上面这段话是什么意思呢?可以看看下面的代码。
const async = require('async');
// 创建一个url的请求列表,用于map函数中的 coll
var urlLists = createUrlLists(BASE_URL, PAGE_MAX);
async.map(urlLists, function(url, callback) {
// 这里的参数url便是urllists中的每一项
var option = {
url: url,
headers: ... // 省略一下
}
// 请求用的函数与之前相同,其实写到外面会更好看
request(option, function(error, response, body) {
if (!error && response.statusCode == 200) {
var $ = cheerio.load(body, {
ignoreWhitespace: true,
xmlMode: true
});
var shopInfo = {
pageNo: option.url.match(/g\d+p(\d+)/)[1],
pageURL: option.url,
info: []
};
var shopList = $('div#shop-all-list').find('a[data-hippo-type = "shop"]');
shopList.each(function(no, shop) {
let info = {};
info.no = no + 1;
info.name = $(shop).attr('title');
info.url = $(shop).attr('href');
shopInfo.info.push(info);
});
shopLists.push(shopInfo);
// 这里的callback是对函数自身的循环
callback(null, url, option);
}
});
}, function(err, result) {
// 这里是要在前面的函数全部完成之后再调用
// 因此shopLists在这个时候已经是了全部url数据的状态了
// 这里的result其实就是我们传入的urlLists
// 但是我们没有对urlLists进行操作,所以我们也可以使用each方法来实现同样的功能
shopLists.sort(function(shop1, shop2) {
return parseInt(shop1.pageNo) - parseInt(shop2.pageNo);
});
fs.exists(FILE_PATH + FILE_NAME, function(exits) {
if (exits) {
fs.appendFile(FILE_PATH + FILE_NAME, shopLists, 'utf-8', function(err) {
if (err) {
console.error("文件生成时发生错误.");
throw err;
}
});
} else {
fs.writeFile(FILE_PATH + FILE_NAME, shopLists, 'utf-8', function(err) {
if (err) {
console.error("文件生成时发生错误.");
throw err;
}
console.info('文件已经成功生成.');
});
}
});
});
这样一来,我们就可以对获取到的内容排序后写入到文件中去了。但是,嗯,是的,问题又来了。如果我们只对3,4个url发出请求,那么问题还不大。但如果是30,40个甚至是上百个请求呢?现在的网站对于爬虫或者恶意的访问都会做防范,如果一次性请求太大或者太过于频繁的话,就很容易被403了。所以呢,就需要考虑到并发数的问题了。(做爬虫也要考虑到对方服务器,不要太过火)并发数的问题同样可以用Async模块来解决。
并发控制可以参照这篇文章
只要把map
方法,换成mapLimit
方法就可以了。
mapLimit(coll, limit, iteratee, callbackopt)
先看看官方文档的解释。其实比起map
就多了一个limit参数,这个limit参数就是用来控制并发数的。
Name | Type | Description |
---|---|---|
coll | Array / Iterable / Object | A collection to iterate over. |
limit | number | The maximum number of async operations at a time. |
iteratee | function | A function to apply to each item in coll. The iteratee is passed a callback(err, transformed) which must be called once it has completed with an error (which can be null) and a transformed item. Invoked with (item, callback). |
callback | function <optional> | A callback which is called when all iteratee functions have finished, or an error occurs. Results is an array of the transformed items from the coll.Invoked with (err, results). |
于是稍作修改便可以了。
async.mapLimit(urlLists, 5, function(url, callback) {
// 这里多了并发的最高上限
// 现在一起发出的请求最多为5条
var option = {
url: url,
headers: ...
}
// 请求用的函数与之前相同,其实写到外面会更好看
request(option, function(error, response, body) {
// 这里与之前相同
});
});
到此为止,基本上这么一个爬虫就算是完成了。当然,要避免403的最有效手段应该还是用代理吧。这个大概可以作为一个番外写一下。
最后这一次是GitHub项目
做完看看,还有很多不成熟的地方,还需要再努力一下啊。
网友评论