nodejs写异步爬虫通过learnyounode课程初步了解了nodejs的主要模块后,不禁感受到了nodejs的强大,让前端小白也可以实现高级功能,同时发现自己已经可以通过nodejs实现一些日常的小功能了。比如看了fs模块,写了一个使用nodejs批量修改文件名的小demo,还是蛮好用的。技术为生活服务,这才是硬道理~上周用nodejs写了一个小爬虫,但是由于当时认知有限,爬虫虽然干活了,但是爬图片的时候老是丢图,经常出现并发过多的HTTP请求导致链接超时,导致爬虫挂掉。研究了别人的爬虫代码,决定用async重写一个爬取安居客租房信息的异步??爬虫,写下这篇笔记记录下自己的经验~爬虫完整代码:https://github.com/zzuzsj/我的N。..需求:使用爬虫将安居客杭州所有区域指定页面内的租房信息以文件夹的形式保存到本地,并将出租房屋的图片保存到对应的文件夹中。整理思路在写爬虫之前,我们需要整理一下思路。首先,我们需要分析一下安居客租房的网页跳转路径:租房信息页面路径:https://hz.zu.anjuke.com/fang...租房信息发布路径:https://hz。zu.anjuke.com/fang...租房信息贴里的楼盘图片路径:https://pic1.ajkimg.com/displ...emmmm,原来只有分页路径有迹可寻follow,这也是我们爬虫的突破点。在需求中,我们需要访问指定页面的租赁信息。我们可以通过node参数将指定数量的页面传入指定页面,拼接成对应页面的url并存储。使用请求模块请求所有分页url,使用cheerio解码页面结构并获取所有租赁信息帖子的路径并存储。保存所有posturl路径后,继续请求这些路径,获取所有租借图片的src路径并存储。存储好所有图片路径后,使用request和fs在本地创建对应的文件夹,将图片下载到本地。然后用async对这些操作进行异步申诉,并发请求时限制并发数。就酱,完美~1.模块引用cheerio和async是nodejs的第三方模块,需要先下载,在命令行运行:npminitnpminstallcheerioasync--save-dev安装完成后,我们在当前目录下创建一个ajkSpider.js,同时创建一个rent_image文件夹,用于存放爬虫爬取的信息。我们首先引用ajkSpider.js中的模块:constfs=require('fs');constcheerio=require('cheerio');constrequest=require('request');constasync=require('async');2.分页路径获取我们需要获取所有的分页路径,并存储在一个数组中。执行文件时通过传递参数指定起始页和结束页。例如nodeajkSpider.js15letpageArray=[];letstartIndex=process.argv[2];letendIndex=process.argv[2];for(leti=startIndex;i{if(err)cb(err,null);让$=cheerio.load(res.body.toString());$('.zu-itemmod').each((i,e)=>{lettopicObj={};lettitle=$(e).find('h3').find('a').attr('title').replace(/[(\s+)\:\、\,\*\\\:]/g,'');让topicUrl=$(e).find('h3').find('a').attr('href');让地址=$(e).find('地址').text().replace(/\/g,'').replace(/\s+/g,'');letprice=$(e).find('.zu-side').find('strong').text();letfileName=price+'_'+address+'_'+title;topicObj.fileName=文件名;topicObj.topicUrl=topicUrl;topicArray.push(topicObj);if(!fs.existsSync('./rent_image/'+fileName)){fs.mkdirSync('./rent_image/'+fileName);}//console.log(topicObj.topicUrl+'\n'+topicObj.fileName+'\n');})console.log('===============page'+pageindex+'end=============');cb(null,'page'+pageindex);页面索引++;});},(err,result)=>{if(err)抛出错误;console.log(topicArray.length);console.log(result+'完成');console.log('\n>1saveAllPageend');如果(回调){回调(null,'任务1saveAllPage');}})}为了方便查看,我把帖子的标题、价格和位置都保存下来,结合价格+位置+帖子标题做文件名,保证文件路径,为了保证标题的有效性,我去掉了标题中的特殊符号,所以有一堆多余的代码,不需要的可以自己去掉。获取到信息后,同步创建相应的文件,方便后期存储帖子中的listing图片。代码中的cb函数是async执行map操作所必需的内部回调。如果异步操作失败,调用cb(err,null)中断操作。如果异步操作成功,则调用cb(null,value)。后面代码中的cb大致就是这个意思。async遍历完所有的异步操作后,会调用map后面的回调函数,我们就可以通过这个回调来进行下一次的异步操作。4.属性图路径的获取我们已经保存了所有的帖子路径,接下来我们需要获取帖子中所有属性图的路径。同样的,我们可以参照上一步保存所有的图片路径。但是需要注意一点,如果post比较多,使用asyncmap功能进行请求很容易导致爬虫挂掉。所以为了爬虫的稳定性,我决定使用asyncmapLimit函数来遍历post路径。好处是可以控制http请求的并发数,让爬虫更稳定。写法和map函数类似,增加第二个参数——并发数量限制。让houseImgUrlArray=[];functionsaveAllImagePage(topicArray,callback){async.mapLimit(topicArray,10,function(obj,cb){request({'url':obj.topicUrl,'method':'GET','accept-charset':'utf-8','headers':{'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8','User-Agent':"Mozilla/5.0(Macintosh;IntelMacOSX10_11_0)AppleWebKit/537.36(KHTML,likeGecko)Chrome/46.0.2490.80Safari/537.36"}},(err,res,body)=>{if(err)cb(err,null);let$=cheerio.load(res.body.toString());letindex=0;$('.bigps').find('.picMove').find('li').find('img').each((i,e)=>{index++;letimgUrlArray={};imgUrlArray.fileName=obj.fileName;varimgsrc=($(e).attr('src').indexOf('默认')!=-1||$(e).attr('src').length<=0)?$(e).attr('数据源'):$(e).attr('src');imgUrlArray.imgsrc=imgsrc;安慰。日志(imgUrlArray.imgsrc+'\n');imgUrlArray.index=索引;houseImgUrlArray.push(imgUrlArray);});cb(null,obj.topicUrl+'\n');});},(err,result)=>{if(err)抛出错误;console.log(houseImgUrlArray.length);console.log('\n>2saveAllImagePageend');if(callback){callback(null,'task2saveAllImagePage');}})}由于页面中的大图采用了懒加载方式,所以对于大部分图片,我们无法直接从dom节点的src属性中获取图片路径。得到图片路径后,我们就可以存储起来,进入最后一步——下载图片~5.houseImageUrlArray中已经记录了房子图片下载和保存图片的文件夹信息。发送完请求后,我们只需要将保存的文件写入对应的文件夹即可。但是在爬虫启动的时候,经常会出现文件夹不存在导致爬虫中断的情况,所以在写文件之前,我先检查对应的路径是否存在,如果不存在,就直接创建文件,避免频繁出现爬虫的中断。下载图片是一个比较重的操作,所以我们不妨将并发请求数保持在较低水平,以保证爬虫的稳定性。functionsaveAllImage(houseImgUrlArray,callback){async.mapLimit(houseImgUrlArray,4,function(obj,cb){console.log(obj);if(!fs.existsSync('./rent_image/'+obj.fileName)){fs.mkdirSync('./rent_image/'+obj.fileName);}request({'url':obj.imgsrc,'method':'GET','accept-charset':'utf-8','headers':{'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8','User-Agent':"Mozilla/5.0(Macintosh;IntelMacOSX10_11_0)AppleWebKit/537.36(KHTML,如Gecko)Chrome/46.0.2490.80Safari/537.36",}}).pipe(fs.createWriteStream('./rent_image/'+obj.fileName+'/'+obj.index+'.jpg').on('close',function(){cb(null,obj.title+'imgrespose');}));},(err,result)=>{if(err)throwerr;console.log('\n>3saveAllImageend');if(callback){callback(null,'task3saveAllImage');}})}通过这一步,就可以把帖子里的房源图片下载到本地文件夹了~看到本地保存了这么多图片,不开心!刺痛不刺激!学会后就可以肆意爬图了!好吧,开个玩笑,稍大一点的网站都??会实施一些反爬虫策略。以安居客为例,懒加载勉强算是一种反爬虫手段。对于网页来说,会需要图片验证码验证,所以有时候运行爬虫什么都爬不出来。至于这种高级爬取技巧,以后再说高级爬取技巧吧。现在只是新手练习~6.组织异步过程其实通过异步回调将以上步骤组织起来就可以构成一个完整的爬虫。但是既然用了async,那我们就用到最后吧,把这些操作组织起来,让代码看起来更好看,更有逻??辑性。async的系列方法可以帮助我们轻松组织起来。函数startDownload(){异步。series([function(callback){//做一些事情...saveAllPage(process.argv[2],process.argv[3],callback);},function(callback){//做更多事情...saveAllImagePage(topicArray,callback);},//function(callback){////做一些更多的事情...//saveAllImageUrl(imgPageArray,callback);//},function(callback){//做一些更多内容...saveAllImage(houseImgUrlArray,callback);}],//可选回调函数(err,results){//结果现在等于['one','two']if(err)throwerr;console.log(结果+'成功');});}startDownload();本文小结虽然这只是最基本的爬虫,但并没有稳定性的保证,也没有破解反爬虫的措施。不过好在已经可以正常运行了~记得我写第一个版本的时候,虽然可以记录帖子的标题,但是图片反正是没有保存的。最多保存一两百张图片。爬虫将结束。参考了很多次,引入了async模块,重构了代码逻辑,终于存了一千多张图片,还是比较满意的~可以说async模块是写这个爬虫最有收获的地方,并且你也可以使用它。学习了nodejs之后,发现自己可以做的事情多了很多,很开心。同时,我也发现自己能做的很少,很苦恼。作为一个前端新手,不知道有什么好的学习方法,但是我知道,能做一些对自己有用的事情总是好的。不如用所学为生活服务。每个在成长路上的人都应该为自己加油,坚持走好下一步。例行给自己下一阶段定个小目标:结合nodejs和electron,写一个带爬虫功能的桌面软件~不知道能不能完成,做完再说吧~