项目简介
Puppeteer 提供高级 API,通过 DevTools 协议或 WebDriver BiDi 控制 Chrome 或 Firefox。默认无头运行,广泛用于浏览器自动化、测试和网络爬虫。支持 Node.js,提供截图、PDF 生成和表单交互等功能。
README 预览
# Puppeteer\n\n[](https://github.com/puppeteer/puppeteer/actions/workflows/ci.yml)\n[](https://npmjs.org/package/puppeteer)\n\n\n\n> Puppeteer is a JavaScript library which provides a high-level API to control\n> Chrome or Firefox over the\n> [DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/) or [WebDriver BiDi](https://pptr.dev/webdriver-bidi).\n> Puppeteer runs in the headless (no visible UI) by default\n\n## [Get started](https://pptr.dev/docs) | [API](https://pptr.dev/api) | [FAQ](https://pptr.dev/faq) | [Contributing](https://pptr.dev/contributing) | [Troubleshooting](https://pptr.dev/troubleshooting)\n\n## Installation\n\n```bash npm2yarn\nnpm i puppeteer # Downloads compatible Chrome during installation.\nnpm i puppeteer-core # Alternatively, install as a library, without downloading Chrome.\n```\n\n:::note\n\nModern package managers (including npm (see the [RFC](https://github.com/npm/rfcs/pull/868)), pnpm, Yarn, Bun, and Deno) block dependency install scripts by default. If the install script is blocked, Puppeteer will not download the browser during installation, leading to runtime errors.\n\nYou can manually download the required browsers after installation by running:\n\n```bash npm2yarn\nnpx puppeteer browsers install\n```\n\nAlternatively, you can configure your package manager to allow the install script to run (for example, with npm, by adding `"puppeteer"` to `"allowScripts"` in your `package.json`).\n\n:::\n\n## MCP\n\nInstall [`chrome-devtools-mcp`](https://github.com/ChromeDevTools/chrome-devtools-mcp),\na Puppeteer-based MCP server for browser automation and debugging.\n\nPuppeteer also supports the experimental [WebMCP](https://pptr.dev/guides/webmcp) API.\n\n## Example\n\n```ts\nimport puppeteer from 'puppeteer';\n// Or import puppeteer from 'puppeteer-core';\n\n// Launch the browser and open a new blank page.\nconst browser = await puppeteer.launch();\nconst page = await browser.newPage();\n\n// Navigate the page to a UR
常见问题 (5)
故障排除如何修复使用 localhost 通过 browserWSEndpoint 将 Puppeteer 连接到 Chrome 时出现的 ECONNREFUSED ::1 错误?
出现这种情况是因为 Node.js 18+ 会将 localhost 解析为 IPv6 地址 ::1,而 Chrome 的 DevTools 服务器只监听 IPv4 的 127.0.0.1。http://localhost:9222/json/version 返回的 webSocketDebuggerUrl 中包含 ws://localhost:...,因此到 ::1:9222 的连接会被拒绝(该问题在 Puppeteer 17.0.0 中开始出现)。**解决方法:**在连接之前,将端点中的 localhost 替换为 127.0.0.1:
const { webSocketDebuggerUrl } = await fetch('http://127.0.0.1:9222/json/version').then(r => r.json());
const browser = await puppeteer.connect({
browserWSEndpoint: webSocketDebuggerUrl.replace('localhost', '127.0.0.1')
});
或者,通过 dns.setDefaultResultOrder('ipv4first') 将 Node 的 DNS 结果顺序设置为 IPv4 优先,也可以在启动 Node 时使用 --dns-result-order=ipv4first 参数。
故障排除为什么在 Puppeteer 全页截图中嵌入的视频缩略图显示为空白,以及如何修复此问题?
嵌入的视频和 iframe 通常采用懒加载,因此在 page.screenshot({ fullPage: true }) 捕获页面时它们往往还未渲染。解决方法:先滚动页面以触发懒加载,然后滚回顶部再截图。
方案 1 — 使用鼠标滚轮模拟滚动:
await page.goto('https://example.com', { waitUntil: 'networkidle0' });
for (let i = 0; i < 10; i++) {
await page.mouse.wheel({ deltaY: 400 });
await new Promise(resolve => setTimeout(resolve, 1000));
}
await page.evaluate(() => window.scrollTo(0, 0));
await new Promise(resolve => setTimeout(resolve, 3000));
await page.screenshot({ fullPage: true, path: 'thumbnails.png' });
方案 2 — 直接将每个 video/iframe 滚动到可视区域:
await page.evaluate(async () => {
for (const el of document.querySelectorAll('video, iframe')) {
el.scrollIntoView();
await new Promise(r => setTimeout(r, 200));
}
window.scrollTo(0, 0);
});
await page.screenshot({ path: 'fullpage.png', fullPage: true });两种方法都能强制懒加载的缩略图在截图前完成渲染。已在 Puppeteer 22.13.1 上验证有效。
故障排除为什么在 Puppeteer 中,来自专用 Worker(dedicated worker)的被拦截请求永远无法完成(request.respond/continue/abort 均不起作用)?
这是 Puppeteer 在 v25.12.0 及之前版本中的一个已知 bug。当专用 worker 调用 fetch() 时,Chrome 会在页面会话(page session)上发出 Fetch.requestPaused,但在 worker 会话(worker session)上发出 Network.requestWillBeSent。由于竞态条件,Puppeteer 存储该暂停事件时没有记录其所属会话,随后在 worker 会话上发送 Fetch.fulfillRequest(或 continue/abort),而该会话没有 Fetch 域。Chrome 会以 'Fetch.fulfillRequest' wasn't found 拒绝该命令,Puppeteer 吞掉了这个错误,worker 请求便会永远挂起。修复方法:将 Puppeteer 升级到包含 PR #15605 的版本(在 25.12.0 之后已在 main 分支修复),例如 npm install puppeteer@latest。由于这是一个会话路由竞态条件,受影响的版本没有可靠的变通方案;只有当 requestWillBeSent 恰好先到达时,请求才会成功。
故障排除为什么在 Puppeteer 的 fullPage 截图中,fixed/sticky 页头会出现在错误的位置(中间或底部),以及如何解决?
原因:page.screenshot({ fullPage: true }) 默认使用 captureBeyondViewport: true,它会在不重置滚动位置的情况下捕获整个文档。如果你的代码滚动了页面(例如,一个滚动到底部以触发懒加载的 autoScroll 辅助函数),window.scrollY 会停留在底部。此时带有 position: fixed/sticky 的元素或基于滚动的 JS 会在当前视口位置渲染,而不是在页面顶部渲染。
修复方法一 —— 在捕获之前滚动回顶部:
await autoScroll(page);
await page.evaluate(() => window.scrollTo(0, 0));
await page.waitForTimeout(300); // 让 sticky 元素稳定下来
await page.screenshot({ path: 'example.png', fullPage: true });
修复方法二 —— 通过注入 CSS 强制将头部设为 absolute 定位,或将其完全隐藏:
await page.addStyleTag({ content: 'header, .navbar, .sticky-nav { position: absolute !important; }' });
// 或者完全移除它:
// await page.addStyleTag({ content: 'header { display: none !important; }' });
await page.screenshot({ path: 'example.png', fullPage: true });
另外,page.screenshot({ fullPage: true, captureBeyondViewport: false }) 可以改变捕获行为,但重置滚动位置是最可靠的变通方法。
故障排除为什么 puppeteer.connect() 会丢弃 browserURL 中的路径前缀,调用 /json/version 而不是 /t/token/json/version?
出现这种情况是因为 Puppeteer 使用了 new URL('/json/version', browserURL)。在 WHATWG URL API 中,以 / 开头的路径是绝对路径,会替换掉基础 URL 的整个 pathname。该问题已在 Puppeteer PR #15435 中修复,修复方式是将 json/version 追加到现有的 pathname 之后。请将 puppeteer-core 或 puppeteer 升级到包含该修复的版本。
临时解决方案:在保留前缀的前提下自行解析 version 端点,然后使用 browserWSEndpoint 进行连接:
const versionURL = new URL(browserURL);
versionURL.pathname = versionURL.pathname.replace(/\/?$/, '/') + 'json/version';
const response = await fetch(versionURL.href);
const { webSocketDebuggerUrl } = await response.json();
const browser = await puppeteer.connect({
browserWSEndpoint: webSocketDebuggerUrl,
});
这样可以正确地将 http://host:9222/t/token 转换为 http://host:9222/t/token/json/version。