Puppeteer 简介

Puppeteer(中文翻译“木偶”),是一个 Node 库,它提供了一个高级 API 来通过 DevTools) 协议控制 Chromium 或 Chrome。Puppeteer 默认以 headless 模式运行,但是可以通过修改配置文件运行“有头”模式。

高级 API

开发者工具能让我们轻松调试目标页面,这其实都是 Chrome DevTools Protocol(基于 WebSocket 协议) 的能力,同样我们可以直接操作协议来控制页面,但是代码难度很高。而 Puppeteer 则进行了更上层的封装,通过暴露的 API 同样可以控制 ChromiumChrome,但代码难度大大降低。

DevTools 协议

开发工具协议,Chrome DevTools 协议允许使用工具来检测,检查,调试和分析 ChromiumChrome 和其他基于 Blink 的浏览器。当前,许多现有项目都使用该协议。 Chrome DevTools 使用此协议,并且团队维护其 API

仪器划分为多个域(DOM,调试器,网络等)。每个域定义了它支持的许多命令以及它生成的事件。命令和事件都是固定结构的序列化 JSON 对象。

Chromium & Chrome

Chromium 是由 Google 发起的一个开源项目(同样它也是一个浏览器),Chromium 的诞生主要是为了发展 Google Chrome 浏览器,新功能都会率先在 Chromium 上试验,验证后才会应用到 Chrome 中。由于 Chrome 是闭源的,所以国内的很多浏览器都是基于 Chromium 开源代码。

headless 模式

“无头”模式,你可以简单的理解为没有界面的模式,就是在终端跑脚本。当然你也可以通过修改文件配置运行“有头”模式。

puppeteer-core

puppeteer 是浏览器自动化的产品。在安装的同时,还是下载 Chromium,并且使用 puppeteer 驱动工作。

puppeteer-core 则是一个库,用来帮助驱动任何支持 DevTools 协议的程序或者应用。puppeteer-core 在安装是不会下载 Chromium

总结:一般情况下直接安装 puppeteer 使用,但当你正在构建 DevTools 协议顶部的另一个产品或库,那通过使用 puppeteer-core 避免下载 Chromium 来节省磁盘空间则是正确的方案。

Puppeteer 综述

puppeteer overview.png

  1. Puppeteer 使用 DevTools 协议与浏览器进行通信。
  2. Browser 实例拥有浏览器上下文。
  3. BrowserContext 实例定义了一个浏览会话并且可拥有多个页面。
  4. Page 至少有一个主框架,可能还有其它框架由 iframe 或者框架标签创建。
  5. Frame 至少有一个执行上下文。一个框架可能有额外的与扩展关联的执行上下文。
  6. Worker 具有单一执行上下文,便于与WebWorks进行交互。

注意:在下面的图表中,浅色框体内容目前不在 Puppeteer 中体现。

Puppeteer 能做什么?

  • 生成页面 PDF
  • 抓取 SPA(单页应用)并生成预渲染内容(即“SSR”(服务器端渲染))。
  • 自动提交表单,进行 UI 测试,键盘输入等。
  • 创建一个时时更新的自动化测试环境。 使用最新的 JavaScript 和浏览器功能直接在最新版本的 Chrome 中执行测试。
  • 捕获网站的 timeline trace,用来帮助分析性能问题。
  • 测试浏览器扩展。

Puppeteer 安装与环境搭建

由于墙的原因,通过官方提供的安装指南可能无法顺利的完成安装,这边提供一个兜底的解决方案。

  1. 使用官方提供的环境变量来安装,忽略 Chromium 的下载。

    1. env PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true npm i puppeteer -D
  2. 手动下载 Chromium 文件,导引地址 https://npm.taobao.org/mirrors/chromium-browser-snapshots/,根据自己的系统以及指定的 Chromium 版本进行下载,并且解压。

