Puppeteer 入门使用教程

Puppeteer 介绍

Puppeteer 是什么

Puppeteer 是一个 NodeJs 库,它提供了一个高级 API 来通过 DevTools 协议控制 Chromium 或 Chrome。相比较 Selenium 或是 PhantomJs,它最大的特点就是完全可以在内存中模拟 DOM 操作,即在 V8 引擎中处理而不打开浏览器,而且关键的是该项目是 Chrome 团队在维护,会拥有更好的兼容性和前景,更多资料可参考以下站点:Puppeteer GithubPuppeteer 中文文档DevTools Protocol 文档Chromium 命令行启动参数

Puppeteer 的功能

  • 生成页面的截图和 PDF
  • 自动提交表单,进行 UI 测试,键盘输入等
  • 捕获网站的时间线跟踪,用来帮助分析性能问题
  • 抓取 SPA(单页应用),并生成预渲染内容,即 “SSR”(服务器端渲染)
  • 创建一个最新的自动化测试环境,使用最新的 JavaScript 和浏览器功能,直接在最新版本的 Chrome 中运行测试
  • 测试浏览器扩展,Chrome / Chromium 扩展当前只能在非无头模式下使用,目前还无法测试扩展弹出窗口或内容脚本

Puppeteer VS Puppeteer-Core

使用区别

自 v1.7.0 以来的 Puppeteer 每个版本都会发布两个包:puppeteer、puppeteer-core,两者的区别如下:

  • puppeteer 是浏览器自动化的产品,安装后它会下载一个最新版本的 Chromium,然后使用 puppeteer-core 驱动工作。作为最终用户产品,puppeteer 支持一堆方便的 PUPPETEER_* 环境变量来调整运行行为
  • puppeteer-core 是一个库来帮助驱动任何支持 DevTools 协议的东西。puppeteer-core 在安装时不会下载 Chromium,作为一个库,puppeteer-core 完全是通过其编程接口驱动的,并且会忽略所有 PUPPETEER_* 环境变量

使用建议

在大多数情况下,可以使用 puppeteer 包,如果是下面这些情况,那可以使用 puppeteer-core:

  • 正在构建使用 DevTools 协议的另一个最终用户产品或库;例如,可以使用 puppeteer-core 构建 PDF 生成器,并编写下载 headless_shell 的自定义 install.js 脚本,而不是使用 Chromium 来节省磁盘空间
  • 正在打包 Puppeteer 用在 Chrome 上的扩展应用或者浏览器中以使用 DevTools 协议,因为下载额外的 Chromium 二进制文件不是必须的

当需要使用 puppeteer-core 时,使用下面这行代码代替原来的引入方式即可:

1
const puppeteer = require('puppeteer-core');

Puppeteer 运行环境与安装

Puppeteer 运行环境

Puppeteer 运行依赖于 NodeJs v6.4.0+,如果要使用 async /await,只有 NodeJs v7.6.0 或更高版本才支持,NodeJs 可以点击这里下载。

Puppeteer 安装

Puppeteer 安装的过程默认会执行 install.js 脚本来下载最新版本的 Chromium(请自备梯子),可以使用 --ignore-scripts 参数跳过 Chromium 的下载,也可以通过设置环境变量 PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1 来跳过下载。

1
2
3
4
5
# 安装puppeteer
$ npm install puppeteer -g --ignore-scripts

# 或者使用淘宝镜像源安装puppeteer
$ npm install puppeteer -g --ignore-scripts --registry=https://registry.npm.taobao.org

手动下载 Chromium 并解压在本地磁盘,下载可以点击这里(请自备梯子):

Puppeteer 入门案例

入门案例

初始化项目:

1
$ npm init

创建 index.js 文件,代码如下,executablePath 是 Chromium 或者 Chrome 可执行文件的路径:

1
2
3
4
5
6
7
8
9
10
11
12
const puppeteer = require('puppeteer');

(async () => {
const browser = await puppeteer.launch({
executablePath: '/usr/bin/google-chrome-stable',
headless: false
});
const page = await browser.newPage();
await page.goto('http://www.baidu.com/');
await page.screenshot({path: 'baidu.png'});
browser.close();
})();

执行 index.js 脚本,运行成功后,会在当前目录下生成网页的截图文件 baidu.png

1
$ node index.js

启动参数

Puppeteer Launch 的启动参数如下:

  • executablePath:Chromium 或 Chrome 可执行文件的路径
  • headless:是否运行在浏览器 headless 模式,true 表示不打开浏览器执行,默认为 true
  • timeout:等待浏览器实例启动的最长时间(以毫秒为单位),默认为 30000,当值为 0 时表示禁用超时
  • args:传递给浏览器实例的其他参数

