基于 Electron 的爬虫框架 Nightmare

2021-02-04 14:08:13

Nightmare作为一个封装号的Electron爬虫框架,并没有太多惊艳之处,只是封装。留作以后参考用吧

参考地址 基于 Electron 的爬虫框架 Nightmare


Electron 可以让你使用纯 JavaScript 调用 Chrome 丰富的原生的接口来创造桌面应用。你可以把它看作一个专注于桌面应用的 Node.js 的变体,而不是 Web 服务器。其基于浏览器的应用方式可以极方便的做各种响应式的交互,接下来介绍下关于 Electron 上衍生出的框架 Nightmare。

Nightmare 是一个基于 Electron 的框架,针对 Web 自动化测试和爬虫(其实爬虫这个是大家自己给这个框架加的功能XD),因为其具有跟 PlantomJS 一样的自动化测试的功能可以在页面上模拟用户的行为触发一些异步数据加载,也可以跟 Request 库一样直接访问 URL 来抓取数据,并且可以设置页面的延迟时间,所以无论是手动触发脚本还是行为触发脚本都是轻而易举的(这边注意,如果事件具备 isTrusted 的检查的话,就无法触发了)。

使用 Nightmare

为了更快速使用 NPM 下载,可以使用淘宝的镜像地址。直接 NPM 安装Nightmare 就完成安装了(二进制的 Electron 依赖有点大,安装时间可能比较长)。

写一个简单的启动 app.js;

const Nightmare = require('nightmare')const nightmare = new Nightmare({     show: true,     openDevTools: {         mode: 'detach'
     }
 })

 nightmare.goto('https://www.hujiang.com')
   .evaluate(function() {       // 该环境中能使用浏览器中的任何对象window/document,并且返回一个promise
     console.log('hello nightmare')     console.log('5 second close window')
   })
   .wait(5000)
   .end()
   .then(()=> {     console.log('close nightmare')
   })

这个脚本会在打开的浏览器的调试控制台中打印出 hello nightmare 并且在5秒后关闭,随后在运行的该脚本的中输出 close nightmare。

Nightmare原理

利用了 Electron 提供的 Browser 的环境,同时具备了 Node.js 的 I/O 能力,所以可以很方便实现一个爬虫应用。Nightmare 的官网有更详细的介绍:

大致操作:

  • 浏览器事件: goto,back,forward,refresh,

  • 用户事件: click,mousedown,mouseup,mouseover,type,insert,select,check,uncheck,selectscrollTo

  • 向网页注入脚本: .js .css的文件类型原理是跟油猴差不多,可以编写自己的js代码注入十分方便

  • wait 函数可以按照延迟时间或者一个 dom 元素的出现

  • evaluate 以浏览器的环境运行的脚本函数,然后返回一个 promise 函数

一个完整的nightmare爬虫应用

我们以抓取知乎上的话题的为应用场景,需要的数据是知乎的话题信息 包含以下字段 话题名称/话题的图片/关注者数量/话题数量/精华话题数量,但是因为后三者只能在其父亲话题中包含,所以必须先抓父话题才能抓取子话题,而且这些子话题是以 hover 的形式在父话题中异步加载的,如果用Request/Superagent 需要 HTTP 传递其解析过的id才能获取,但是用Nightmare 可以直接调用其 hover 事件触发数据的加载。

第一步获取需要抓取的话题深度,默认的根是现在知乎的根话题;

/** 
* 抓取对应的话题页面的url和对应的深度保存到指定的文件名中
* @param {string} rootUrl - 顶层的url 
* @param {int} deep - 抓取页面的深度 
* @param {string} toFile - 保存的文件名
* @param {Function} cb - 完成后的回调 
*/async function crawlerTopicsFromRoot (rootUrl, deep, toFile, cb) {
  rootUrl = rootUrl ||'https://www.zhihu.com/topic/19776749/hot'
  toFile = toFile || './topicsTree.json'
  console.time()  const result = await interactive
      .iAllTopics(rootUrl, deep)  console.timeEnd()
  util.writeJSONToFile(result['topics'], toFile, cb)
}

crawlerTopicsFromRoot('', 2, '', _ => {  console.log('完成抓取')
})

然后进行交互函数的核心函数,注意在开始抓取前,要去看看知乎的 robots.txt 文件看看哪些能抓和抓取的间隔不然很容易 timeout 的错误。