image.png
Chromium 需要选择的版本,可以到项目目录中的 node_moudules/puppeteer/package.json 文件里查看。

  1. 将解压好的 chrome—mac 文件放到自己的项目目录中,通过给 launch API 设置 executablePath 参数进行使用即可。 ```javascript const puppeteer = require(‘puppeteer’) const path = require(‘path’) const executablePath = path.join(__dirname, ‘../chrome-mac/Chromium.app/Contents/MacOS/Chromium’)

;(async () => { const browser = await puppeteer.launch({ executablePath, }) })()

  1. <a name="zDvFA"></a>
  2. ## 基础 Demo
  3. ```javascript
  4. const puppeteer = require('puppeteer')
  5. ;(async () => {
  6. const browser = await puppeteer.launch()
  7. const page = await browser.newPage()
  8. await page.goto('https://www.baidu.com')
  9. await browser.close()
  10. })()

解读上面代码块的含义:

  1. 通过 puppeteer.launch 创建一个浏览器实例 browser
  2. 通过 browser 对象的 newPage 方法创建一个页面对象 page
  3. 调用 page 对象的 goto 方法跳转到指定页面。
  4. 最后调用 browser 对象的 close 方法关闭浏览器。

基础 API

  • puppeteer.launch(options):用于创建一个 brower 实例
