建立一個(gè) lesson5 項(xiàng)目,在其中編寫(xiě)代碼。
代碼的入口是 app.js
,當(dāng)調(diào)用 node app.js
時(shí),它會(huì)輸出 CNode(https://cnodejs.org/ ) 社區(qū)首頁(yè)的所有主題的標(biāo)題,鏈接和第一條評(píng)論,以 json 的格式。
注意:與上節(jié)課不同,并發(fā)連接數(shù)需要控制在 5 個(gè)。
輸出示例:
[
{
"title": "【公告】發(fā)招聘帖的同學(xué)留意一下這里",
"href": "http://cnodejs.org/topic/541ed2d05e28155f24676a12",
"comment1": "呵呵呵呵"
},
{
"title": "發(fā)布一款 Sublime Text 下的 JavaScript 語(yǔ)法高亮插件",
"href": "http://cnodejs.org/topic/54207e2efffeb6de3d61f68f",
"comment1": "沙發(fā)!"
}
]
lesson4 的代碼其實(shí)是不完美的。為什么這么說(shuō),是因?yàn)樵?lesson4 中,我們一次性發(fā)了 40 個(gè)并發(fā)請(qǐng)求出去,要知道,除去 CNode 的話(huà),別的網(wǎng)站有可能會(huì)因?yàn)槟惆l(fā)出的并發(fā)連接數(shù)太多而當(dāng)你是在惡意請(qǐng)求,把你的 IP 封掉。
我們?cè)趯?xiě)爬蟲(chóng)的時(shí)候,如果有 1000 個(gè)鏈接要去爬,那么不可能同時(shí)發(fā)出 1000 個(gè)并發(fā)鏈接出去對(duì)不對(duì)?我們需要控制一下并發(fā)的數(shù)量,比如并發(fā) 10 個(gè)就好,然后慢慢抓完這 1000 個(gè)鏈接。
用 async 來(lái)做這件事很簡(jiǎn)單。
這次我們要介紹的是 async 的 mapLimit(arr, limit, iterator, callback)
接口。另外,還有個(gè)常用的控制并發(fā)連接數(shù)的接口是 queue(worker, concurrency)
,大家可以去 https://github.com/caolan/async#queueworker-concurrency 看看說(shuō)明。
這回我就不帶大家爬網(wǎng)站了,我們來(lái)專(zhuān)注知識(shí)點(diǎn):并發(fā)連接數(shù)控制。
對(duì)了,還有個(gè)問(wèn)題是,什么時(shí)候用 eventproxy,什么時(shí)候使用 async 呢?它們不都是用來(lái)做異步流程控制的嗎?
我的答案是:
當(dāng)你需要去多個(gè)源(一般是小于 10 個(gè))匯總數(shù)據(jù)的時(shí)候,用 eventproxy 方便;當(dāng)你需要用到隊(duì)列,需要控制并發(fā)數(shù),或者你喜歡函數(shù)式編程思維時(shí),使用 async。大部分場(chǎng)景是前者,所以我個(gè)人大部分時(shí)間是用 eventproxy 的。
正題開(kāi)始。
首先,我們偽造一個(gè) fetchUrl(url, callback)
函數(shù),這個(gè)函數(shù)的作用就是,當(dāng)你通過(guò)
fetchUrl('http://www.baidu.com', function (err, content) {
// do something with `content`
});
調(diào)用它時(shí),它會(huì)返回 http://www.baidu.com
的頁(yè)面內(nèi)容回來(lái)。
當(dāng)然,我們這里的返回內(nèi)容是假的,返回延時(shí)是隨機(jī)的。并且在它被調(diào)用時(shí),會(huì)告訴你它現(xiàn)在一共被多少個(gè)地方并發(fā)調(diào)用著。
// 并發(fā)連接數(shù)的計(jì)數(shù)器
var concurrencyCount = 0;
var fetchUrl = function (url, callback) {
// delay 的值在 2000 以?xún)?nèi),是個(gè)隨機(jī)的整數(shù)
var delay = parseInt((Math.random() * 10000000) % 2000, 10);
concurrencyCount++;
console.log('現(xiàn)在的并發(fā)數(shù)是', concurrencyCount, ',正在抓取的是', url, ',耗時(shí)' + delay + '毫秒');
setTimeout(function () {
concurrencyCount--;
callback(null, url + ' html content');
}, delay);
};
我們接著來(lái)偽造一組鏈接
var urls = [];
for(var i = 0; i < 30; i++) {
urls.push('http://datasource_' + i);
}
這組鏈接的長(zhǎng)這樣:
更多建議: