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


Posted in Javascript onJune 12, 2016

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

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

关键点
全局变量设定:

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

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

$ = 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的操作会有序进行,并不能让这些操作与其它的操作有序化!如,下面的代码就是不正确的。

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)之间被运行!哪怎么办?
我使用递归来解决这个问题。如下示例代码:

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);

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

var regs = [/<link rel=\"stylesheet\" href=\"?[^\"]*\">/g,
  /<script>?[^<]*<\/script>/g,
 /<style>?[^<]*<\/style>/g,
 /<a ?[^>]*>/g,
 /<\/a>/g,
 /srcset=(\"?[^\"]*\")/g
 ]
 regs.forEach(function(rs){
  var mactches = rsHtml.match(rs);
  for (var i=0;i < mactches.length ; i++)
  {
   rsHtml = rsHtml.replace(mactches[i],mactches[i].indexOf('stylesheet')>-1?'<link rel="stylesheet" href="wiki'+(i+1)+'.css"':'');
  }
 })

运行效果
上维基中文是需要FQ的,试运行了一下,抓取 航空母舰 分类,运行过程中,发现了三百左右的相关链接(包括分类页面,这些页面我是只取有效链接,不下载),最终正确的下载了209个,手工测试了一些出错链接,发现都为无效链接,显示该词条还未建立,整个过程大概花了不到十五分钟,压缩后近三十M,感觉效果还不错。

源代码
https://github.com/zhoutk/wikiSpider
小结
到昨晚基本完成任务,思路一能够抓取内容比较准确的页面,而且页面不重复,但抓取效率不高,分类信息无法准确获得;思路二能够按维基百科的分类,自动抓取并分门别类的把文件存储到本地,效率高(实测,抓取【军舰】类,共抓取页面近六千个,费时五十来分钟,每分钟能抓取超过一百个页面),能准确的保存分类信息。
最大的收获在于深刻的理解了异步编程的整体流程控制。

Javascript 相关文章推荐
用js判断浏览器是否是IE的比较好的办法
May 08 Javascript
jQuery使用andSelf()来包含之前的选择集
May 19 Javascript
JavaScript跨浏览器获取页面中相同class节点的方法
Mar 03 Javascript
微信小程序 跳转传参数与传对象详解及实例代码
Mar 14 Javascript
js匿名函数使用&amp;传参(实例)
Sep 08 Javascript
4个顶级JavaScript高级文本编辑器
Oct 10 Javascript
傻瓜式解读koa中间件处理模块koa-compose的使用
Oct 30 Javascript
javascript中的with语句学习笔记及用法
Feb 17 Javascript
JS浏览器BOM常见操作实例详解
Apr 27 Javascript
在vue中使用cookie记住用户上次选择的实例(本次例子中为下拉框)
Sep 11 Javascript
vue3.0 加载json的方法(非ajax)
Oct 26 Javascript
原生JavaScript实现换肤
Feb 19 Javascript
JavaScript解八皇后问题的方法总结
Jun 12 #Javascript
jQuery遍历json的方法(推荐)
Jun 12 #Javascript
jQuery移动端图片上传组件
Jun 12 #Javascript
jQuery通过ajax请求php遍历json数组到table中的代码(推荐)
Jun 12 #Javascript
JavaScript中实现键值对应的字典与哈希表结构的示例
Jun 12 #Javascript
JavaScript中输出信息的方法(信息确认框-提示输入框-文档流输出)
Jun 12 #Javascript
JS中常用的输出方式(五种)
Jun 12 #Javascript
You might like
PHP file_get_contents 函数超时的几种解决方法
2009/07/30 PHP
PHP编写登录验证码功能 附调用方法
2016/05/19 PHP
Yii遍历行下每列数据的方法
2016/10/17 PHP
Discuz论坛密码与密保加密规则
2016/12/19 PHP
PHP简单实现图片格式转换(jpg转png,gif转png等)
2019/10/30 PHP
js中window.open打开一个新的页面
2014/08/10 Javascript
JavaScript中获取样式的原生方法小结
2014/10/08 Javascript
jquery实现简单文字提示效果
2015/12/02 Javascript
JS如何设置iOS中微信浏览器的title
2016/11/22 Javascript
Vue过滤器的用法和自定义过滤器使用
2017/02/08 Javascript
JS表单数据验证的正则表达式(常用)
2017/02/18 Javascript
详解vue过滤器在v2.0版本用法
2017/06/01 Javascript
Bootstrap fileinput文件上传组件使用详解
2017/06/06 Javascript
bootstrap模态框嵌套、tabindex属性、去除阴影的示例代码
2017/10/17 Javascript
jQuery实现获取选中复选框的值实例详解
2018/06/28 jQuery
[03:54]DOTA2英雄梦之声_第06期_昆卡
2014/06/23 DOTA
[01:04:05]Mineski vs TNC 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/16 DOTA
浅谈flask截获所有访问及before/after_request修饰器
2018/01/18 Python
python 实现将文件或文件夹用相对路径打包为 tar.gz 文件的方法
2019/06/10 Python
Python定时任务工具之APScheduler使用方式
2019/07/24 Python
Python通过Manager方式实现多个无关联进程共享数据的实现
2019/11/07 Python
css3 旋转按钮 使用CSS3创建一个旋转可变色按钮
2012/12/31 HTML / CSS
Html5游戏开发之乒乓Ping Pong游戏示例(三)
2013/01/21 HTML / CSS
瑞贝卡·泰勒官方网站:Rebecca Taylor
2016/09/24 全球购物
巴西食品补充剂在线零售商:Músculos na Web
2017/08/07 全球购物
捷克家居装饰及图书音像购物网站:Velký košík
2018/04/16 全球购物
英国票务网站:Ticketmaster英国
2018/08/27 全球购物
什么是smarty? Smarty的优点是什么?
2013/08/11 面试题
什么是URL
2015/12/13 面试题
酒店led欢迎词
2014/01/09 职场文书
cf战队收人广告词
2014/03/14 职场文书
2015年党员干部承诺书
2015/01/21 职场文书
2016年习总书记讲话学习心得体会
2016/01/20 职场文书
《落花生》教学反思
2016/02/16 职场文书
解决MultipartFile.transferTo(dest) 报FileNotFoundExcep的问题
2021/07/01 Java/Android
Win11 Beta 22621.601 和 22622.601今日发布 KB5017384修复内容汇总
2022/09/23 数码科技