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 动态数据下的锚点错位问题解决方法
Dec 24 Javascript
js 编程笔记 无名函数
Jun 28 Javascript
javascript-简单的日历实现及Date对象语法介绍(附图)
May 30 Javascript
iframe里面的元素触发父窗口元素事件的jquery代码
Oct 19 Javascript
jQuery中$this和$(this)的区别介绍(一看就懂)
Jul 06 Javascript
辨析JavaScript中的Undefined类型与null类型
May 26 Javascript
获取JS中网页各种高宽与位置的方法总结
Jul 27 Javascript
详解react如何在组件中获取路由参数
Jun 15 Javascript
vue日历/日程提醒/html5本地缓存功能
Sep 02 Javascript
原生js实现随机点名
Jul 05 Javascript
js实现全选和全不选功能
Jul 28 Javascript
jquery插件实现搜索历史
Apr 24 jQuery
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分页函数示例代码分享
2014/02/24 PHP
php使用$_POST或$_SESSION[]向js函数传参
2014/09/16 PHP
php单元测试phpunit入门实例教程
2017/11/17 PHP
phpcms实现验证码替换及phpcms实现全站搜索功能教程详解
2017/12/13 PHP
PHP定义字符串的四种方式详解
2018/02/06 PHP
PHP使用HTML5 FileApi实现Ajax上传文件功能示例
2019/07/01 PHP
laravel入门知识点整理
2020/09/15 PHP
点击文章内容处弹出页面代码
2009/10/01 Javascript
理解Javascript_09_Function与Object
2010/10/16 Javascript
myFocus slide3D v1.1.0 使用方法与下载
2011/01/12 Javascript
jquery判断RadioButtonList和RadioButton中是否有选中项示例
2013/09/29 Javascript
JS连接SQL数据库与ACCESS数据库的方法实例
2013/11/21 Javascript
javascript禁用Tab键脚本实例
2013/11/22 Javascript
关于javascript的一些知识以及循环详解
2016/09/12 Javascript
Vue组件通信之Bus的具体使用
2017/12/28 Javascript
微信小程序自定义组件的实现方法及自定义组件与页面间的数据传递问题
2018/10/09 Javascript
jQuery轻量级表单模型验证插件
2018/10/15 jQuery
vue基于两个计算属性实现选中和全选功能示例
2019/02/08 Javascript
JavaScript 九种跨域方式实现原理
2019/02/11 Javascript
JavaScript中的this原理及6种常见使用场景详解
2020/02/14 Javascript
Vue环境搭建+VSCode+Win10的详细教程
2020/08/19 Javascript
JS中多层次排序算法的实现代码
2021/01/06 Javascript
[07:27]DOTA2卡尔工作室 英雄介绍水晶室女篇
2013/06/21 DOTA
[01:07:47]Secret vs Optic Supermajor 胜者组 BO3 第一场 6.4
2018/06/05 DOTA
Python socket C/S结构的聊天室应用实现
2014/11/30 Python
python打包exe开机自动启动的实例(windows)
2019/06/28 Python
Python Django 页面上展示固定的页码数实现代码
2019/08/21 Python
pycharm中使用request和Pytest进行接口测试的方法
2020/07/31 Python
Pycharm的Available Packages为空的解决方法
2020/09/18 Python
高清安全摄像头系统:Lorex Technology
2018/07/20 全球购物
关于抽烟的检讨书
2014/02/25 职场文书
2014年五一活动策划方案
2014/03/15 职场文书
无保留意见审计报告
2015/06/05 职场文书
小学运动会前导词
2015/07/20 职场文书
企业转让协议书(范文2篇)
2019/08/15 职场文书
vue 把二维或多维数组转一维数组
2022/04/24 Vue.js