页面交互
Puppeteer 允许通过鼠标、触摸事件和键盘输入与页面上的元素交互。通常,你首先使用 CSS 选择器 查询一个 DOM 元素,然后对选中的元素执行操作。所有接受选择器的 Puppeteer API 默认都接受 CSS 选择器。此外,Puppeteer 还提供 自定义选择器语法,允许使用 XPath、文本、无障碍属性来查找元素,并访问 Shadow DOM,而无需执行 JavaScript。
如果你想在不先选择元素的情况下触发鼠标或键盘事件,可以使用 page.mouse、page.keyboard 和 page.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();