目標
- 使用 async(https://github.com/caolan/async) 來控制并發(fā)連接數(shù)
新建文件 app.js
卡睦,當調(diào)用 node app.js
時宴胧,輸出 CNode(https://cnodejs.org/ ) 社區(qū)首頁的所有帖子標題和鏈接以及第一條評論帘睦,以 json 的格式返回度苔。
輸出示例:
[
{
title: '微信應用號在前端開發(fā)圈火了温峭,而Docker其實早已火遍后端',
href: 'https: //cnodejs.org/topic/57e45421015b4f570e0d02df',
comment1: 'weapp跟Docker。陆蟆。雷厂。這兩者有關系嗎。叠殷。改鲫。。'
},
{
title: '開發(fā)過微信簽名算法的大大看過來林束。像棘。。',
href: 'https: //cnodejs.org/topic/57e287a43af3942a3aa3b959',
comment1: '這個參數(shù)是微信加的用于跟蹤之類而且前端wx.config注冊一次以后就用不到簽名了還是看你的業(yè)務邏輯吧'
}
]
注意:與上篇文章(使用 eventproxy 控制并發(fā))不同壶冒,并發(fā)連接數(shù)需要控制在 5 個缕题。
如果你親自運行過上篇文章的代碼,就會發(fā)現(xiàn)一個問題胖腾,有的返回值為空或者報錯烟零。這是因為 cnodejs.org 網(wǎng)站有并發(fā)連接數(shù)的限制,所以當請求發(fā)送太快的時候會導致返回值為空或報錯咸作。
注意:有的的網(wǎng)站有可能會因為你發(fā)出的并發(fā)連接數(shù)太多而當你是在惡意請求锨阿,把你的 IP 封掉。
我們在寫爬蟲的時候记罚,如果有 1000 個鏈接要去爬墅诡,那么不可能同時發(fā)出 1000 個并發(fā)鏈接,我們需要控制一下并發(fā)的數(shù)量桐智,比如并發(fā) 10 個就好末早,然后慢慢抓完這 1000 個鏈接。
用 async 來做這件事很簡單酵使。
這次我們要介紹的是 async 的常用的控制并發(fā)連接數(shù)的接口 queue(worker, concurrency)(http://caolan.github.io/async/docs.html#.queue) 荐吉。
什么時候用 eventproxy,什么時候使用 async 呢口渔?它們不都是用來做異步流程控制的嗎样屠?
答案:
當你需要去多個源(一般是小于 10 個)匯總數(shù)據(jù)的時候,用 eventproxy 方便缺脉;當你需要用到隊列痪欲,需要控制并發(fā)數(shù),或者你喜歡函數(shù)式編程思維時攻礼,使用 async业踢。大部分場景是前者,所以大部分時間是用 eventproxy 的礁扮。
最終版的代碼:
var superagent = require('superagent');
var cheerio = require('cheerio');
var url = require('url');
var async = require('async');
var cnodeUrl = 'https://cnodejs.org/'
superagent.get(cnodeUrl)
.end(function(err, res) {
var topicUrls = [];
var $ = cheerio.load(res.text);
$('#topic_list .topic_title').each(function(index, element) {
var $element = $(element);
var href = url.resolve(cnodeUrl, $element.attr('href'));
topicUrls.push(href);
});
var data = [];
/**
* queue(worker, concurrency)
* queue 是一個串行的消息隊列知举,通過限制了 worker 數(shù)量瞬沦,不再一次性全部執(zhí)行。
* 當 worker 數(shù)量不夠用時雇锡,新加入的任務將會排隊等候逛钻,直到有新的 worker 可用。
*
*/
// 定義一個 queue锰提,設 worker 數(shù)量為 5
var q = async.queue(function(task, callback) {
var topicUrl = task.topicUrl;
superagent.get(topicUrl)
.end(function(err, res) {
var $ = cheerio.load(res.text);
var result = {
title: $('.topic_full_title').text().trim(),
href: topicUrl,
comment1: $('.reply_content').eq(0).text().trim()
};
callback(data.push(result));
});
}, 5);
/**
* 監(jiān)聽:當所有任務都執(zhí)行完以后曙痘,將調(diào)用該函數(shù)
*/
q.drain = function() {
console.log('all tasks have been processed');
console.log(data);
};
/**
* 添加任務
*/
topicUrls.forEach(function(topicUrl) {
q.push({ topicUrl: topicUrl }, function(err) {
console.log('push finished ' + topicUrl);
});
});
});