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获取本机内网和外网ip地址的实现代码
Jun 01 NodeJs
抛弃Nginx使用nodejs做反向代理服务器
Jul 17 NodeJs
nodejs实现的一个简单聊天室功能分享
Dec 06 NodeJs
轻松创建nodejs服务器(2):nodejs服务器的构成分析
Dec 18 NodeJs
nodejs导出excel的方法
Jun 30 NodeJs
nodejs实现bigpipe异步加载页面方案
Jan 26 NodeJs
NodeJs使用Mysql模块实现事务处理实例
May 31 NodeJs
nodejs使用express获取get和post传值及session验证的方法
Nov 09 NodeJs
NodeJS安装图文教程
Apr 19 NodeJs
NodeJs实现简单的爬虫功能案例分析
Dec 05 NodeJs
nodejs实现日志读取、日志查找及日志刷新的方法分析
May 20 NodeJs
浅谈使用nodejs搭建web服务器的过程
Jul 20 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
PHP 中关于ord($str)&amp;gt;0x80的详细说明
2012/09/23 PHP
PHP循环函数使用介绍之PHP基础入门教程
2013/09/21 PHP
php 批量查询搜狗sogou代码分享
2015/05/17 PHP
crontab无法执行php的解决方法
2016/01/25 PHP
PHP chop()函数讲解
2019/02/11 PHP
PHP反射学习入门示例
2019/06/14 PHP
动态加载图片路径 保持JavaScript控件的相对独立性
2010/09/03 Javascript
对setInterval在火狐和chrome切换标签产生奇怪的效果之探索,与解决方案!
2011/10/29 Javascript
jQuery实现鼠标经过图片预览大图效果
2014/04/10 Javascript
Vue.js每天必学之数据双向绑定
2016/09/05 Javascript
Vue生命周期示例详解
2017/04/12 Javascript
Vue2.0基于vue-cli+webpack同级组件之间的通信教程(推荐)
2017/09/14 Javascript
深入理解js 中async 函数的含义和用法
2018/05/13 Javascript
在AngularJs中设置请求头信息(headers)的方法及不同方法的比较
2018/09/04 Javascript
js console.log打印对象时属性缺失的解决方法
2019/05/23 Javascript
vue项目中播放rtmp视频文件流的方法
2020/09/17 Javascript
原生js实现弹窗消息动画
2020/11/20 Javascript
[36:19]2018DOTA2亚洲邀请赛 小组赛 A组加赛 Newbee vs LGD
2018/04/03 DOTA
python装饰器与递归算法详解
2016/02/18 Python
python类:class创建、数据方法属性及访问控制详解
2016/07/25 Python
python在ubuntu中的几种安装方法(小结)
2017/12/08 Python
神经网络理论基础及Python实现详解
2017/12/15 Python
Flask入门之上传文件到服务器的方法示例
2018/07/18 Python
Python split() 函数拆分字符串将字符串转化为列的方法
2019/07/16 Python
python自定义时钟类、定时任务类
2021/02/22 Python
Django中的cookie和session
2019/08/27 Python
Python logging设置和logger解析
2019/08/28 Python
Pandas对DataFrame单列/多列进行运算(map, apply, transform, agg)
2020/06/14 Python
CSS3 实现发光边框特效
2020/11/11 HTML / CSS
详解移动端html5页面长按实现高亮全选文本内容的兼容解决方案
2016/12/03 HTML / CSS
STP的判定过程
2012/10/01 面试题
工作表现自我评价
2014/02/08 职场文书
放弃遗产继承公证书
2015/01/26 职场文书
校园广播稿范文
2015/08/19 职场文书
请学会珍惜眼前,因为人生没有下辈子!
2019/11/12 职场文书
JavaScript 中for/of,for/in 的详细介绍
2021/11/17 Javascript