Puppeteer 实战

Puppeteer 环境变量

Puppeteer 的环境变量如下,在使用 puppeteer-core 时,下述环境变量中以 PUPPETEER_* 开头的会被忽略:

  • HTTP_PROXY、HTTPS_PROXY, NO_PROXY - 定义用于下载和运行 Chromium 的 HTTP 代理设置
  • PUPPETEER_SKIP_CHROMIUM_DOWNLOAD - 请勿在安装步骤中下载绑定的 Chromium
  • PUPPETEER_DOWNLOAD_HOST - 覆盖用于下载 Chromium 的 URL 的主机部分
  • PUPPETEER_CHROMIUM_REVISION - 在安装步骤中指定一个 puppeteer 使用的特定版本的 Chromium
  • PUPPETEER_EXECUTABLE_PATH - 指定一个 Chrome 或者 Chromium 可执行文件的路径,会被用于 puppeteer.launch

Puppeteer 的选择器

Puppeteer 中获取元素的方法和浏览器里面的一样,但是获取元素的属性的办法和浏览器不一样,它有一套 API 用来获取界面中的元素,还有一套 API 用来获取元素的属性。

获取元素的操作如下:

1
2
3
4
5
// Page.$(selector) 获取单个元素,底层是调用的是 document.querySelector(),所以选择器的 selector 格式遵循 CSS 选择器规范
let inputElement = await page.$('#search');

// Page.$$(selector) 获取一组元素,底层调用的是 document.querySelectorAll(),返回 Promise(Array(ElemetHandle)) 元素数组
const links = await page.$$("a");

获取元素的属性的操作如下:

1
2
3
4
5
6
7
// Puppeteer 获取元素属性跟平时写 JavaScript 的逻辑有点不一样,按照通常的逻辑,应该是现获取元素,然后再获取元素的属性
// Puppeteer 获取元素的 API 最终返回的都是 ElemetHandle 对象,而 ElemetHandle 并没有提供获取元素属性的 API,而 Puppeteer 专门提供了一套获取元素属性的 API,分别是: Page.$eval() 和 Page.$$eval()

const href = await page.$eval('#a', ele => ele.href);
const content = await page.$eval('.content', ele => ele.outerHTML);
const value = await page.$eval('input[name=search]', input => input.value);
const textArray = await page.$$eval('#dom', els => Array.from(els).map(el => el.textContent));

常用的元素选择器:

选择器示例示例说明
id 选择器#id 选择匹配 id 的元素,仅存在一个
class 选择器.class 同时匹配多个 class 元素
属性选择器 div[attr] 匹配具有 attr 的属性,不考虑具体的值
属性选择器 div[attr=‘122‘] 匹配具有 attr 的属性,值为 122
后代选择器 div span 后代选择器,匹配所有 div 后面的 span 标签,div 与 span 之间用空格隔开
子元素选择器 div > span 子元素选择器,匹配 div 后所有的 span
匹配父元素下的第 n 个子元素 div:nth-child(2) 匹配父元素下的第 2 个元素

SegmentFault 模拟登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const puppeteer = require('puppeteer');

(async () => {
const browser = await puppeteer.launch({
executablePath: '/usr/bin/google-chrome-stable',
headless: false
});
const page = await browser.newPage();
page.setJavaScriptEnabled(true);
page.setCacheEnabled(true);

await page.goto("https://segmentfault.com/user/login", {
"timeout" : 30000
});

// 选择登录方式
await page.tap(".login-nav > a[data-mode='password']");
// 输入用户名
await page.type("form[class='password-form'] > div > input[name='username']", 'admin', {delay:100});
// 输入密码
await page.type("form[class='password-form'] > div > input[name='password']", '123456', {delay:100});
// 点击登录按钮
await page.tap("form[class='password-form'] > button[type='submit']");

// await page.close();
// await browser.close();
})();

Puppeteer 结合 Jest 使用

Puppeteer 周边的开源项目

  • jvppeteer,Java 版的 Puppeteer
  • pyppeteer,Python 版的 Puppeteer
  • awesome-puppeteer,Puppeteer 相关的开源项目整理
  • docker-puppeteer,A minimal Docker image for Puppeteer
  • puppeteer-cluster,Puppeteer Pool, run a cluster of instances in parallel
  • puppeteer-deep,爬取《es6 标准入门》、自动推文到掘金、站点性能分析;高级爬虫、自动化 UI 测试、性能分析的实践案例
  • puppeteer-recorder,Puppeteer recorder is a Chrome extension that records your browser interactions and generates a Puppeteer script