最近项目中需要实现动态数据的抓取,之前在抓取动态页面的时候都是通过 Phantomjs
、Selenium
、Chrome 或 Firefox的 Headless
模式等方法来实现,自从 Google Chrome 团队推出官方的 Headless Chrome
工具 Puppeteer
之后,似乎之前使用的那些工具一下都黯淡了。
Puppeteer
是一个 Node
代码库,基于 DevTools
协议,提供高级 API 自动化控制谷歌 Chrome 或 Chromium 浏览器。Puppeteer
默认以 无界面方式
运行。
安装 Puppeteer 过程中会下载完整版的谷歌Chromium浏览器到 node_modules
目录。
从 1.7.0
版后,谷歌发布了新的 puppeteer-core
安装包,默认不再自动下载谷歌Chromium浏览器。
puppeteer-core
是 Puppeteer
的轻量级版本,复用本地已安装的浏览器,或者连接到远程浏览器。
安装yarn
这里我使用 yarn
来管理依赖包。
Mac
1 | $ brew install yarn |
1 | $ sudo npm install -g yarn |
Win
Chocolatey
是一个windows下的包管理器。
1 | choco install yarn |
使用yarn
查看版本信息
1 | $ yarn -v |
初始化项目
1 | $ yarn init |
添加一个依赖
1 | $ yarn add [package] |
全局安装依赖
1 | $ yarn global add [package] |
更新一个依赖
1 | $ yarn upgrade [package] |
移除一个依赖
1 | $ yarn remove [package] |
安装package.json中所有的依赖项
1 | $ yarn |
查看yarn配置
1 | $ yarn config list |
更改 registry
1 | #安装淘宝镜像 |
安装Puppeteer
修改地址源
这里我采用的是 puppeteer-core
。由于国内网络原因,需要修改仓库源地址:
1 | $ npm config set registry "https://registry.npm.taobao.org" |
配置Chrome路径
使用 puppeteer-core
,需要手动指定已安装的Chrome浏览器的安装路径。
Mac电脑上Chrome浏览器的的安装路径,可以通过在浏览器中输入 chrome:\\version
来查看。
我的电脑上的路径为:
1 | /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome |
Puppeteer配置
无界面模式
Puppeteer 默认使用 headless
模式运行,通过设置 headless:false
来显示GUI界面:
1 | const browser = await puppeteer.launch({ |
其他配置参数
其他 puppeteer.launch()
配置项:
1 | { |
沙箱、共享内存
1 | const browser = await puppeteer.launch({ |
--no-sandbox
: 去沙箱运行--disable-dev-shm-usage
: 默认情况下,Docker运行一个/dev/shm
共享内存空间为64MB 的容器。这通常对Chrome来说太小,并且会导致Chrome在渲染大页面时崩溃。要修复,必须运行容器docker run --shm-size=1gb
以增加/dev/shm
的容量。从Chrome 65开始,使用--disable-dev-shm-usage
标志启动浏览器即可,这将会写入共享内存文件/tmp
而不是/dev/shm
.
Linux沙箱:在计算机安全领域,沙箱(Sandbox)是一种程序的隔离运行机制,其目的是限制不可信进程的权限。沙箱技术经常被用于执行未经测试的或不可信的客户程序。为了避免不可信程序可能破坏其它程序的运行。
跳转到指定页
1 | await page.goto('https://github.com/login'); |
等待
1 | # 等待指定时间 ,second |
设置视图大小
1 | await page.setViewport({width: 1280, height: 600}) |
要注意:这里的视图大小指的是网页页面显示的大小,和浏览器界面的大小是两个概念。
截屏
fullPage
可以控制是否截取整个页面:
1 | await page.screenshot({ |
设置UserAgent
1 | await page.setUserAgent('Mozilla/5.0 (iPhone; CPU iPhone OS 9_0_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13A404 Safari/601.1') |
获取页面内容
返回页面的完整 html
代码,包括 doctype
:
1 | page.content(); |
拦截请求页面中的图片
1 | // 拦截请求页面中的图片 |
跳转等待页面加载完毕
有时候使用 timeout:3000
这样的方式,并不能完全确定页面能在相应的时间范围内加载完而导致异常,可以通过如下的参数来实现,而尽量少用 timeout
这种限定性的方式:
1 | await page.goto('https://discordbots.org', {waitUntil: 'domcontentloaded'}); |
getBoundingClientRect()
getBoundingClientRect()用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置。
一个例子
这里,实现了一个简单的页面截屏的功能:
get_png.js
:
1 | const puppeteer = require('puppeteer-core'); |
执行 node get_png.js
看效果。
遇到的问题
ERR_NAME_RESOLUTION_FAILED
使用 puppeteer
测试时报错:
1 | 2019-04-07 19:41:50: received 12260073 |
找到一种说法:设置 UserAgent
,如下:
1 | (async function main() { |
经测试观察,添加了 UserAgent
设置后确实不会报错了。
Promise中的console.log
在函数内有 console.log('按f12,我出现在浏览器的console中,并不在node命令行')
你会发现node命令行看不到这句话,而在Chromium的console中看见。
1 | const result = await page.$eval(selector, el => { |
在page.evaluate中用console是不能在node命令行打印出来的,不过有了监听事件就可以改变这个规则了。也可以在监听事件里面做容错处理。
如下的方式实现监听事件:
1 | page.on('console', msg => { |
Docker镜像
为了部署的方便,我实现了 puppeteer
环境的Docker镜像,具体可查看: