Puppeteer 中文文档v25.8.0

页面交互

Puppeteer 允许通过鼠标、触摸事件和键盘输入与页面上的元素交互。通常,你首先使用 CSS 选择器 查询一个 DOM 元素,然后对选中的元素执行操作。所有接受选择器的 Puppeteer API 默认都接受 CSS 选择器。此外,Puppeteer 还提供 自定义选择器语法,允许使用 XPath、文本、无障碍属性来查找元素,并访问 Shadow DOM,而无需执行 JavaScript。

如果你想在不先选择元素的情况下触发鼠标或键盘事件,可以使用 page.mousepage.keyboardpage.touchscreen API。本指南的其余部分概述了如何选择 DOM 元素并对其执行操作。

定位器

定位器是选择元素并与之交互的推荐方式。定位器封装了如何选择元素的信息,并允许 Puppeteer 自动等待元素出现在 DOM 中且处于适合执行操作的状态。你始终使用 page.locator()frame.locator() 函数来实例化定位器。如果定位器 API 没有提供你需要的功能,你仍然可以使用更底层的 API,例如 page.waitForSelector()ElementHandle

使用定位器点击元素

// 'button' is a CSS selector.
await page.locator('button').click();

在点击之前,定位器会自动检查以下内容:

  • 确保元素在视口内。
  • 等待元素变为 可见 或隐藏。
  • 等待元素变为可用。
  • 等待元素在两个连续的动画帧内具有稳定的边界框。

填充输入框

// 'input' is a CSS selector.
await page.locator('input').fill('value');

自动检测输入类型,并选择合适的方式用提供的值进行填充。例如,它既能填充 <select> 元素,也能填充 <input> 元素。

在向输入框输入之前,定位器会自动检查以下内容:

  • 确保元素在视口内。
  • 等待元素变为 可见 或隐藏。
  • 等待元素变为可用。
  • 等待元素在两个连续的动画帧内具有稳定的边界框。

悬停在元素上

await page.locator('div').hover();

在悬停之前,定位器会自动检查以下内容:

  • 确保元素在视口内。
  • 等待元素变为 可见 或隐藏。
  • 等待元素在两个连续的动画帧内具有稳定的边界框。

滚动元素

[.scroll()] 函数使用鼠标滚轮事件来滚动元素。

// Scroll the div element by 10px horizontally
// and by 20 px vertically.
await page.locator('div').scroll({
  scrollLeft: 10,
  scrollTop: 20,
});

在滚动之前,定位器会自动检查以下内容:

  • 确保元素在视口内。
  • 等待元素变为 可见 或隐藏。
  • 等待元素在两个连续的动画帧内具有稳定的边界框。

等待元素可见

有时你只需要等待元素变为可见。

// '.loading' is a CSS selector.
await page.locator('.loading').wait();

在返回之前,定位器会自动检查以下内容:

  • 等待元素变为 可见 或隐藏。

等待一个函数

有时,等待以 JavaScript 函数形式表示的任意条件会很有用。在这种情况下,可以使用函数而不是选择器来定义定位器。下面的示例会一直等待,直到页面上至少出现 3 个段落,然后提取它们的文本。你也可以调用诸如 .click().fill() 之类的定位器函数,而不是将元素映射为文本。

const paragraphs = await page
  .locator(() => {
    const paragraphs = document.querySelectorAll('p');

    if (paragraphs.length >= 3) {
      return [...paragraphs].map(p => p.textContent);
    }
  })
  .wait();

在定位器上应用过滤器

下面的示例展示了如何以 JavaScript 函数的形式向定位器添加额外条件。只有当按钮元素的 textContent 是 'My button' 时,它才会被点击。

await page
  .locator('button')
  .filter(button => button.textContent === 'My button')
  .click();

由于 .filter() 的回调在浏览器上下文中执行,它无法访问 Node 作用域中的变量。你可以构造一个字符串函数来注入变量:

const buttonName = 'My button';
await page
  .locator('button')
  .filter(`button => button.textContent === ${JSON.stringify(buttonName)}`)
  .click();

从定位器返回值

map 函数允许将元素映射为 JavaScript 值。在这种情况下,调用 wait() 将返回反序列化后的 JavaScript 值。

const enabled = await page
  .locator('button')
  .map(button => !button.disabled)
  .wait();

从定位器返回 ElementHandle

waitHandle 函数允许返回 ElementHandle。如果你需要的操作没有对应的定位器 API,这可能会很有用。

const buttonHandle = await page.locator('button').waitHandle();
await buttonHandle.click();

配置定位器

可以对定位器进行配置,以调整前置条件和其它选项:

// Clicks on a button without waiting for any preconditions.
await page
  .locator('button')
  .setEnsureElementIsInTheViewport(false)
  .setVisibility(null)
  .setWaitForEnabled(false)
  .setWaitForStableBoundingBox(false)
  .click();

定位器超时

默认情况下,定位器继承页面的超时设置。但也可以在单个定位器的基础上设置超时。如果在指定的时间段内找不到元素或前置条件未满足,将会抛出 TimeoutError

// Time out after 3 sec.
await page.locator('button').setTimeout(3000).click();

获取定位器事件

目前,定位器支持 单个事件,当定位器即将执行操作、表明前置条件已满足时,该事件会通知你:

let willClick = false;
await page
  .locator('button')
  .on(LocatorEvent.Action, () => {
    willClick = true;
  })
  .click();

该事件可用于日志记录/调试或其它用途。如果定位器重试操作,该事件可能会触发多次。

waitForSelector

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

// 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 函数的结果。

选择器

Puppeteer 在每一个接受选择器的 API 中都接受 CSS 选择器。此外,你可以选择使用额外的选择器语法,做 CSS 选择器做不到的事情。

非 CSS 选择器

Puppeteer 使用自定义 伪元素 扩展了 CSS 语法,这些伪元素定义了如何使用非 CSS 选择器来选择元素。Puppeteer 支持的伪元素以 -p 厂商前缀开头。

XPath 选择器(-p-xpath

XPath 选择器将使用浏览器的原生 Document.evaluate 来查询元素。

// Runs the `//h2` as the XPath expression.
const element = await page.waitForSelector('::-p-xpath(//h2)');

文本选择器(-p-text

文本选择器会选择包含给定文本的“最小”元素,即使在(开放的)shadow root 中也是如此。这里的“最小”指的是包含给定文本的最深层元素,而不是它们的父元素(从技术上讲,父元素也包含给定文本)。

// Click a button inside a div element that has Checkout as the inner text.
await page.locator('div ::-p-text(Checkout)').click();
// You need to escape CSS selector syntax such '(', ')' if it is part of the your search text ('Checkout (2 items)').
await page.locator(':scope >>> ::-p-text(Checkout \\(2 items\\))').click();
// or use quotes escaping any quotes that are part of the search text ('He said: "Hello"').
await page.locator(':scope >>> ::-p-text("He said: \\"Hello\\"")').click();

ARIA 选择器(-p-aria

ARIA 选择器可用于通过计算出的可访问名称和角色来查找元素。这些标签是使用浏览器内部对无障碍树的表示计算出来的。这意味着在查询运行之前,诸如 labeledby 之类的 ARIA 关系已被解析。如果你不想依赖任何特定的 DOM 结构或 DOM 属性,ARIA 选择器会很有用。

await page.locator('::-p-aria(Submit)').click();
await page.locator('::-p-aria([name="Click me"][role="button"])').click();

Pierce 选择器(pierce/

Pierce 选择器是一种返回文档中所有 shadow root 内匹配给定 CSS 选择器的所有元素的选择器。我们建议改用 深度组合器,因为它们在组合不同选择器时提供了更大的灵活性。pierce/ 仅在 前缀表示法 中可用。

await page.locator('pierce/div').click();
// Same query as the pierce/ one using deep combinators.
await page.locator('& >>> div').click();

查询 Shadow DOM 中的元素

CSS 选择器不允许深入 Shadow DOM,因此,Puppeteer 在 CSS 选择器语法中增加了两个组合器,允许在 shadow DOM 内部进行搜索。

>>> 组合器

>>> 被称为 深度后代 组合器。它类似于 CSS 的后代组合器(用单个空格字符   表示,例如 div button),它会选择父元素下任意深度的匹配元素。例如,my-custom-element >>> button 将选择 my-custom-element(即 shadow host)的 shadow DOM 内所有可用的 button 元素。

ℹ️注意

深度组合器只对 CSS 选择器的第一个“深度”和开放的 shadow root 生效;例如,:is(div > > a) 将不起作用。

>>>> 组合器

>>>> 被称为 深度子元素 组合器。它类似于 CSS 的子元素组合器(用 > 表示,例如 div > button),如果父元素有直接的 shadow root,它会选择该 shadow root 下的匹配元素。例如,my-custom-element >>>> button 将选择 my-custom-element(即 shadow host)的直接 shadow root 内所有可用的 button 元素。

自定义选择器

你也可以使用 Puppeteer.registerCustomQueryHandler 添加自己的伪元素。这对于基于框架对象或你的应用程序创建自定义选择器非常有用。

例如,你可以使用 react-component 伪元素编写所有选择器,并实现如何解析提供的 ID 的自定义逻辑。

Puppeteer.registerCustomQueryHandler('react-component', {
  queryOne: (elementOrDocument, selector) => {
    // Dummy example just delegates to querySelector but you can find your
    // React component because this callback runs in the page context.
    return elementOrDocument.querySelector(`[id="${CSS.escape(selector)}"]`);
  },
  queryAll: (elementOrDocument, selector) => {
    // Dummy example just delegates to querySelector but you can find your
    // React component because this callback runs in the page context.
    return elementOrDocument.querySelectorAll(`[id="${CSS.escape(selector)}"]`);
  },
});

现在,你可以在应用程序中如下编写选择器。

await page.locator('::-p-react-component(MyComponent)').click();
// OR used in conjunction with other selectors.
await page.locator('.side-bar ::-p-react-component(MyComponent)').click();

另一个示例展示了如何为定位 vue 组件定义自定义查询处理器:

⚠️警告

依赖库或框架的内部 API 时要小心。它们随时可能发生变化。

Puppeteer.registerCustomQueryHandler('vue', {
  queryOne: (element, name) => {
    const walker = document.createTreeWalker(element, NodeFilter.SHOW_ELEMENT);
    do {
      const currentNode = walker.currentNode;
      if (
        currentNode.__vnode?.ctx?.type?.name.toLowerCase() ===
        name.toLocaleLowerCase()
      ) {
        return currentNode;
      }
    } while (walker.nextNode());

    return null;
  },
});

如下搜索给定的视图组件:

const element = await page.$('::-p-vue(MyComponent)');

前缀选择器语法

⚠️警告

虽然我们会维护前缀选择器,但推荐的方式是使用上文记录的选择器语法。

还支持以下旧版语法(${nonCssSelectorName}/${nonCssSelector}),它允许一次运行一个非 CSS 选择器。请注意,此语法不允许组合多个选择器。

// Same as ::-p-text("My text").
await page.locator('text/My text').click();
// Same as ::-p-xpath(//h2).
await page.locator('xpath///h2').click();
// Same as ::-p-aria(My label).
await page.locator('aria/My label').click();

await page.locator('pierce/div').click();