// 获取对应的话题的信息const cntObj = queue.shift()const url = `https://www.zhihu.com/topic/${cntObj['id']}/hot`const topicOriginalInfo = await nightmare
  .goto(url)
  .wait('.zu-main-sidebar') // 等待该元素的出现
  .evaluate(function () {   // 获取这块数据
      return document.querySelector('.zu-main-sidebar').innerHTML
  })// .....若干步的操作后// 获取其子话题的数值信息const hoverElement = `a.zm-item-tag[href$='${childTopics[i]['id']}']`const waitElement = `.avatar-link[href$='${childTopics[i]['id']}']`const topicAttached = await nightmare
  .mouseover(hoverElement) // 触发hover事件
  .wait(waitElement)
  .evaluate(function () {      return document.querySelector('.zh-profile-card').innerHTML
  })
  .then(val => {      return parseRule.crawlerTopicNumbericalAttr(val)
  })
  .catch(error => {      console.error(error)
  })

cheerio 是一个 jQuery 的 selector 库,可以应用于 HTML 片段并且获得对应的DOM 元素,然后我们就可以进行对应的 DOM 操作->增删改查都可以,这边主要用来查询 DOM 和获取数据。

const $ = require('cheerio')/** *抓取对应话题的问题数量/精华话题数量/关注者数量 */const crawlerTopicNumbericalAttr = function (html) {  const $ = cheerio.load(html)  const keys = ['questions', 'top-answers', 'followers']  const obj = {}
  obj['avatar'] = $('.Avatar.Avatar--xs').attr('src')
  keys.forEach(key => {
      obj[key] = ($(`div.meta a.item[href$=${key}] .value`).text() || '').trim()
  })  return obj
}/** * 抓取话题的信息 */const crawlerTopics = function (html) {  const $ = cheerio.load(html)  const  obj = {}  const childTopics = crawlerAttachTopic($, '.child-topic')  
  obj['desc'] = $('div.zm-editable-content').text() || ''
  if (childTopics.length > 0) {
      obj['childTopics'] = childTopics
  }  return obj
}/** * 抓取子话题的信息id/名称 */const crawlerAttachTopic = function ($, selector) {  const topicsSet = []
  $(selector).find('.zm-item-tag').each((index, elm) => {      const self = $(elm)      const topic = {}
      topic['id'] = self.attr('data-token')
      topic['value'] = self.text().trim()
      topicsSet.push(topic)
  })  return topicsSet
}

然后一个简单的爬虫就完成了,最终获得部分数据格式如何:

{  "value": "rootValue",  "id": "19776749",  "fatherId": "-1",  "desc": "知乎的全部话题通过父子关系构成一个有根无循环的有向图。「根话题」即为所有话题的最上层的父话题。话题精华即为知乎的 Top1000 高票回答。请不要在问题上直接绑定「根话题」。这样会使问题话题过于宽泛。",  "cids": [      "19778317",      "19776751",      "19778298",      "19618774",      "19778287",      "19560891"
  ]
},
{  "id": "19778317",  "value": "生活、艺术、文化与活动",  "avatar": "https://pic4.zhimg.com/6df49c633_xs.jpg",  "questions": "3.7M",  "top-answers": "1000",  "followers": "91K",  "fid": "19776749",  "desc": "以人类集体行为和人类社会文明为主体的话题,其内容主要包含生活、艺术、文化、活动四个方面。",  "cids": [      "19551147",      "19554825",      "19550453",      "19552706",      "19551077",      "19550434",      "19552266",      "19554791",      "19553622",      "19553632"
  ]
},

总结

Nightmare 作为爬虫的最大优势是只需要知道数据所在页面的 URL 就可以获取对应的同步/异步数据,并不需要详细的分析 HTTP 需要传递的参数。只需要知道进行哪些操作能使得网页页面数据更新,就能通过获取更新后的 HTML 片段获得对应的数据,在 Demo 中的 Nightmare 是打开了 chrome-dev 进行操作的,但是实际运行的时候是可以关闭的,关闭了之后其操作的速度会有一定的上升。下面的项目中还包含了另外一个爬取的知乎的动态。

