当前位置: 首页 > 后端技术 > Node.js

【从零开始写一个爬虫1】批量下载表情包

时间:2023-04-03 22:45:03 Node.js

前言我打算写一个关于node的爬虫教程,接下来带大家一步步写一个表情包爬虫,从获取页面,解析表情包打包链接,清理脏数据,下载表情到本地。在开始之前,你需要对chrome调试工具和ES6有一定的了解,包括async/await的使用。获取页面地址,我们打开U表情搜索地址,如下图,可以看到http://www.ubiaoqing.com/search/加上关键词,就是完整的搜索结果地址。准备工作需要先安装node7.x以上版本。如果没有安装,请自行下载安装。我不会在这里解释。我们新建一个js文件memes.js和一个存放表情包的文件夹memes;然后安装我们需要的模块npminstallcheerio--savenpminstallsuperagent--savecheerionode版本的jQuery,用法同jQuery。Chineseinformationsuperagent轻量级、渐进式请求模块。中文资料正式开始,我们先请求获取HTML的地址'usestrict'constrequest=require('superagent');constcheerio=require('cheerio');constSEARCH_URL='http://www.ubiaoqing.com/search/';constkeyword='SingleDog';letpage=1;letlinkAssemble=[];//链接收集函数requestURL(keyword,page){leturl=`${SEARCH_URL}${encodeURI(keyword)}/${page}`;//抓取地址returnrequest.get(url).then(res=>res.text);}可以看到表情包的链接在li->a->下class为ver-middle的div中img的href属性,使用jQuery选择器是div.ver-middle>a;我们要获取li下的所有表情functionselectLink(html){let$=cheerio.load(html);//加载html到cheerio//遍历所有标签,获取href属性returnArray.from($('li.ver-middle').map(function(){return$(this).find('img').attr('src');}))}解析后返回数据如下:.jpg","http://ubq.ubiaoqing.com/ubiaoqing50ff2027dd2e0b257da02fe6cb364ea5.gif,"http://ubq.ubiaoqing.com/ubiaoqing1ad82a6cf881cd0205765b69a5073188.jpg","http://ubq.ubiaoqing.com/ubiaoqing57e7ad299029e31406.gif","http://ubq.ubiaoqing.com/ubiaoqingbe7e7ff3368c5c5cadgif","https://img.alicdn.com/imgextra/i2/3161190279/TB2kn9rdMRkpuFjy1zeXXc.6FXa_!!3161190279.jpg","http://ubq.ubiaoqing.com/ubiaoqing7a8e3432684650ab2e12bf3a234e4203/jpg","http/file.ubiaoqing.com/mp-weixin.jpg"]但是我们得到的数据并不干净,包括中间的一些广告,这显然不符合我们的预期,我们需要过滤掉那些不包含http://ubq的.ubiaoqing.com/ubiaoqing前缀的链接functioncleanseLink(links){returnlinks.filter((link)=>link.includes('http://ubiaoqing.com/ubiaoqing'));}清洗后返回["http://ubq.ubiaoqing.com/ubiaoqing18891b279231433893c19bc0a7507f5a.jpg","http://ubq.ubiaoqing.com/ubiaoqingb8b5240336c953316b99d3e4963b13b6.jpg","http://ubq.ubiaoqing.com/ubiaoqing50ff2027dd2e0b257da02fe6cb364ea5.gif","http://ubiaoqing.com/ubiaoqing1ad82a6cf881cd0205765b69a5073188.jpg,"http://ubq.ubiaoqing.com/ubiaoqing57e7ad299029e31406.gif","http://ubq.ubiaoqing.com/ubiaoqingcadd8ff3468c4c03a052e55b1a7ad825.gif","http://ubq.ubiaoqing.com/ubiaoqing7a8e3432684650ab2e132functionbgetf3"/step1获取页面lethtml=awaitrequestURL(keyword,page);//step2解析数据selectLink(html);//step3清理脏数据cleanseLink(html);}然后我们会递归获取所有页面的表情.我们翻到最后一页,看到没有下一页的按钮,我们可以根据这个条件判断是否是最后一页asyncfunctiongetLinksByPage(keyword,page){try{//step1getpageconsole.log(`getpage->keyword:${keyword}page${page}`);lethtml=awaitrequestURL(keyword,page);//step2parsedataconsole.log(`Parsedata...`);letlinks=selectLink(html);//step3清除脏数据console.log('cleandata...');letresult=cleanseLink(links);//将结果添加到linksAssembleArray.prototype.push.apply(linkAssemble,result);//如果有下一页,继续抓取下一页的emoji链接if(html.includes('nextpage')){returngetLinksByPage(keyword,++page);}console.log(linkAssemble);返回链接组装;}catch(err){console.error(err.message);//如果有错误,则跳过当前页面继续爬取!返回getLinksByPage(关键字,++页面);}}运行测试,然后让我们运行它。由于使用了async/await,所以我们需要添加--harmony参数node--harmonymemes.js至此第一部分代码已经完成。完整代码如下:github'usestrict'//启用严格模式constrequest=require('superagent');constcheerio=require('cheerio');constSEARCH_URL='http://www.ubiaoqing.com/search/';constkeyword='单身狗';letpage=1;letlinkAssemble=[];//链接收集asyncfunctiongetLinksByPage(keyword,page){try{//step1获取页面console.log(`getpage->Keyword:${keyword}page${page}`);lethtml=awaitrequestURL(keyword,page);//step2解析数据console.log(`parsedata...`);让链接=selectLink(html);//步3清除脏数据console.log('Cleansedata...');让结果=cleanseLink(链接);//将结果添加到linksAssembleArray.prototype.push.apply(linkAssemble,result);//如果有一页继续抓下一页的表情包链接if(html.includes('nextpage')){returngetLinksByPage(keyword,++page);}返回链接组装;}catch(err){//错误则跳过当前页面,继续爬!console.error(err.message);返回getLinksByPage(关键字,++页面);}}getLinksByPage(keyword,page);/***@requestURL*@paramkeyword{String}搜索关键字*@parampage{String}页数*@returnrequest**/functionrequestURL(keyword,page){leturl=`${SEARCH_URL}${encodeURI(keyword)}/${page}`;//抓取URLreturnrequest.get(url).then(res=>res.text);}/***@requestURL*@paramhtml{String}待解析的html*@returnlinks{Array}**/函数selectLink(html){让$=cheerio.load(html);//加载html到cheerio//遍历所有标签,获取href属性returnArray.from($('li.ver-middle').map(function(){return$(this).find('img').attr('src');}))}/***@cleanseLink*@paramlinks{Array}需要清理的链接*@returnlinks{Array}**/functioncleanseLink(links){returnlinks.filter((link)=>link.includes('http://ubq.ubiaoqing.com/ubiaoqing')));}第一部分我们完成了表情包链接的获取,接下来开始批量下载表情包到本地下载表情包,在本地观察表情包地址。我们发现表情包后面的22位数字是其完整且唯一的文件名。我们先判断本地是否存在该文件,存在则跳过下载。如果不存在,我们创建一个可写文件流,然后请求表情包地址,pipe到流中,监听close事件,触发完成Promise。functiondownloadMeMe(url){console.log(`下载:${url}`);让filePath=`./memes/${url.substr(-22)}`;//获取最后22位作为文件名letstream=fs.createWriteStream(filePath);//创建一个可写流对象//请求表情包地址,并管道到刚刚创建的流对象request.get(url).pipe(stream);}限流器假设当我们打开表情包页面时,他会请求一个整个页面的表情符号,所以我们只需要限制批量请求之间的间隔。写一个限流器来控制单个请求的数量。如果访问频率太快,爬虫会被发现。您还可以设置随机延迟。functiontimerChunk(any,fn,limit,wait=0){letrun=asyncfunction(){if(!any.length){返回;}//这里延迟等待是随机0等待毫秒await(newPromise((resolve,reject)=>setTimeout(resolve,~~(Math.random()*wait))));让params=any.splice(0,limit);//每次取出限制数量的任务params.forEach((param)=>fn(param));返回运行();}returnrun();}组装函数的最后一步是构建块来拼凑函数letlinks=awaitgetLinksByPage(keyword,1);//在本地下载表情符号awaittimerChunk(links,downloadMeMe,5,3000);console.log('Complete!');}catch(err){console.error(err);}})();让我们运行我们项目的完整代码