网站首页 > 技术文章 正文
上文我们说到用 node 爬了博客园 20*150 条数据,但是这 150 次请求完全是并发的,如果某些网站有 "反爬" 机制,就很有可能封锁你的 IP。这样的情况下我们就可以使用async模块。
还是以具体例子来说明,一直爬博客园也不好,我们换个爬爬。在 Nodejs - 如何用 eventproxy 模块控制并发一文中介绍了如何用 eventproxy 模块并发完成 10 条请求,文中爬的是zoj,后来发现 zoj 有时网络太差,便改爬hdoj,完整代码可以猛戳这里,具体实现可以参考上文。今天我们要完成的事情还是类似,爬取 hdoj 1000-1099 100道题目的最优解提交时间,并发量控制为 5(本来想爬取最优解的用户 id,发现 hdoj 用的是 gbk,中文会乱码,这个问题暂时放一放)。
主要用到的是 async 的 mapLimit(arr, limit, iterator, callback)接口。用法很简单,直接看代码:
第一个参数 urls 为数组,保存了需要爬取页面的 100 个 url,第二个参数 5 表示并发爬取数量为 5,第三个参数是迭代函数(每个 url 需要执行这个函数),其第一个参数 url,是 urls 数组的每个 item,第二个参数 callback 与 mapLimit 方法第四个参数有关,callback 会往 result 参数里存放数据。如何理解?callback 是第三个参数 iterator 的回调,以爬虫为例,爬完页面肯定会分析一些数据,然后保存,执行 callback 函数就能把结果保存在 result(第四个参数函数中的参数) 中。
说完 mapLimit,我们从头开始这次爬虫。
首先,我们采集 100 个需要爬取页面的 url,保存在 urls 数组中:
// 需要爬的网址
function getUrls {
var urls =
, baseUrl = 'http://acm.hdu.edu.cn/statistic.php?pid=';
for (var i = 1000; i < 1010; i++) {
var tmp = baseUrl + i;
urls.push(tmp);
}
return urls;
}接着我们用 async 进行异步爬取:
app.get('/', function (req, res, next) {
var urls = getUrls;
// 并发量控制为 5
async.mapLimit(urls, 5, function (url, callback) {
fetchUrl(url, callback);
}, function (err, result) {
res.send(result);
});
});我们再来看 fetchUrl 函数:
// 抓取网页内容
function fetchUrl(url, callback) {
superagent.get(url)
.end(function (err, res) {
var page = res.text;
// 页面分析,返回需要的数据
var postTime = analyze(page);
callback(null, postTime);
});
}这里要注意的是 callback 中的第二个参数,其实 postTime 已经储存在了 mapLimit 方法第四个参数的 result 参数中。当然,你完全可以自定义变量来保存数据。
代码比较简单,完整代码可以猛戳 这里。
还有个问题是,什么时候用 eventproxy,什么时候使用 async 呢?它们不都是用来做异步流程控制的吗?
alsotang 的答案是:
当你需要去多个源(一般是小于 10 个)汇总数据的时候,用 eventproxy 方便;当你需要用到队列,需要控制并发数,或者你喜欢函数式编程思维时,使用 async。大部分场景是前者,所以我个人大部分时间是用 eventproxy 的。
经过测试,并发量确实为 5。具体测试方法,定义一个全局(global)变量 num,初始化为 0。当其进入 fetchUrl 函数时,num 自加,同时打印此时并发的数量,在 superagent 的 end 回调中,num 自减,因为此时该并发已经结束。
猜你喜欢
- 2025-09-18 GPU集群扩展:Ray Serve与Celery的技术选型与应用场景分析
- 2025-09-18 【不背八股】2.操作系统-进程、线程、协程的基本理解
- 2025-09-18 两张图看透Android Handler使用与机制
- 2025-09-18 Spring Boot 3.x 日志配置与 Logback 集成指南
- 2025-09-18 解锁C++异步之力:高效并发编程指南
- 2025-09-18 Flutter框架分析(八)-Platform Channel
- 2025-09-18 原来你是这样打印日志的,怪不得天天背锅……
- 2025-09-18 .NET Aspire 9.4 发布了 CLI GA、交互式仪表板和高级部署功能
- 2025-09-18 27.8K!一把梭 LLM:LiteLLM 带你用一套接口召唤 100+ 大模型
- 2025-09-18 Rust异步编程神器:用Tokio轻松创建定时任务
- 最近发表
-
- 聊一下 gRPC 的 C++ 异步编程_grpc 异步流模式
- [原创首发]安全日志管理中心实战(3)——开源NIDS之suricata部署
- 超详细手把手搭建在ubuntu系统的FFmpeg环境
- Nginx运维之路(Docker多段构建新版本并增加第三方模
- 92.1K小星星,一款开源免费的远程桌面,让你告别付费远程控制!
- Go 人脸识别教程_piwigo人脸识别
- 安卓手机安装Termux——搭建移动服务器
- ubuntu 安装开发环境(c/c++ 15)_ubuntu安装c++编译器
- Rust开发环境搭建指南:从安装到镜像配置的零坑实践
- Windows系统安装VirtualBox构造本地Linux开发环境
- 标签列表
-
- cmd/c (90)
- c++中::是什么意思 (84)
- 标签用于 (71)
- 主键只能有一个吗 (77)
- c#console.writeline不显示 (95)
- pythoncase语句 (88)
- es6includes (74)
- sqlset (76)
- apt-getinstall-y (100)
- node_modules怎么生成 (87)
- chromepost (71)
- flexdirection (73)
- c++int转char (80)
- mysqlany_value (79)
- static函数和普通函数 (84)
- el-date-picker开始日期早于结束日期 (76)
- js判断是否是json字符串 (75)
- c语言min函数头文件 (77)
- asynccallback (87)
- localstorage.removeitem (77)
- vector线程安全吗 (73)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- 无效的列索引 (74)