Demo源码地址: https://github.com/williamstar/nightmare-demo


  • 2018-12-04 15:30:01

    如何在Mac OS X上安装 Ruby运行环境

    ​ 对于新入门的开发者,如何安装 Ruby和Ruby Gems 的运行环境可能会是个问题,本页主要介绍如何用一条靠谱的路子快速安装 Ruby 开发环境。 此安装方法同样适用于产品环境!

  • 2018-12-04 15:31:15

    iOS--Pod install && Pod update

    许多人在最初接触CocoaPods时认为pod install只是在第一次为项目设置CocoaPods时使用,之后都应该使用pod update.看起来是这样,但也不是(But that's not the case at all.)。 这篇文章的目的就是教你啥时候用pod install,啥时候用pod update

  • 2018-12-04 15:33:19

    CocoaPods安装和使用教程

    当你开发iOS应用时,会经常使用到很多第三方开源类库,比如JSONKit,AFNetWorking等等。可能某个类库又用到其他类库,所以要使用它,必须得另外下载其他类库,而其他类库又用到其他类库,“子子孙孙无穷尽也”,这也许是比较特殊的情况。总之小编的意思就是,手动一个个去下载所需类库十分麻烦。另外一种常见情况是,你项目中用到的类库有更新,你必须得重新下载新版本,重新加入到项目中,十分麻烦。如果能有什么工具能解决这些恼人的问题,那将“善莫大焉”。所以,你需要 CocoaPods。

  • 2018-12-04 23:37:37

    pod install 和 pod update

    当我们新建一个Podfile文件运行后,会自动生成一个Podfile.lock文件,Podfile.lock文件里存储着我们已经安装的依赖库(pods)的版本。 当我们第一次运行Podfile时,如果对依赖库不指定版本的话,cocoapods会安装最新的版本,同时将pods的版本记录在Podfile.lock文件中。这个文件会保持对每个pod已安装版本的跟踪,并且锁定这些版本。

  • 2018-12-04 23:40:26

    pod删除已导入的第三方库和移除项目中的cocoapods

    CocoaPods是一个负责管理iOS项目中第三方开源库的工具。CocoaPods的项目源码在Github上管理。在我们有了CocoaPods这个工具之后,只需要将用到的第三方开源库放到一个名为Podfile的文件中,然后在命令行执行$ pod install命令。CocoaPods就会自动将这些第三方开源库的源码下载下来,并且为我的工程设置好相应的系统依赖和编译参数. 但是如果我们导入的某个第三方不适用,或者我们又不想使用该第三方,那我们又该如何将这些相关的东西从我们的项目中清理出去呢?

  • 2018-12-04 23:41:47

    制作自己的Pod库(公有/私有)

    目的:1.管理自己常用的类;2.组件化开发步骤:1.想一个比较酷的名字,在桌面简历文件夹。2.打开terminal,cd到这个文件夹下面,执行pod lib create  xxx(这里我们以JJCategoryKit为例子,下同)命令,如下图。这个过程会问几个问题,根据实际情况输入回答即可。这里我们选择添加demo,结束的时候会自动Lanuch这个app. 作者:深水日月 链接:https://www.jianshu.com/p/ece0b5721461 來源:简书 简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

  • 2018-12-05 06:08:26

    CocoaPods建立私有仓库 spec repo

    好多项目里都有公共的组件,copy来,copy去很容易出错,而且不容易维护,所以就想到用用cocoapods 建自己的私有库,Carthage用法虽然相对简单,但是它是把公共组件都放在framework里不容易单步调试,所以我还是选择用Cocoapods 来建立私有仓库 参考使用Cocoapods创建私有podspec

  • 2018-12-05 15:11:18

    为什么 Objective-C非常难

    作为一个Objective-C的coder,我总能听到一部 分人在这门语言上抱怨有很多问题。他们总在想快速学习这门语言来写一个App出来,但他们也总是联想到Objective-C看上去实在太难了或者在想这 些语法符号都是神马玩意?不错,他们问得非常好,所以本人也解释一下为什么很多程序员相比较学习Ruby或者Java很容易,但在决定开发iOS或者OS X应用时会那么犹豫。

  • 2018-12-05 15:22:23

    十分钟让你明白Objective-C的语法(和Java、C++的对比)

    很多想开发iOS,或者正在开发iOS的程序员以前都做过Java或者C++,当第一次看到Objective-C的代码时都会头疼,Objective-C的代码在语法上和Java, C++有着很大的区别,有的同学会感觉像是看天书一样。不过,语言都是相通的,有很多共性。下面列出Objective-C语言的语法和Java,C++的对比,这样你就会很容易Objective-C的语法是怎么回事了。

  • 2018-12-05 15:33:33

    一篇文章看懂有关iOS开发语言的一切!

    OS开发语言有哪些?OS开发语言主要包括什么?iOS开发语言具体怎么学习?今天重点介绍一下: iOS开发语言主要包括:C语言基础、Obiective-C编程、Swift、UIKit框架详解这几大块,在这里项目阶段就不详细的介绍了。 C语言基础 C语言是开发语言的基础,是最常用的一门程序设计语言,最常用于编写计算机程序。