Node.js环境下编写爬虫爬取维基百科内容的实例分享

基本思路

思路一(origin:master):从维基百科的某个分类(比如:航空母舰(key))页面开始,找出链接的title属性中包含key(航空母舰)的所有目标,加入到待抓取队列中。这样,抓一个页面代码及其图片的同时,也获取这个网页上所有与key相关的其它网页的地址,采取一个类广度优先遍历的算法来完成此任务。 思路二(origin:cat):按分类进行抓取。注意到,维基百科上,分类都以Category:开头,由于维基百科有很好的文档结构,很容易从任一个分类,开始,一直把其下的所有分类全都抓取下来。这个算法对分类页面提取子分类,且并行抓取其下所有页面,速度快,可以把分类结构保存下来,但其实有很多的重复页面,不过这个可以后期写个脚本就能很容易的处理。

库的选择

开始想用jsdom,虽然感觉它功能强大,但也比较“重”,最要命的是说明文档不够好,只说了它的优势,没一个全面的说明。因此,换成cheerio,轻量级,功能比较全,至少文档一看就能有一个整体概念。其实做到后来,才发现根本不需要库,用正则表达式就能搞定一切!用库只是少写了一点正则而矣。

关键点

全局变量设定:

rush:js;"> var regKey = ['航空母舰','航空母艦','航母']; //链接中若包含此中关键词,即为目标 var allKeys = []; //链接的title,也是页面标识,避免重复抓取 var keys = ['Category:%E8%88%AA%E7%A9%BA%E6%AF%8D%E8%88%B0']; //等待队列,起始页

图片下载

使用request库的流式操作,让每一个下载操作形成闭包。注意异步操作可能带来的副作用。另外,图片名字要重新设定,开始我取原名,不知道为什么,有的图明明存在,就是显示不出来;并且要把srcset属性清理掉,不然本面显示不出来。

rush:js;"> $ = cheer.load(downHtml); var rsHtml = $.html(); var imgs = $('#bodyContent .image'); //图片都由这个样式修饰 for(img in imgs){ if(typeof imgs[img].attribs === 'undefined' || typeof imgs[img].attribs.href === 'undefined') {continue;} //结构为链接下的图片链接不存在,跳过 else { var picUrl = imgs[img].children[0].attribs.src; //图片地址 var dirs = picUrl.split('.'); var filename = baseDir+uuid.v1()+'.'+dirs[dirs.length -1]; //重新命名
request("https:"+picUrl).pipe(fs.createWriteStream('pages/'+filename));  //下载

rsHtml = rsHtml.replace(picUrl,filename);  //换成本地路径
// console.log(picUrl);

}
}

广度优先遍历

开始没能完全理解异步的概念,以循环方式来做,以为使用了Promise,就已经全转化为同步了,但其实只是能保证交给promise的操作会有序进行,并不能让这些操作与其它的操作有序化!如,下面的代码就是不正确的。

rush:js;"> var keys = ['航空母舰']; var key = keys.shift(); while(key){ data.get({ url:encodeURI(key),qs:null }).then(function(downHtml){ ... keys.push(key); //(1) } }); key = keys.shift(); //(2) }

上面的操作看试很正常,但其实(2)会在(1)之间被运行!哪怎么办? 我使用递归来解决这个问题。如下示例代码

rush:js;"> var key = keys.shift(); (function doNext(key){ data.get({ url:key,qs:null }).then(function(downHtml){ ... keys.push(href); ... key = keys.shift(); if(key){ doNext(key); }else{ console.log('抓取任务顺利完成。') } }) })(key);

正则清理

使用正则表达式清理无用的页面代码,因为有很多模式需要处理,写了一个循环统一处理。

rush:js;"> var regs = [/?[^<]*<\/script>/g,/