基于 Electron 的爬虫框架 Nightmare
作者:William
本文为原创文章,转载请注明作者及出处
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

iKcamp原创新书《移动Web前端高效开发实战》已在亚马逊、京东、当当开售。
报名地址:http://www.huodongxing.com/event/4404706591400
基于 Electron 的爬虫框架 Nightmare的更多相关文章
- scrapy爬虫框架实例一,爬取自己博客
本篇就是利用scrapy框架来抓取本人的博客,博客地址:http://www.cnblogs.com/shaosks scrapy框架是个比较简单易用基于python的爬虫框架,相关文档:http:/ ...
- 基于java的网络爬虫框架(实现京东数据的爬取,并将插入数据库)
原文地址http://blog.csdn.net/qy20115549/article/details/52203722 本文为原创博客,仅供技术学习使用.未经允许,禁止将其复制下来上传到百度文库等平 ...
- 基于redis的简易分布式爬虫框架
代码地址如下:http://www.demodashi.com/demo/13338.html 开发环境 Python 3.6 Requests Redis 3.2.100 Pycharm(非必需,但 ...
- [原创]一款基于Reactor线程模型的java网络爬虫框架
AJSprider 概述 AJSprider是笔者基于Reactor线程模式+Jsoup+HttpClient封装的一款轻量级java多线程网络爬虫框架,简单上手,小白也能玩爬虫, 使用本框架,只需要 ...
- 基于Python使用scrapy-redis框架实现分布式爬虫
1.首先介绍一下:scrapy-redis框架 scrapy-redis:一个三方的基于redis的分布式爬虫框架,配合scrapy使用,让爬虫具有了分布式爬取的功能.github地址: https: ...
- 基于Java的开源爬虫框架WebCollector的使用
一.WebCollector介绍 WebCollector是一个无须配置.便于二次开发的JAVA爬虫框架(内核),它提供精简的的API,只需少量代码即可实现一个功能强大的爬虫. WebCollecto ...
- 基于node.js的爬虫框架 node-crawler简单尝试
百度爬虫这个词语,一般出现的都是python相关的资料. py也有很多爬虫框架,比如scrapy,Portia,Crawley等. 之前我个人更喜欢用C#做爬虫. 随着对nodejs的熟悉.发现做这种 ...
- 使用Squirrel创建基于Electron开发的Windows 应用安装包
我们把自己开发的Electron应用发布之前,需要把app打包成简单的安装包,这样app更容易被获取,以此来发布我们的应用.我们可以参考Wix或其他的安装程序,但是对于Electron应用更好的打包程 ...
- 爬虫框架YayCrawler
爬虫框架YayCrawler 各位好!从今天起,我将用几个篇幅的文字向大家介绍一下我的一个开源作品——YayCrawler,其在GitHub上的网址是:https://github.com/liush ...
随机推荐
- winform WebBrowser控件中,cs后台代码执行动态生成的js
很多文章都是好介绍C# 后台cs和js如何交互,cs调用js方法(js方法必须是页面上存在的,已经定义好的),js调用cs方法, 但如果想用cs里面执行动态生成的js代码,如何实现呢? 思路大致是这样 ...
- 利用python设计PDF报告,jinja2,whtmltopdf,matplotlib,pandas
转自:https://foofish.net/python-crawler-html2pdf.html 工具准备 弄清楚了网站的基本结构后就可以开始准备爬虫所依赖的工具包了.requests.beau ...
- git 工作流介绍
GIT Git工作流你可以理解为工作中团队成员遵守的一种代码管理方案,在Git中有以下几种工作流方案作为方案指导: 集中式工作流 功能开发工作流 Gitflow工作流 Forking工作流 下面针对性 ...
- CentOS7.3虚拟机扩展数据磁盘
操作之前需要重点查看: 由于扩容磁盘的操作非同小可,一旦哪一步出现问题,就会导致分区损坏,数据丢失等一系列严重的问题,因此建议:在进行虚拟机分区扩容之前,一定要备份重要数据文件,并且先在测试机上验证以 ...
- 车牌识别OCR—易泊时代智慧城市解决方案模块
牌识别(License Plate Recognition,LPR) 是视频图像识别技术在智能交通领域中的一个模块.车牌识别运用OCR技术,将视频流或图片中的汽车牌照从复杂的应用场景中提取并识别出来, ...
- 如何使用EF优雅的配置一对一的关系
在这两天的时间已经有两位同事问到EF(Code First)如何配置一对一的关系,这个说难也不难,说简单吧,一旦设计跑偏那么在Coding的过程中将会很痛苦. 先举个很简单的例子,两个类User和Pr ...
- Project 1 :创建链表与显示链表
目标:创建一个链表,并将链表输出.结构体中包括学号与分数.链表以输入学号为0作为结束.输出模版为 No.学号 Score:分数 输入样例: 10101 98 10102 97 10103 100 10 ...
- java 多线程(0) Java线程
线程 线程是系统调度的基本单元,每当创建一个进程时,会有许多的线程,也叫轻量级进程,在一个进程中拥有多个线程,各自都有自己的计数器,堆和局部变量属性,并且能够分享内存变量. 为什么要使用多线程 1. ...
- Java 多线程(二) 线程的实现
线程的实现 在Java中通过run方法为线程指明要完成的任务,有两种技术来为线程提供run方法: 1.继承Thread类并重写它的run方法.之后创建这个子类的对象并调用start()方法. 2.通过 ...
- WEB跨域资源共享:Cross-origin Resource Sharing(CORS)
跨域资源共享(CORS):浏览器同源策略中的同源指协议+域名+端口三者完全一致,其中任何一个不同即为跨域 1. 浏览器同源政策是隔离潜在恶意文件的安全机制,限制信息传递和使用的边界,不是信息的保密机制 ...