Skip to content

puppeteer

简介

Puppeteer 是一个 JavaScript 库,其功能如下

  • 模拟用户事件,执行自动化测试
  • 生成页面的屏幕截图和 PDF。
  • 抓取 SPA(单页应用)并生成预渲染内容

核心概念

浏览器管理

js
import puppeteer from 'puppeteer';
// 启动浏览器
const browser = await puppeteer.launch();
// 新建页面
const page = await browser.newPage();
// 关闭浏览器
await browser.close();

页面交互

定位器

定位器在单击前自动检查以下内容:

  • 确保元素位于视口中。
  • 等待元素变为 visible 或隐藏。
  • 等待元素启用。
  • 等待元素在两个连续的动画帧上具有稳定的边界框。
js
// 'button' is a CSS selector.
//使用定位器单击元素
await page.locator('button').click();
// 自动检测输入类型并选择合适的方式用提供的值填写。例如,它将填充 <select> 元素以及 <input> 元素。
await page.locator('input').fill('value');
// 将鼠标悬停在元素上
await page.locator('div').hover();
// [.scroll()] 函数使用鼠标滚轮事件来滚动元素。
await page.locator('div').scroll({
  scrollLeft: 10,
  scrollTop: 20,
});
// 有时你只需要等待元素可见。
await page.locator('.loading').wait();

waitForSelector

与定位器相比,waitForSelector 是一种更底层的 API,允许等待元素在 DOM 中可用。如果操作失败,它不会自动重试该操作,并且需要手动处理生成的 ElementHandle 以防止内存泄漏。该方法存在于 Page、Frame 和 ElementHandle 实例上。

js
// Import puppeteer
import puppeteer from 'puppeteer';

// Launch the browser.
const browser = await puppeteer.launch();

// Create a page.
const page = await browser.newPage();

// Go to your site.
await page.goto('YOUR_SITE');

// Query for an element handle.
const element = await page.waitForSelector('div > .class-name');

// Do something with element...
await element.click(); // Just an example.

// Dispose of handle.
await element.dispose();

// Close browser.
await browser.close();

出于向后兼容的原因,某些页面级 API(例如 page.click(selector)page.type(selector)page.hover(selector))是使用 waitForSelector 实现的。

无需等待即可查询

有时你知道元素已在页面上。在这种情况下,Puppeteer 提供了多种方法来查找与选择器匹配的元素或多个元素。这些方法存在于 Page、Frame 和 ElementHandle 实例中。

  • page.$() 返回与选择器匹配的单个元素。
  • page.$$() 返回与选择器匹配的所有元素。
  • page.$eval() 返回对与选择器匹配的第一个元素运行 JavaScript 函数的结果。
  • page.$$eval() 返回对与选择器匹配的每个元素运行 JavaScript 函数的结果。

JavaScript 执行

Puppeteer 允许在 Puppeteer 驱动的页面上下文中评估 JavaScript 函数:

js
// Import puppeteer
import puppeteer from 'puppeteer';

(async () => {
  // Launch the browser
  const browser = await puppeteer.launch();

  // Create a page
  const page = await browser.newPage();

  // Go to your site
  await page.goto('YOUR_SITE');

  // Evaluate JavaScript
  const three = await page.evaluate(() => {
    return 1 + 2;
  });

  console.log(three);
  // 如果你从评估调用中返回 Promise,则将自动等待该 promise。例如
	await page.evaluate(() => {
    // wait for 100ms.
    return new Promise(resolve => setTimeout(resolve, 100));
  });
 
  // Close browser.
  await browser.close();
})();

尽管该函数是在脚本上下文中定义的,但它实际上会被 Puppeteer 转换为字符串,发送到目标页面并在那里进行评估。这意味着该函数无法访问范围变量或调用 Puppeteer 脚本中定义的其他函数,并且你需要在函数体内定义整个函数逻辑。

网络日志记录

默认情况下,Puppeteer 监听所有网络请求和响应,并在页面上触发网络事件。

js
const page = await browser.newPage();
page.on('request', request => {
  console.log(request.url());
});

page.on('response', response => {
  console.log(response.url());
});

调试

  1. 关闭 headless

  2. 捕获 console.* 输出

    js
    page.on('console', msg => console.log('PAGE LOG:', msg.text()));
    
    await page.evaluate(() => console.log(`url is ${location.href}`));
  3. 使用浏览器中的调试器

    启动 Puppeteer 时将 devtools 设置为 true

    const browser = await puppeteer.launch({devtools: true});

    在你想要调试的任何客户端代码中添加 debugger。例如,

    ts
    await page.evaluate(() => {
      debugger;
    });
  4. node调试

预渲染插件设计原理

流程图