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 相关文章推荐
JavaScript 原型学习总结
Oct 29 Javascript
jquery实现弹出窗口效果的实例代码
Nov 28 Javascript
在JS中如何调用JSP中的变量
Jan 22 Javascript
jQuery使用之处理页面元素用法实例
Jan 19 Javascript
jquery使用each方法遍历json格式数据实例
May 18 Javascript
JS将滑动门改为选项卡(需鼠标点击)的实现方法
Sep 27 Javascript
js实现仿微博滚动显示信息的效果
Dec 21 Javascript
JS+CSS3制作炫酷的弹窗效果
Nov 08 Javascript
vue 和vue-touch 实现移动端左右导航效果(仿京东移动站导航)
Apr 22 Javascript
js实现文件上传功能 后台使用MultipartFile
Sep 08 Javascript
JavaScript学习笔记之DOM基础操作实例小结
Jan 09 Javascript
微信小程序实现3D轮播图效果(非swiper组件)
Sep 21 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 基本语法格式
2009/12/15 PHP
php中利用post传递字符串重定向的实现代码
2011/04/21 PHP
php 面试碰到过的问题 在此做下记录
2011/06/09 PHP
PHP中使用foreach和引用导致程序BUG的问题介绍
2012/09/05 PHP
30个php操作redis常用方法代码例子
2014/07/05 PHP
PHP中的类型提示(type hinting)功能介绍
2015/07/01 PHP
php二维码生成以及下载实现
2017/09/28 PHP
PHP 应用容器化以及部署方法
2018/02/12 PHP
Yii2处理密码加密及验证的方法
2019/05/12 PHP
传智播客学习之java 反射
2009/11/22 Javascript
JavaScript实现页面滚动图片加载(仿lazyload效果)
2011/07/22 Javascript
利用js实现选项卡的特别效果的实例
2013/03/03 Javascript
GridView中获取被点击行中的DropDownList和TextBox中的值
2013/07/18 Javascript
javascript从作用域链谈闭包
2020/07/29 Javascript
基于javascript实现页面加载loading效果
2020/09/15 Javascript
JS实现物体带缓冲的间歇运动效果示例
2016/12/22 Javascript
利用JS代码自动删除稿件的普通弹幕功能
2019/09/20 Javascript
微信小程序自定义联系人弹窗
2020/05/26 Javascript
在vue中实现禁止回退上一步,路由不存历史记录
2020/07/22 Javascript
vue监听浏览器原生返回按钮,进行路由转跳操作
2020/09/09 Javascript
关于python的bottle框架跨域请求报错问题的处理方法
2017/03/19 Python
python Matplotlib底图中鼠标滑过显示隐藏内容的实例代码
2019/07/31 Python
python numpy中cumsum的用法详解
2019/10/17 Python
使用pyhon绘图比较两个手机屏幕大小(实例代码)
2020/01/03 Python
浅谈keras中的目标函数和优化函数MSE用法
2020/06/10 Python
Python 使用SFTP和FTP实现对服务器的文件下载功能
2020/12/17 Python
html5表单及新增的改良元素详解
2016/06/07 HTML / CSS
世界上最大的各式箱包网络零售店:eBag
2016/07/21 全球购物
英国领先的NHS批准的在线药店:Pharmacy2U
2017/01/06 全球购物
豪华复古化妆:Besame Cosmetics
2019/09/06 全球购物
美国围栏公司:Walpole Outdoors
2019/11/19 全球购物
外贸业务员求职信范文
2013/12/12 职场文书
科级干部群众路线教育实践活动对照检查材料思想汇报
2014/09/20 职场文书
创先争优承诺书
2015/01/20 职场文书
2015年领导班子工作总结
2015/05/23 职场文书
2016年三八红旗手先进事迹材料
2016/02/26 职场文书