页面交互
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 函数的任意条件很有用。在这种情况下,可以使用函数而不是选择器来定义定位器。以下示例等待 MutationObserver 检测到页面上出现 HTMLCanvasElement
元素。您还可以在函数定位器上调用其他定位器函数,例如 .click()
或 .fill()
。
await page
.locator(() => {
let resolve!: (node: HTMLCanvasElement) => void;
const promise = new Promise(res => {
return (resolve = res);
});
const observer = new MutationObserver(records => {
for (const record of records) {
if (record.target instanceof HTMLCanvasElement) {
resolve(record.target);
}
}
});
observer.observe(document);
return promise;
})
.wait();
在定位器上应用过滤器
以下示例展示了如何向表示为 JavaScript 函数的定位器添加额外的条件。仅当按钮元素的 innerText
为 'My button' 时,才会单击该按钮元素。
await page
.locator('button')
.filter(button => button.innerText === 'My button')
.click();
从定位器返回值
map
函数允许将元素映射到 JavaScript 值。在这种情况下,调用 wait()
将返回反序列化的 JavaScript 值。
const enabled = await page
.locator('button')
.map(button => !button.disabled)
.wait();
从定位器返回 ElementHandles
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
)
文本选择器将选择包含给定文本的“最小”元素,即使在(打开的)影子根中也是如此。这里,“最小”是指包含给定文本的最深层元素,而不是它们的父元素(技术上也会包含给定文本)。
// 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/
)
穿透选择器是一种返回文档中所有阴影根中与提供的 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 选择器的第一个“深度”和打开的阴影根上起作用; 例如,:is(div > > a)
将不起作用。
>>>>
组合器
>>>>
称为深度子组合器。它类似于 CSS 的子组合器(用 >
表示,例如 div > button
),并且它选择父元素直接阴影根下的匹配元素(如果该元素有)。例如,my-custom-element >>>> button
将选择 my-custom-element
(shadow host) 直接阴影根中的所有 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();