使用async控制并发
原文链接: https://github.com/alsotang/node-lessons/tree/master/lesson5
博主已学习,并做记录
目标
建立一个lesson5项目,在其中编写代码
代码入口app.js
, $ node app.js
, 会输出CNode社区首页的所有主题的标题、链接和第一条评论,以json格式
注意: 与lesson4不同. 并发连接数需要控制在5个
输出实例:
1 2 3 4 5 6 7 8 9 10 11 12
| [ { "title": "【公告】发招聘帖的同学留意一下这里", "href": "http://cnodejs.org/topic/541ed2d05e28155f24676a12", "comment1": "呵呵呵呵" }, { "title": "发布一款 Sublime Text 下的 JavaScript 语法高亮插件", "href": "http://cnodejs.org/topic/54207e2efffeb6de3d61f68f", "comment1": "沙发!" } ]
|
知识点
当你需要去多个源(一般是小于 10 个)汇总数据的时候,用 eventproxy 方便;当你需要用到队列,需要控制并发数,或者你喜欢函数式编程思维时,使用 async。大部分场景是前者
开始:
首先,我们伪造一个 fetchUrl(url, callback)
函数,这个函数的作用,就是当你调用它时,它会返回url的页面内容回来.
1 2 3
| fetchUrl(url, function(err, content){ // do something with 'content' });
|
当然,我们这里的返回内容是假的,返回延时是随机的.并且在它被调用时,会告诉你它现在一共被多少个地方并发的调用着.
1 2 3 4 5 6 7 8 9 10 11
| var connectCurrentCount = 0; var fetchUrl = function(url, callback) { // delay 的值在2000以内,是个随机的整数 var delay = parseInt((Math.random() * 10000000) % 2000, 10); connectCurrentCount++; console.log('现在的并发连接数为->', connectCurrentCount, ' ,正在抓取的url地址为->', url, ' ,耗时' + delay + '毫秒'); setTimeout(function(){ connectCurrentCount--; callback(null, url + ' html content'); }, delay); };
|
我们继续,来伪造一组链接
1 2 3 4
| var urls = []; for(var i = 0; i < 30; i++) { urls.push('http://datasource...' + i); }
|
有图有真相:
接着,我们使用 async.mapLimit
来并发抓取,获取结果.
1 2 3 4 5 6
| async.mapLimit(urls, 5, function(url, callback) { fetchUrl(url, callback); }, function (err, result) { console.log('final:'); console.log(result); });
|
完整的代码是这样.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| var async = require('async'); var connectCurrentCount = 0; var fetchUrl = function(url, callback) { // delay 的值在2000以内,是个随机的整数 var delay = parseInt((Math.random() * 10000000) % 2000, 10); connectCurrentCount++; console.log('现在的并发连接数为->', connectCurrentCount, ' ,正在抓取的url地址为->', url, ' ,耗时' + delay + '毫秒'); setTimeout(function(){ connectCurrentCount--; callback(null, url + ' html content'); }, delay); }; var urls = []; for(var i = 0; i < 30; i++) { urls.push('http://datasource...' + i); } async.mapLimit(urls, 5, function(url, callback) { fetchUrl(url, callback); }, function (err, result) { console.log('final:'); console.log(result); });
|
有图有真相:
作为补充,完成了我们的目标
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| // 加载模块 var async = require('async'); var superagent = require('superagent'); var cheerio = require('cheerio'); var url = require('url'); // 主站链接 var cnodeUrl = 'https://cnodejs.org/'; // 当前连接数 var connectCurrentCount = 0; // 处理 var getComment = function(topicUrl, callback) { connectCurrentCount++; console.log('现在的并发数是', connectCurrentCount, ',正在抓取的是', topicUrl); superagent.get(topicUrl) .end(function (err, sres) { connectCurrentCount--; if(err) { getCommont(topicUrl, callback); return; } var $ = cheerio.load(sres.text); callback(null, { title: $('.topic_full_title').text().trim(), href: topicUrl, comment: $('.reply_content').eq(0).text().trim() }); }); }; // 访问 superagent.get(cnodeUrl) .end(function(err, res) { if(err) { console.log(err); return; } var $ = cheerio.load(res.text); var urls = []; $('#topic_list .topic_title').each(function(idx, element){ var $element = $(element); urls.push(url.resolve(cnodeUrl, $element.attr('href'))); }); console.log(urls); // 得到40个主题的url // 开启并发访问 async.mapLimit(urls, 5, function(topicUrl, callback){ getComment(topicUrl, callback); }, function(err, result){ console.log('final->'); console.log(result); // 40次返回结果汇总为数组 }); });
|
有图有真相: