Nodejs爬虫进阶教程之异步并发控制


Posted in NodeJs onFebruary 15, 2016

之前写了个现在看来很不完美的小爬虫,很多地方没有处理好,比如说在知乎点开一个问题的时候,它的所有回答并不是全部加载好了的,当你拉到回答的尾部时,点击加载更多,回答才会再加载一部分,所以说如果直接发送一个问题的请求链接,取得的页面是不完整的。还有就是我们通过发送链接下载图片的时候,是一张一张来下的,如果图片数量太多的话,真的是下到你睡完觉它还在下,而且我们用nodejs写的爬虫,却竟然没有用到nodejs最牛逼的异步并发的特性,太浪费了啊。

思路

这次的的爬虫是上次那个的升级版,不过呢,上次那个虽然是简单,但是很适合新手学习啊。这次的爬虫代码在我的github上可以找到=>NodeSpider。

整个爬虫的思路是这样的:在一开始我们通过请求问题的链接抓取到部分页面数据,接下来我们在代码中模拟ajax请求截取剩余页面的数据,当然在这里也是可以通过异步来实现并发的,对于小规模的异步流程控制,可以用这个模块=>eventproxy,但这里我就没有用啦!我们通过分析获取到的页面从中截取出所有图片的链接,再通过异步并发来实现对这些图片的批量下载。

抓取页面初始的数据很简单啊,这里就不做多解释啦

/*获取首屏所有图片链接*/
var getInitUrlList=function(){
request.get("https://www.zhihu.com/question/")
.end(function(err,res){
if(err){
console.log(err);
}else{
var $=cheerio.load(res.text);
var answerList=$(".zm-item-answer");
answerList.map(function(i,answer){
var images=$(answer).find('.zm-item-rich-text img');
images.map(function(i,image){
photos.push($(image).attr("src"));
});
});
console.log("已成功抓取"+photos.length+"张图片的链接");
getIAjaxUrlList();
}
});
}

模拟ajax请求获取完整页面

接下来就是怎么去模拟点击加载更多时发出的ajax请求了,去知乎看一下吧!

Nodejs爬虫进阶教程之异步并发控制

Nodejs爬虫进阶教程之异步并发控制

Nodejs爬虫进阶教程之异步并发控制

有了这些信息,就可以来模拟发送相同的请求来获得这些数据啦。

/*每隔毫秒模拟发送ajax请求,并获取请求结果中所有的图片链接*/
var getIAjaxUrlList=function(offset){
request.post("https://www.zhihu.com/node/QuestionAnswerListV")
.set(config)
.send("method=next¶ms=%B%url_token%%A%C%pagesize%%A%C%offset%%A" +offset+ "%D&_xsrf=adfdeee")
.end(function(err,res){
if(err){
console.log(err);
}else{
var response=JSON.parse(res.text);/*想用json的话对json序列化即可,提交json的话需要对json进行反序列化*/
if(response.msg&&response.msg.length){
var $=cheerio.load(response.msg.join(""));/*把所有的数组元素拼接在一起,以空白符分隔,不要这样join(),它会默认数组元素以逗号分隔*/
var answerList=$(".zm-item-answer");
answerList.map(function(i,answer){
var images=$(answer).find('.zm-item-rich-text img');
images.map(function(i,image){
photos.push($(image).attr("src"));
});
});
setTimeout(function(){
offset+=;
console.log("已成功抓取"+photos.length+"张图片的链接");
getIAjaxUrlList(offset);
},);
}else{
console.log("图片链接全部获取完毕,一共有"+photos.length+"条图片链接");
// console.log(photos);
return downloadImg();
}
}
});
}

在代码中post这条请求https://www.zhihu.com/node/QuestionAnswerListV2,把原请求头和请求参数复制下来,作为我们的请求头和请求参数,superagent的set方法可用来设置请求头,send方法可以用来发送请求参数。我们把请求参数中的offset初始为20,每隔一定时间offset再加20,再重新发送请求,这样就相当于我们每隔一定时间发送了一条ajax请求,获取到最新的20条数据,每获取到了数据,我们再对这些数据进行一定的处理,让它们变成一整段的html,便于后面的提取链接处理。 异步并发控制下载图片再获取完了所有的图片链接之后,即判定response.msg为空时,我们就要对这些图片进行下载了,不可能一条一条下对不对,因为如你所看到的,我们的图片足足有

Nodejs爬虫进阶教程之异步并发控制

没错,2万多张,不过幸好nodejs拥有神奇的单线程异步特性,我们可以同时对这些图片进行下载。但这个时候问题来了,听说同时发送请求太多的话会被网站封ip哒!这是真的吗?我不知道啊,没试过,因为我也不想去试( ̄? ̄〃),所以这个时候我们就需要对异步并发数量进行一些控制了。

在这里用到了一个神奇的模块=>async,它不仅能帮我们拜托难以维护的回调金字塔恶魔,还能轻松的帮我们进行异步流程的管理。具体看文档啦,因为我自己也不怎么会用,这里就只用到了一个强大的async.mapLimit方法。真的很厉害哦。

var requestAndwrite=function(url,callback){
request.get(url).end(function(err,res){
if(err){
console.log(err);
console.log("有一张图片请求失败啦...");
}else{
var fileName=path.basename(url);
fs.writeFile("./img/"+fileName,res.body,function(err){
if(err){
console.log(err);
console.log("有一张图片写入失败啦...");
}else{
console.log("图片下载成功啦");
callback(null,"successful !");
/*callback貌似必须调用,第二个参数将传给下一个回调函数的result,result是一个数组*/
}
});
}
});
}
var downloadImg=function(asyncNum){
/*有一些图片链接地址不完整没有“http:”头部,帮它们拼接完整*/
for(var i=;i<photos.length;i++){
if(photos[i].indexOf("http")===-){
photos[i]="http:"+photos[i];
}
}
console.log("即将异步并发下载图片,当前并发数为:"+asyncNum);
async.mapLimit(photos,asyncNum,function(photo,callback){
console.log("已有"+asyncNum+"张图片进入下载队列");
requestAndwrite(photo,callback);
},function(err,result){
if(err){
console.log(err);
}else{
// console.log(result);<=会输出一个有万多个“successful”字符串的数组
console.log("全部已下载完毕!");
}
});
};

