核心概念
- 浏览器管理
- 页面交互
- JavaScript 执行器
- 网络侦听
浏览器管理:
启动浏览器
javascriptimport puppeteer from 'puppeteer' (async () => { // 启动浏览器 const browser = await puppeteer.launch() const page = await browser.newPage() })()
关闭浏览器
javascriptimport puppeteer from 'puppeteer' (async () => { const browser = await puppeteer.launch() const page = await browser.newPage() // 关闭浏览器 await browser.close() })()
上下文:用来隔离自动化任务中 Cookie 和 本地存储共享的问题,当上下文关闭时所关联的页面会一同关闭;
javascriptimport puppeteer from 'puppeteer' (async () => { const browser = await puppeteer.launch() // Cookie 和 本地存储只会在此上下文中共享 const context = await browser.createBrowserContext() const page1 = await context.newPage() const page2 = await context.newPage() await context.close() })()
上下文权限:例如在第一次访问高德地图时浏览器会提示你手动开启定位权限,如遇到类似情况,就可以执行
overridePermissions()
函数提前配置权限;javascriptimport puppeteer from 'puppeteer' (async () => { const browser = await puppeteer.launch({ headless: false, }) const context = await browser.defaultBrowserContext() const url = 'https://ditu.amap.com/' await context.overridePermissions(url, ['geolocation']) const page = await context.newPage() await page.goto(url) })()
连接到正在运行的浏览器
javascriptimport puppeteer from 'puppeteer' (async () => { // 携带远程调试端口参数打开第一个浏览器窗口,并导航到如下地址获取 webSocketDebuggerUrl const browser = await puppeteer.launch({ headless: false, args: ['--remote-debugging-port=9222'] }) const page = await browser.newPage() await page.goto('http://localhost:9222/json/version') })()
javascriptimport puppeteer from 'puppeteer' (async () => { const browser = await puppeteer.connect({ headless: false, browserWSEndpoint: '' // 将 webSocketDebuggerUrl 填写到此,并连接第一个浏览器窗口 }) await browser.newPage() // 任务处理完毕后可断开连接而不会关闭浏览器窗口 browser.disconnect() })()
页面交互:
Puppeteer 允许通过鼠标、触摸事件和键盘输入与页面上的元素进行交互。通过,首选 CSS 选择器查询 DOM 元素,然后对元素调用操作。除 CSS 选择器被 Puppeteer 默认 API 支持以外,还提供了自动以选择器语法,允许使用 XPath、Text、Accessibility 属性查找元素并访问 Shadow DOM。
定位器:
定位器(Locators)是选择元素并与之交互的推荐方式,定位器 API 支持 Puppeteer 自动等待元素在 DOM 中处于可操作的正确状态。当 定位器 API 无法满足要求时仍可以使用低级别的 API,例如:page.waitForSelector()
或 ElementHandle
。
使用定位器单机元素:
javascriptawait page.locator('button').click();
PS:定位器会在单击前自动检查以下内容:
- 确保元素位于视口中
- 等待元素变得可见或隐藏
- 等待元素启用
- 等待元素在两个连续的动画帧上具有稳定的边界框
使用定位器录入内容:
javascriptawait page.locator('input').fill('hello world');
PS:定位器会在输入前自动检查以下内容:
- 确保元素位于视口中
- 等待元素变得可见或隐藏
- 等待元素启用
- 等待元素在两个连续的动画帧上具有稳定的边界框
使用定位器实现鼠标悬停(hover):
javascriptawait page.locator('div').hover();
PS:定位器会在悬停前自动检查以下内容:
- 确保元素位于视口中
- 等待元素变得可见或隐藏
- 等待元素在两个连续的动画帧上具有稳定的边界框。
使用定位器滚动元素:
javascriptawait page.locator('div').scroll({ scrollTop: 10, scrollLeft: 20 });
PS:定位器会在滚动前自动检查以下内容:
- 确保元素位于视口中
- 等待元素变得可见或隐藏
- 等待元素在两个连续的动画帧上具有稳定的边界框
使用定位器等待元素可见:
javascriptawait page.locator('.loading').wait();
PS:定位器在返回前会检查以下内容:
- 等待元素变得可见或隐藏
使用定位器实现等待函数:
示例中展示了使用定位器实现 JavaScript 函数 等待
MutationObserver
检测页面出现MutationObserver
元素:javascriptawait page.locator(() => { let resolve; 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()
定位器与过滤器配合:
示例中展示了使用定位器匹配
button
且通过过滤器再次匹配button
文本中包含Click Me
的元素,最后才会点击此元素。javascriptawait page.locator('button') .filter(el = el.innerText().includes('Click Me')) .click();
从定位器获取值:
使用
map
函数将元素映射为 JavaScript 值,再调用wait()
将返回序列化的 JavaScript 值:javascriptconst enabled = await page.locator('button') .map(el => !el.disabled).wait();
从定位器获取 ElementHandles:
使用
waitHandle()
函数将返回 ElementHandles 实例。如果定位器内置 API 无法满足时,此操作会很有用。javascriptconst buttonHandle = await page.locator('button').waitHandle(); await buttonHandle.click();
配置定位器自检项:
配置定位器以改变定位器默认的自动检查项,实例中将不做任何检查对按钮触发单击事件:
javascriptawait page.locator('button') .setEnsureElementIsInTheViewport(false) .setVisibility(null) .setWaitForEnabled(false) .setWaitForStableBoundingBox(false) .click();
配置定位器超时:
由于网页的响应速度存在差异,默认的超时时间不满足需要的情况下,可使用
setTimeout()
函数适当延长,超时后将抛出 TimeoutError 异常:javascriptawait page.locator('button').setTimeout(5 * 1000).click();
获取定位器事件:
目前定位器支持一个单独的事件,事件会在定位器准备执行某项动作前触发,以此表示所有前置条件已经得到满足,此事件可在日志记录或调试等场景发挥一定的作用:
javascriptawait page.locator('button') .on(LocatorEvent.Action, () => { console.log('clicked'); }).click();
等待执行选择器 :
等待选择器(waitForSelector)与定位器相比是一个比较低级别的 API,等待选择器允许等待元素在 DOM 中可用但不会自动重试该操作,并且需要手动释放生成的 ElementHandle 以防止内存泄漏。
import pprt from "puppeteer"
(async () => {
const browser = await pprt.launch()
const page = await browser.newPage()
await page.goto("URL_ADDRESS")
// 使用 waitForSelector 查询句柄
const element = await page.waitForSelector("div > .class-name")
await element.click();
// 注意释放资源
await element.dispose();
await browser.close();
})()
立即执行选择器:
立即查询选择器是由 Puppeteer 提供查询已知存在于页面元素的一组 API:
API | 描述 |
---|---|
page.$() | 返回与选择器匹配的单个元素 |
page.$$() | 返回与选择器匹配的多个元素 |
page.$eval() | 返回与选择器匹配的第一个元素上运行 JavaScript 函数的结果 |
page.$$eval() | 返回与选择器匹配的每一个元素上运行 JavaScript 函数的结果 |
选择器扩展:
Puppeteer 在选择器 API 中接受 CSS 选择器,此外还扩展了额外的选择器语法来为 CSS 选择器提供更多的功能。
非 CSS 选择器:Puppeteer 自定义伪元素扩展 CSS语法实现非 CSS 选择器选择元素。
XPath 选择器(
-p-path
):javascriptimport pptr from 'puppeteer' (async () => { const browser = await pptr.launch({ headless: false }) const page = await browser.newPage() await page.setViewport({ width: 1080, height: 1024 }) await page.goto('https://developer.mozilla.org/zh-CN/') // XPath 选择器 const textContent = await page.locator('::-p-xpath((//*[@class="tile-container"]/div/h3/a)[1])') .map(el => el.textContent) .wait() console.log(textContent) await page.screenshot({ path: 'screenshot.png' }) await browser.close() })()
文本选择器(
-p-text
):javascriptimport pptr from 'puppeteer' (async () => { const browser = await pptr.launch({ headless: false }) const page = await browser.newPage() await page.setViewport({ width: 1080, height: 1024 }) await page.goto('https://developer.mozilla.org/zh-CN/') // 文本选择器 const textContent = await page.locator('::-p-text(Developer essentials: JavaScript console methods)') .map(el => el.textContent) .wait() console.log(textContent) await page.screenshot({ path: 'screenshot.png' }) await browser.close() })()
ARIA 选择器(
-p-aria
):javascriptimport pptr from 'puppeteer' (async () => { const browser = await pptr.launch({ headless: false }) const page = await browser.newPage() await page.setViewport({ width: 1080, height: 1024 }) await page.goto('https://developer.mozilla.org/zh-CN/') // 无障碍属性选择器 const innerHTML = await page.locator('::-p-aria(MDN homepage)') .map(el => el.innerHTML) .wait() console.log(innerHTML) await page.screenshot({ path: 'screenshot.png' }) await browser.close() })()
Pierce 选择器(
pierce/
):javascriptimport pptr from 'puppeteer' (async () => { const browser = await pptr.launch({ headless: false }) const page = await browser.newPage() await page.setViewport({ width: 1080, height: 1024 }) await page.goto('https://mdn.github.io/web-components-examples/composed-composed-path/') // 穿透 shadow DOM 选择器,深度组合器 const textContent = await page.locator('& >>> p') .map(el => el.textContent) .wait() console.log(textContent) await page.screenshot({ path: 'screenshot.png' }) await browser.close() })()
自定义选择器:
javascriptimport puppeteer, { Puppeteer } from "puppeteer" (async () => { // 注册 class 选择器 Puppeteer.registerCustomQueryHandler('class', { queryOne: (node, selector) => { return node.querySelector(`.${selector}`) }, queryAll: (node, selector) => { return [...node.querySelectorAll(`.${selector}`)] } }) const browser = await puppeteer.launch() const page = await browser.newPage() await page.goto("https://developer.mozilla.org/zh-CN/") // 使用 class 选择器 const innerHTML = await page.locator('::-p-class(tile-title)').map(el => el.innerText).wait() console.log(innerHTML) await browser.close() })()
Shadow DOM 选择器:CSS 选择器不允许穿透 Shadow DOM,因此,Puppeteer 在 CSS 选择器语法中添加了两个组合器,允许在 Shadow DOM 中进行搜索。
>>>
深度组合器:类似于 CSS 后代组合器(用单空格表示,例如div button
);>>>>
深度子组合器:类似于 CSS 子组合器(用>
表示,例如div > button
);
带前缀选择器语法:
${nonCssSelectorName}/${nonCssSelector}
为旧语法,允许一次运行一个非 CSS 选择器,不允许组合多个选择器,切不再推荐使用。通过文本选择器匹配元素,并返回该元素的文本内容:
javascript// 文本选择器 const textContent = await page.locator('text/Developer essentials: JavaScript console methods') .map(el => el.textContent) .wait() console.log(textContent)
通过 XPath 选择器匹配元素,并返回该元素的文本内容:
javascript// XPath 选择器 const textContent = await page.locator('xpath/(//*[@class="tile-container"]/div/h3/a)[1]') .map(el => el.textContent) .wait() console.log(textContent)
通过无障碍选择器匹配元素,并返回该元素的 HTML 结构:
javascript// 无障碍属性选择器 const innerHTML = await page.locator('aria/MDN homepage') .map(el => el.innerHTML) .wait() console.log(innerHTML)
通过 Pierce 选择器匹配 shadow DOM 元素,并返回第一个元素的文本内容:
javascript// 穿透 shadow DOM 选择器 const textContent = await page.locator('pierce/p') .map(el => el.textContent) .wait() console.log(textContent)
JavaScript 执行器:
Puppeteer 运行在 Puppeteer 驱动的页面上下文中执行 JavaScript 函数。
在示例中通过编写 JavaScript 函数来获取 MDN 网页中的指定元素:
import puppeteer from "puppeteer"
(async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto("https://developer.mozilla.org/zh-CN/")
// 获取第一个 h1 元素
const innerHTML = await page.evaluate(() => document.querySelector('h1').innerHTML)
console.log(innerHTML)
await browser.close()
})()
重构此函数以支持参数传递,灵活获取元素:
const innerHTML = await page.evaluate(
(selector) => document.querySelector(selector).innerHTML,
'h1'
)
如果此函数返回一个对象,Puppeteer 会将其序列化为 JSON 并在脚本端对齐重建。此过程可能会得到不正确的结果,为了处理返回的对象,Puppeteer 提供了一种通过引用返回对象的方法:
const handle = await page.evaluateHandle(
(selector) => document.querySelector(selector),
'h1'
)
console.log(handle instanceof ElementHandle)
网络请求日志:
Puppeteer 默认监听所有网络的请求和响应,并在页面上发出网络事件。
import puppeteer from "puppeteer"
(async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto("https://developer.mozilla.org/zh-CN/")
page.on("request", request => {
console.log(request.url())
})
page.on("respone", respone => {
console.log(respone.url())
})
})()