上文 《Node爬虫之使用 eventproxy 控制并发》我们说到用 node 爬了csdn首页数据,但是这些请求完全是并发的,如果某些网站有 “反爬” 机制,就很有可能封锁你的 IP。这样的情况下我们就可以使用 async 模块。主要用到的是 async 的 mapLimit(arr, limit, iterator, callback)
接口。
async.mapLimit(arr, 5, function (url, callback) {
fetchUrl(url, callback);
}, function (err, result) {
res.send(result);
});
第一个参数 arr为数组,保存了需要爬取页面的 url,第二个参数 limit 表示并发爬取数量。第三个参数是迭代函数(每个 url 需要执行这个函数),其第一个参数 url,是 urls 数组的每个 item,第二个参数 callback 与 mapLimit 方法第四个参数有关,callback 会往 result 参数里存放数据。如何理解?callback 是第三个参数 iterator 的回调,以爬虫为例,爬完页面肯定会分析一些数据,然后保存,执行 callback 函数就能把结果保存在 result(第四个参数函数中的参数) 中。
首先,我们采集 需要爬取页面的 url,保存在 urls 数组中:
$('.clearfix .list_con .title h2 a').each(function(idx, element) {
var $element = $(element);
// console.log('$element',$element);
var href = url.resolve(csdn, $element.attr('href'));
urls.push(href);
});
接着我们用 async 进行异步爬取:
async.mapLimit(urls, 5, function(url, callback) {
fetchUrl(url, callback);
},function(err, result) {
console.log('final: ', result);
response.send(result);
})
通过fetchUrl函数来抓起页面内容:
var fetchUrl = function(url, callback) {
superagent.get(url)
.end(function(err, res) {
if(err) {
console.log(err);
}
console.log('fetch ' + url + ' successful!');
var $ = cheerio.load(res.text);
var jsonData = {
title: $('.title-article').text().trim(),
href: url,
comment1: $('.comment').eq(0).text().trim()
};
console.log(JSON.stringify(jsonData));
callback(null, jsonData);
})
}
这里要注意的是 callback 中的第二个参数,其实 jsonData 已经储存在了 mapLimit 方法第四个参数的 result 参数中。
完整代码:
var express = require('express');
var superagent = require('superagent');
var cheerio = require('cheerio');
var async = require('async');
var url = require('url');
var csdn = 'https://blog.csdn.net/';
var app = express();
app.get('/', function(request, response, next) {
superagent.get(csdn)
.end(function(err, res) {
if(err) {
return console.error(err);
}
var urls = [];
var $ = cheerio.load(res.text);
$('.clearfix .list_con .title h2 a').each(function(idx, element) {
var $element = $(element);
// console.log('$element',$element);
var href = url.resolve(csdn, $element.attr('href'));
urls.push(href);
});
console.log('urls', urls);
var concurrencyCount = 0;
var fetchUrl = function(url, callback) {
superagent.get(url)
.end(function(err, res) {
if(err) {
console.log(err);
}
console.log('fetch ' + url + ' successful!');
var $ = cheerio.load(res.text);
var jsonData = {
title: $('.title-article').text().trim(),
href: url,
comment1: $('.comment').eq(0).text().trim()
};
console.log(JSON.stringify(jsonData));
callback(null, jsonData);
})
}
async.mapLimit(urls, 5, function(url, callback) {
fetchUrl(url, callback);
},function(err, result) {
console.log('final: ', result);
response.send(result);
})
})
})
app.listen(3002, function() {
console.log('app is listenling at port 3002');
})
结果:
还有个问题是,什么时候用 eventproxy,什么时候使用 async 呢?它们不都是用来做异步流程控制的吗?
当你需要去多个源(一般是小于 10 个)汇总数据的时候,用 eventproxy方便;当你需要用到队列,需要控制并发数,或者你喜欢函数式编程思维时,使用 async。大部分场景是前者。
参考:使用 async 控制并发扫描二维码关注公众号,回复: 3981945 查看本文章