配置项 描述 默认值
headless 是否以 无头模式 运行浏览器 true
executablePath 可运行 Chromium 或 Chrome 可执行文件的路径,而不是绑定的 Chromium ‘’
slowMo 将 Puppeteer 操作减少指定的毫秒数。这样你就可以看清发生了什么。 ‘’
timeout 等待浏览器实例启动的最长时间(以毫秒为单位) 默认是 30000 (30 秒)。通过 0 来禁用超时。
devtools 是否为每个选项卡自动打开DevTools面板 默认是 false。如果这个选项是 trueheadless 选项将会设置成 false
  • browser.newPage():返回一个新的 Page 对象
  • browser.close():关闭 Chromium 及其所有页面
  • page.goto(url[, options]):页面导航到指定的地址
  • page.pdf([options]):将页面保存成 pdf(目前仅支持无头模式的 Chrome
  • page.screenshot([options]):获取页面截图
  • page.$(selector):获取页面指定元素。此方法在页面内执行 document.querySelector。如果没有元素匹配指定选择器,返回值是 null
  • page.close([options]):关闭当前页面

进阶 Demo

ES6 入门教程内容爬取保存成 PDF

利用了 Puppeteer 的获取浏览器网页 DOM 内容以及 page.pdf 能力。

  1. const puppeteer = require('puppeteer')
  2. const { timeout, delDir } = require('../utils/tools')
  3. const path = require('path')
  4. const executablePath = require('../utils/getExecutablePath')
  5. ;(async () => {
  6. delDir('../pdf/es6-pdf')
  7. console.time('time')
  8. // headless: false does not support PDFs
  9. // https://github.com/puppeteer/puppeteer/issues/830
  10. const browser = await puppeteer.launch({
  11. executablePath,
  12. })
  13. let page = await browser.newPage()
  14. // Configure the navigation timeout
  15. await page.setDefaultNavigationTimeout(100000)
  16. await page.goto('https://es6.ruanyifeng.com/#README')
  17. // 获取「目录」 => 返回「标题 + 链接」
  18. let catalogTags = await page.evaluate(() => {
  19. let allCatalog = [...document.querySelectorAll('#sidebar ol li a')]
  20. return allCatalog.map((catalog) => {
  21. return {
  22. href: catalog.href.trim(),
  23. name: catalog.text,
  24. }
  25. })
  26. })
  27. for (let i = 0; i < catalogTags.length; i++) {
  28. page = await browser.newPage()
  29. console.log('name: ', catalogTags[i])
  30. await page.goto(catalogTags[i].href, { waitUntil: 'networkidle2' })
  31. await page.pdf({
  32. path: path.join(__dirname, `../data/pdf/es6-pdf/${catalogTags[i].name}.pdf`),
  33. format: 'A4',
  34. })
  35. await page.close()
  36. }
  37. console.timeEnd('time')
  38. browser.close()
  39. })()

自动登录

在做一些自动化测试的时候,我们需要时常需要先登录应用,Puppeteer 为我们提供了相应的 API。

  1. // https://www.yuque.com/allenzhoujiawei/uia384
  2. const puppeteer = require('puppeteer')
  3. const executablePath = require('../utils/getExecutablePath')
  4. ;(async () => {
  5. console.time('time')
  6. const browser = await puppeteer.launch({
  7. executablePath,
  8. })
  9. const page = await browser.newPage()
  10. await page.goto('https://www.yuque.com/allenzhoujiawei/uia384')
  11. await page.type('input[type="tel"]', '****** your account ******')
  12. await page.type('input[type="password"]', '****** your secret ******')
  13. await page.click('.btn-login')
  14. console.timeEnd('time')
  15. })()

获取 QQ 音乐前 50 位摇滚歌手

  1. const puppeteer = require('puppeteer-cn')
  2. const { delDir } = require('../utils/tools')
  3. const fs = require('fs')
  4. const path = require('path')
  5. ;(async () => {
  6. console.time('time')
  7. delDir('../data/qq-rock-music/top50-player-profile')
  8. const browser = await puppeteer.launch({
  9. headless: true
  10. })
  11. const page = await browser.newPage()
  12. await page.goto('https://y.qq.com/portal/singer_list.html#page=1&genre=2&')
  13. await page.waitFor(2000)
  14. let rockPlayers = await page.evaluate(() => {
  15. let allRockPlayers
  16. let part_with_avatar = [
  17. ...document.querySelectorAll('.js_avtar_list .singer_list__item .singer_list__title a')
  18. ]
  19. let part_only_txt = [...document.querySelectorAll('.singer_list_txt .singer_list_txt__item a')]
  20. allRockPlayers = part_with_avatar.concat(part_only_txt).splice(0, 50)
  21. return allRockPlayers.map(rockPlayer => {
  22. return {
  23. title: rockPlayer.title.trim().replace(/\//g, ''),
  24. href: rockPlayer.href
  25. }
  26. })
  27. })
  28. // console.log(rockPlayers)
  29. for (let i = 0; i < rockPlayers.length; i++) {
  30. console.log(rockPlayers[i].title)
  31. await page.goto(rockPlayers[i].href)
  32. let playerDescTxt = await page.evaluate(() => {
  33. return document.querySelector('.data__cont .data__desc_txt').textContent
  34. })
  35. // console.log(playerDescTxt)
  36. // 文件流形式写入文件
  37. let writeStream
  38. try {
  39. writeStream = fs.createWriteStream(
  40. path.join(
  41. __dirname,
  42. `../data/qq-rock-music/top50-player-profile/${rockPlayers[i].title}-个人简介.txt`
  43. )
  44. )
  45. writeStream.write(playerDescTxt, 'UTF8')
  46. writeStream.end()
  47. } catch (e) {
  48. console.log(e)
  49. }
  50. }
  51. await page.close()
  52. await browser.close()
  53. console.timeEnd('time')
  54. })()

获取指定网页的性能分析

  1. const puppeteer = require('puppeteer-cn')
  2. /**
  3. * 网页性能分析
  4. * API:page.tracing.start
  5. * API:page.tracing.stop
  6. */
  7. ;(async () => {
  8. console.time('time')
  9. const browser = await puppeteer.launch({
  10. headless: false
  11. })
  12. const page = await browser.newPage()
  13. await page.tracing.start({
  14. path: '../data/trace/trace.json'
  15. })
  16. await page.goto('https://zjiawei.cn/')
  17. await page.tracing.stop()
  18. await page.close()
  19. await browser.close()
  20. console.timeEnd('time')
  21. })()

Puppeteer 畅想

京喜前端自动化测试之路
使用 Puppeteer 搭建统一海报渲染服务
服务端预渲染在可视化搭建业务的实践

文档与参考

Demo 案例仓库:https://github.com/AllenChinese/Puppeteer-tools
Github:https://github.com/puppeteer/puppeteer#faq
英文文档:https://pptr.dev/
中文文档:https://zhaoqize.github.io/puppeteer-api-zh_CN/
Google Chrome:https://developers.google.com/
DevTools Protocol:https://www.wangshaoxing.com/blog/2017-08-24-chrome-devtools-protocol.html