先看这里=>

Nodejs爬虫进阶教程之异步并发控制

mapLimit方法的第一个参数photos是所有图片链接的数组,也是我们并发请求的对象,asyncNum是限制并发请求的数量,如果没有这个参数的话,将会有同时两万多条请求发送过去,嗯,你的ip就会被成功的封掉,但当我们有这个参数时,比如它的值是10,则它一次就只会帮我们从数组中取10条链接,执行并发的请求,这10条请求都得到响应后,再发送下10条请求。告诉泥萌,并发到同时100条没有事的,下载速度超级快,再往上就不知道咯,你们来告诉我...

以上所述给大家介绍了Nodejs爬虫进阶教程之异步并发控制的相关知识,希望对大家有所帮助。

NodeJs 相关文章推荐
nodejs中实现阻塞实例
Mar 24 NodeJs
NodeJS使用formidable实现文件上传
Oct 27 NodeJs
Nodejs下DNS缓存问题浅析
Nov 16 NodeJs
nodejs 实现钉钉ISV接入的加密解密方法
Jan 16 NodeJs
详解nodejs操作mongodb数据库封装DB类
Apr 10 NodeJs
详解nodejs微信公众号开发——4.自动回复各种消息
Apr 11 NodeJs
详解使用PM2管理nodejs进程
Oct 24 NodeJs
Nodejs Express 通过log4js写日志到Logstash(ELK)
Aug 30 NodeJs
NodeJS加密解密及node-rsa加密解密用法详解
Oct 12 NodeJs
nodejs 使用nodejs-websocket模块实现点对点实时通讯
Nov 28 NodeJs
nodejs log4js 使用详解
May 31 NodeJs
nodejs实现聊天机器人功能
Sep 19 NodeJs
你一定会收藏的Nodejs代码片段
Feb 04 #NodeJs
Nodejs中session的简单使用及通过session实现身份验证的方法
Feb 04 #NodeJs
nodejs实现bigpipe异步加载页面方案
Jan 26 #NodeJs
NodeJS实现阿里大鱼短信通知发送
Jan 17 #NodeJs
实例详解Nodejs 保存 payload 发送过来的文件
Jan 14 #NodeJs
Nodejs express框架一个工程中同时使用ejs模版和jade模版
Dec 28 #NodeJs
深入浅析NodeJs并发异步的回调处理
Dec 21 #NodeJs
You might like
PHP4实际应用经验篇(6)
2006/10/09 PHP
Yii清理缓存的方法
2016/01/06 PHP
PHP生成随机数的方法总结
2018/03/01 PHP
php中isset与empty函数的困惑与用法分析
2019/07/05 PHP
JavaScript弹簧振子超简洁版 完全符合能量守恒,胡克定理
2009/10/25 Javascript
javascript如何创建表格(javascript绘制表格的二种方法)
2013/12/10 Javascript
用jquery写的一个万年历(自写)
2014/01/20 Javascript
在百度知道团队中快速审批新成员的js脚本
2014/02/02 Javascript
2014 年最热门的21款JavaScript框架推荐
2014/12/25 Javascript
JS+CSS实现Li列表隔行换色效果的方法
2015/02/16 Javascript
jQuery取消ajax请求的方法
2015/06/09 Javascript
Jquery数字上下滚动动态切换插件
2015/08/08 Javascript
Javascript简单实现面向对象编程继承实例代码
2015/11/27 Javascript
浅谈javascript中new操作符的原理
2016/06/07 Javascript
利用jQuery实现滑动开关按钮效果(附demo源码下载)
2017/02/07 Javascript
JavaScript 基础表单验证示例(纯Js实现)
2017/07/20 Javascript
bootstrap table合并行数据并居中对齐效果
2018/10/17 Javascript
Vue中用props给data赋初始值遇到的问题解决
2018/11/27 Javascript
详解几十行代码实现一个vue的状态管理
2019/01/28 Javascript
webpack自动打包和热更新的实现方法
2019/06/24 Javascript
原生javascript运动函数的封装示例【匀速、抛物线、多属性的运动等】
2020/02/23 Javascript
vue引入静态js文件的方法
2020/06/20 Javascript
Python多线程编程(七):使用Condition实现复杂同步
2015/04/05 Python
Python win32com 操作Exce的l简单方法(必看)
2017/05/25 Python
Python实现一个服务器监听多个客户端请求
2018/04/12 Python
python中使用zip函数出现错误的原因
2018/09/28 Python
python Dijkstra算法实现最短路径问题的方法
2019/09/19 Python
Python调用.net动态库实现过程解析
2020/06/05 Python
机械电子工程专业推荐信范文
2013/11/20 职场文书
带病坚持工作事迹
2014/05/03 职场文书
办公室文员岗位职责范本
2014/06/12 职场文书
节电标语大全
2014/06/23 职场文书
商务英语专业大学生职业生涯规划书
2014/09/14 职场文书
中班上学期个人总结
2015/02/12 职场文书
python函数的两种嵌套方法使用
2022/04/02 Python
浅谈Redis缓冲区机制
2022/06/05 Redis