9

puppeteer

Puppeteer 是什么

Puppeteer 是一个 Node library,提供了一套完整的通过 DevTools 协议操纵 Chrome 或 Chromium 的 API。Puppeteer 默认以 无头(headless) 的方式运行, 也可以使用 GUI 的方式运行 Chrome 和 Chromium。

熟悉爬虫或者 UI 自动化的同学可能会联想到 PhantomJSCasperJS 或者 Selenium,而作为 Chrome DevTools 团队亲自出品和维护的 puppeteer 不管是在功能的完整性、稳定性、兼容性、安全性还是性能都将成为碾压其他工具的存在。

Puppeteer 的作用

理论上我们在 Chrome 里能做的事情,通过 puppeteer 都能够做到。比如:

  • 对页面和元素截图
  • 把页面保存为 PDF
  • 爬取 SPA(Single-Page Application)网站的内容并为 SSR(Server-Side Rendering)网站生成 pre-render 的内容
  • UI 自动化测试、自动填充/提交表单、模拟 UI 输入
  • 测试最新的 Javascript 和 Chrome 功能
  • 性能测试,生成 timeline trace 用于定位网站性能问题
  • 测试 Chrome 的插件

当然,puppeteer 也不是全能的,比如在跨浏览器兼容方面就有所欠缺,目前只对 Firefox 做了实验性的支持,所以要对网站做浏览器兼容性测试还是得选择 Selenium/WebDriver 之类的工具,puppeteer 更多的是专注于和 Chromium 的互通,以提供更丰富更可靠的功能。

安装 Puppeteer

npm i puppeteer

yarn add puppeteer
安装 puppeteer 的过程中会下载最新版本的 Chromiun (~170MB Mac, ~282MB Linux, ~280MB Win),以确保最新版的 puppeteer 和 Chromium 完全兼容. 我们也可以跳过 Chromium 的下载,或者下载其他版本的 Chromium 到特定路径,这些都可以通过环境变量进行配置,具体参考 Environment variables.

puppeteer-core

puppeteer-core 是 puppeteer 的一个轻量版本,不会默认下载 Chromium,而是需要选择使用本地或远程的 Chrome。

npm i puppeteer-core

yarn add puppeteer-core

使用 puppeteer-core 需要确保它的版本和连接的 Chrome 版本可以兼容。

puppeteer-core 会忽略所有的 PUPPETEER\_* 环境变量

关于 puppeteer 和 puppeteer-core 的详细对比请参考:puppeteer vs puppeteer-core

用法举例

示例 1 - 访问 https://example.com 并对网页截图

创建 screenshot.js

const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto("https://example.com");
  await page.screenshot({ path: "example.png" });

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

执行 screenshot.js

node screenshot.js

生成图片预览:

screenshot

Puppeteer 初始的窗口尺寸为 800x600px, 这也决定了对页面的截图的尺寸为 800x600px。我们可以使用 Page.setViewport() 对窗口尺寸进行设置,比如设置成 1080P 的:

page.setViewport({
  width: 1920,
  height: 1080,
});

如果想要对真个网页进行滚动截图,可以使用:

await page.screenshot({ fullPage: true });
示例 2 - 访问 https://github.com/puppeteer/... 并将网页保存为 PDF 文件。

创建 savePDF.js

const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  page.setViewport({
    width: 1920,
    height: 1080,
  });
  await page.goto("https://github.com/puppeteer/puppeteer", {
    waitUntil: "networkidle2",
  });
  await page.pdf({
    path: "puppeteer.pdf",
    format: "a2",
  });

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

执行 savePDF.js

node savePDF.js

生成的 PDF 预览:

savePDF

生成 PDF 的更多选项请参考:Page.pdf()

示例 3 - 在浏览器的上下文中执行 JS 代码

创建 get-dimensions.js

const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto("https://example.com");

  // Get the "viewport" of the page, as reported by the page.
  const dimensions = await page.evaluate(() => {
    return {
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight,
      deviceScaleFactor: window.devicePixelRatio,
    };
  });

  console.log("Dimensions:", dimensions);

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

执行 get-dimensions.js

node get-dimensions.js

执行结果:

evaluate

更多 evaluate 的用法请参考 Page.evaluate()

示例 4 - 自动填充表单并提交(在 https://developers.google.com 页面搜索框中输入关键词 Headless Chrome 并搜索)

创建 search.js

const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch({
    headless: false, // GUI模式
  });
  const page = await browser.newPage();
  await page.goto("https://developers.google.com/web/");
  // 在搜索框中输入关键词
  await page.type(".devsite-search-field", "Headless Chrome");
  // 按Enter键
  await page.keyboard.press("Enter");
  // 等待结果返回
  const resultsSelector = ".gsc-result .gs-title";
  await page.waitForSelector(resultsSelector);
  // 从页面中爬取结果
  const links = await page.evaluate((resultsSelector) => {
    const anchors = Array.from(document.querySelectorAll(resultsSelector));
    return anchors.map((anchor) => {
      const title = anchor.textContent.split("|")[0].trim();
      return `${title} - ${anchor.href}`;
    });
  }, resultsSelector);
  // 打印结果
  console.log(links.join("\n"));

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

执行 search.js

node search.js

结果展示:

search

Debugging 技巧

Puppeteer 在 debugging 层面非常强大,下面罗列了一些常用的技巧。

1. 关闭“无头”模式 - 看到浏览器的显示内容对调试很有帮助

const browser = await puppeteer.launch({ headless: false });

2. 打开“慢动作”模式 - 进一步看清浏览器的运行

const browser = await puppeteer.launch({
  headless: false,
  slowMo: 250, // 将puppeteer的操作减慢250ms
});

3. 监听浏览器控制台中的输出

page.on("console", (msg) => console.log("PAGE LOG:", msg.text()));

await page.evaluate(() => console.log(`url is ${location.href}`));

4. 在浏览器执行代码中使用 debugger

目前有两种执行上下文:运行测试代码的 node.js 上下文和运行被测试代码的浏览器上下文,我们可以使用 page.evaluate() 在浏览器上下文中插入 debugger 进行调试:

  • 首先在启动 puppeteer 的时候设置 {devtools: true}

    const browser = await puppeteer.launch({ devtools: true });
  • 然后在 evaluate() 的执行代码中插入 debugger,这样 Chromium 在执行到这一步的时候会停止:

    await page.evaluate(() => {
      debugger;
    });

5. 启用详细日志记录(verbose loggin) - 内部 DevTools 协议流量将通过 puppeteer 命名空间下的debug 模块记录

基本用法:

DEBUG=puppeteer:* node screenshot.js

Windows 下面可以使用cross-env

npx cross-env DEBUG=puppeteer:* node screenshot.js

协议流量可能相当复杂,我们可以过滤掉所有网络域消息

env DEBUG=puppeteer:\* env DEBUG_COLORS=true node ./examples/screenshot.js 2>&1 | grep -v '"Network'

6. 使用 ndb 工具进行调试,具体用法请参考 ndb

资源链接

  1. Puppeteer 官网
  2. API 文档
  3. 使用示例
  4. Github - Awesome Puppeteer
  5. Troubleshooting

本文Demo链接:https://github.com/MudOnTire/...


CodeSteppe
7.1k 声望4.1k 粉丝