Puppeteer 是 Google 推出的 Node 库,可通过 JavaScript 驱动真实的 Chrome——你来编写自动化逻辑,命令则通过 Chrome DevTools Protocol 传递,完整浏览器会先把页面渲染好,你再读取其中内容。它在 GitHub 上的仓库是 puppeteer/puppeteer:Apache-2.0 许可证、TypeScript 编写,星标数大约 9.53 万(我截取快照当天是 95,307)。它的官方定位非常克制——“一个用于控制 Chrome(并实验性支持 Firefox)的 JavaScript API”——这既说明了它能做什么,也同样清楚地告诉你它不是什么。
我把 Puppeteer 24.16.0 放到和我们评测其他浏览器自动化库相同的测试环境与公开演示站里跑了一遍:包含分页的静态目录页、文章页、JavaScript 渲染目录页、JSON API、返回 500 的路由、小型爬取图,以及 Books to Scrape 和 Quotes to Scrape。它的渲染表现干净利落,没有花里胡哨。与此同时,它也把同样一个任务留在了我的桌面上——也就是所有无头浏览器库都会留下的那类任务——坦白承认这个缺口,正是把一篇有价值的评测和一篇公关稿区分开的关键。
比召回率更让我注意的是一个数字:在数据来自 JSON 端点的页面上,Puppeteer 完全没有抓取 DOM,就直接在页面里执行 fetch 并读取响应对象,拿到了全部 8 条记录。再加上原生渲染和可正常生成的截图,这正是这款工具的本质:它是一个成熟的 Chrome 渲染器,而不是爬虫框架;在你敲下第一行代码之前,这种差异就已经很重要了。
Puppeteer 到底是什么,又在和谁竞争
这里的分类标签并不是摆设,所以先从它说起。Puppeteer 是一个浏览器自动化库。它会启动 Chrome,打开页面,让页面自己的 JavaScript 运行,然后把渲染后的结果交给你去读取或截图。你之所以会选它,而不是 HTTP 客户端加 HTML 解析器,原因就在于:你想要的是脚本执行完成后的页面,而不是服务器先返回的那个空壳。
从形态上看,它真正竞争的是其他真实浏览器库——Playwright 和 Selenium——而不是 Scrapy 这类爬虫框架,也不是 LLM-Markdown 工具。如果你把一千个 URL 丢给 Puppeteer,然后指望它自动排队、去重、限速、写入数据集,那你是拿一个渲染器去解决爬取问题。它会把每一页都渲染得漂漂亮亮,但不会替你做任何编排。(这不是 bug,而是边界;我稍后还会回来讲,因为在采用这款工具之前,这恰恰是最需要先搞清楚的一点。)

Puppeteer 源自 Google 的 Chrome 团队,这也是它为什么天生偏向 Chrome,以及它的 API 为什么读起来像是给浏览器调试协议套上了一层薄而顺手的手套。它已经“老”到一种好事:你需要的方法早就稳定了好几年,文档完整,周边生态也很成熟。
原生渲染与页面内 fetch 的用法
从结果表里挑出两个行为单独看,会更能说明它在实际中会怎么用。
第一,原生渲染。那个由 JavaScript 在加载后于客户端构建商品网格的目录页,最终拿到了 8/8 的结果,并把整页截图保存到了磁盘。设置很简洁,但确实在 goto 之后等待了目标内容。公开的 Quotes to Scrape JS 页面也用同样的方式返回了全部 10 条 quote。这些都是测试样例的结果,不代表通用的渲染召回分数。
第二,JSON API 样例从 /api/dynamic-products 加载商品。通过 page.evaluate 在同源环境里执行 fetch,无需解析渲染后的行,就直接拿到了全部 8 条记录。这是一种通用的浏览器端执行模式,并不是 Puppeteer 独有的发现数据能力。只要你已经知道端点和请求约定,它可以让提取过程简单很多;但认证头、运行时 token、凭证策略、CORS/CSP、service worker 以及分页机制,仍然可能让应用请求和你想象的不一样。
第三点需要记住的不是数字,而是定位:Puppeteer 是一个成熟的 Chrome 渲染器,不是爬虫。两者都对,但很多文章只会写后半句。
Puppeteer 如何与 Chrome 对话

在 Chrome 上,Puppeteer 使用的是 Chrome DevTools Protocol(CDP)——也就是浏览器 DevTools 背后通过 WebSocket 传 JSON 的那套通道。puppeteer.launch() 会启动 Chrome 并建立这条协议连接;像 goto、$$eval 和 screenshot 之类的方法,则是通过更高层的 API 暴露浏览器能力。Firefox 的支持则走下文提到的 WebDriver BiDi 路径,因此并不是 Puppeteer 的每个操作都等价于一个 CDP 命令。
page.evaluate 会在页面上下文里执行函数,所以相对路径的 fetch('/api/...') 会使用该页面的 origin,并且可能复用符合条件的 cookie 和会话状态。但它不会自动复现应用自己加上的授权头、请求选项、token 或 service worker 行为。在这个同源样例里,它确实直接返回了 JSON;但生产环境的请求仍然要按真实约定逐项检查。
这也是为什么 Puppeteer 会显得“重”。每个页面都是真实的浏览器标签页,背后有完整的渲染引擎。这换来的是在 JavaScript 密集型页面上的正确性,代价则是相比纯 HTTP 抓取带来更多内存和启动时间。渲染从来都不是免费的;CDP 只是让账单变得清清楚楚。
引擎问题,直接说清楚
网上常有一种说法,说 Puppeteer 是“只能跑 Chrome”。对我测试的这个版本来说,这种说法并不准确;而把这件事说对,会直接改变比较方式。
| 引擎 | Puppeteer 24.16.0 的驱动方式 | 本次测试是否覆盖 |
|---|---|---|
| Chrome | 以 Chrome 为核心,基于 CDP —— 默认路径,既有自动化继续可用 | 是 |
| Firefox | 自 v23 起,通过 WebDriver BiDi 提供文档化支持 | 否 |
| WebKit | 完全不驱动 | — |
Chrome for Developers 和 Mozilla 都在 Firefox 支持落地时写过相关说明。我运行的版本是 24.16.0,已经远远超过 v23,所以“只能 Chrome”其实低估了它现在的能力。真正决定广度差异的,是它没有 WebKit,再加上它的跨引擎故事相较 Playwright 还年轻——而不是“一个引擎对三个引擎”这种简单表述。
我测试的版本中,Firefox/BiDi 路径是有文档、也可用的,但我没有把样例跑到那条路径上,所以这里报告的是能力,而不是测量结果。如果 Firefox 渲染对你的目标站点至关重要,务必先在你自己的页面上验证,再决定是否采用。对于想看引擎和语言支持正面对比的读者,我们的 Playwright 与 Puppeteer 对比 会用同样的测试把两者跑一遍,并在那篇里回答“选哪个”的问题;而本文的主题只聚焦 Puppeteer。
安装与部署的现实:重头戏其实是浏览器
默认的 npm install puppeteer 会下载一个兼容的 Chrome for Testing 构建。这个行为可以通过配置跳过或改向,用户也可以把 Puppeteer 指向另一个可执行文件,所以版本匹配取决于你的部署选择。对我这次安装来说,浏览器下载才是最重的部分;而包管理器的审计快照,也不能被视为长期稳定的安全属性。
这种自动打包既是实实在在的易用性提升,也是实实在在的体积成本,两边都值得明确说出来。好处是:你不用到处找兼容浏览器,也不用手动锁版本;npm install 就能拿到可用的一对。代价是:你下载的是一个浏览器,因此要为磁盘和带宽留预算,尤其是在 CI 环境里,冷缓存会让每个新 runner 都付出这笔成本。
这也让它和 Playwright 形成了一个非常真实的对照:后者把这两步拆开——先安装库,再运行单独的 npx playwright install 去拉取浏览器构建。两种方式都不难,只是“出错的方式”不同。Puppeteer 的单条命令可能会在流量计费网络上因体积吓你一跳,而 Playwright 的第二步则可能因为忘记执行而让你措手不及。你得知道自己在跑哪一种。
实测结果

所有测试都在 127.0.0.1 的本地样例服务器和两个公开练习站上运行,环境为 Node v22.22.3、macOS arm64、Puppeteer 24.16.0 及其自带的 Chrome。每个样例的真实答案都在运行前先写好了,因此召回率是和固定的预期集合做比较,而不是和 Puppeteer 当时实际打印了什么做比较。
公开研究包里包含 样例服务器、测试运行器 和 真实答案。依赖锁文件和原始运行摘要没有放进发布包,因为严格的安全审查拒绝了依赖锁数据和与环境绑定的端点材料。要复现安全的本地样例,请先运行 npm install,然后在 tools/puppeteer/tests 目录里执行 node run_puppeteer_material_tests.mjs,再把结果与已发布的真实答案对照。公开演示页可能变化,所以本地样例才是预期数量检查最稳定的基础。
如果你要做精确的历史依赖重建,请在本地创建并审计一个新的 lockfile,而不要把未公开的 lock 当作公开证据。
| 测试 | 目标 | 结果 |
|---|---|---|
| 静态目录 + 分页 | 本地样例 | 12/12,召回率 1.0 |
| 文章提取 | 本地样例 | 标题 + 3/3 段落,且成功分离样板内容 |
| 动态 JS 页面(原生渲染) | 本地样例 | 8/8,召回率 1.0,已保存整页截图 |
动态 JSON API(页面内 fetch) | 本地样例 | 8/8,召回率 1.0,无需抓取 DOM |
| HTTP 500 处理 | 本地样例 | 可检查到 500 状态,未抛错 |
| 爬取图(手写 BFS) | 本地样例 | 12 个页面,深度 {0:1, 1:4, 2:7} |
| Books to Scrape | 公开演示 | 20 个商品 |
| Quotes JS | 公开演示 | 10 条 quote,原生渲染 |
其中有几项值得比表格多说一句。
手写的分页代码找回了全部 12 个预期目录项。文章选择器提取到了标题和 3 段正文;周围的样板内容仍然保留在 DOM 中。Puppeteer 提供的是渲染后的 DOM,而哪些内容算“文章内容”,则由选择器逻辑决定,不是 Puppeteer 本身决定。
在测试的 HTTP 500 路由上,goto 返回了一个可以检查 500 状态码的 response 对象,而且没有抛异常。这并不代表没有其它导航错误——超时、DNS 故障、浏览器崩溃、已分离的 frame 等——这些仍然需要你显式处理。
爬取图才是把整个故事讲完整的那一项。沿着样例中的内部链接走完 12 个页面,并通过深度控制避免重复访问 URL,靠的是一个手写的广度优先搜索——因为 Puppeteer 没有内置爬取队列。它找到了全部 12 个页面,深度分布为 {0:1, 1:4, 2:7},也就是说,我写的 BFS 是有效的。但 BFS 是我写的。Puppeteer 负责渲染每一页;至于“怎么走站点”,那是我自己写的代码。对于 12 个页面来说,这不过是十几行代码,不算什么大事。可如果是几千个 URL,还要去重、重试和礼貌延迟,那十几行就会变成一个项目。
还有一个我想再次强调的注意点,因为它很容易被误用:这些产物里包含了每项测试的耗时,但那只是单次、单机的观察,不是基准测试。我不会因为一台笔记本和一次运行,就把 Puppeteer 的速度排在别的工具前面。这里能支持的,只是对八种不同页面类型的召回和行为观察——不是计时器层面的结论。
我没有测试什么
为了避免把结果读得比实际还广,这里列出这次测试没有覆盖、因此也不应被这些数字代表的部分:
| 不在测试范围内 | 状态 |
|---|---|
| 通过 WebDriver BiDi 驱动 Firefox | 文档已说明,24.16.0 可用,但本次未实际跑 |
| 代理与请求拦截 | 未测试;这是支持的功能,但我没有运行 |
| 并发多页规模 | 我跑的是小规模;真实并发下的浏览器集群行为未测量 |
| 在最新版本上重跑 | 我测试的是 24.16.0;截至 2026-07-09,npm latest 是 25.3.0,已经跨了一个完整大版本。我要说明的是,我实际使用的 API(launch、goto、$$eval、screenshot、页面内 fetch)在 24→25 之间都保持稳定,但如果你要押注精确数字,最稳妥的做法还是先在 25.3.0 上重跑 |
这些都不是缺点,它们只是一次样例运行能够诚实主张的边界。
优点与缺点
优点:
- 原生 JavaScript 渲染,且有明确的内容等待:动态样例 8/8、公开演示 10/10,截图也可正常生成。
- 手写的分页与文章选择器成功提取到了预期样例项。
- 页面内
fetch可从已知的同源端点取回全部 8 条记录,无需解析 DOM。 - 测试中的 HTTP 500 会以可检查的响应返回,不会直接抛异常。
- 默认安装会下载兼容的 Chrome for Testing 构建;也支持指定其他可执行文件和跳过下载。
- 基于 CDP 的成熟 Chrome 优先 API,生态深厚、文档完整,Apache-2.0 许可证。
- 比外界印象更广:自 v23 起已通过 WebDriver BiDi 文档化支持 Firefox。
缺点:
- 没有内置爬取队列、数据集写入器或限速器——要做爬取规模的工作,你得自己写,或者套一层封装。
- 不支持 WebKit,而且跨引擎能力比 Playwright 更年轻。
- 真实浏览器带来的体积:要下载捆绑的 Chrome,而且每个页面都有内存成本;这比纯 HTTP 工具重得多。
- 基于 Node;如果你要从其他语言调用,就得自己搭并维护桥接层。
- 我运行的版本(24.16.0)比 npm latest(25.3.0)落后了一个大版本——在押注精确结果前,最好先用当前版本重新验证。
适合谁,不适合谁
如果你主要在 Node 里工作,目标站点在 Chrome 中渲染正常(大多数都可以),并且你想要一个成熟、专注的库,把“JavaScript 执行完之后的页面”变成可读取、可截图的内容,那就优先考虑 Puppeteer。无论是抓取一组动态页面、在页面自己的会话里读取 JSON API,还是把渲染后的截图当作证据,它都是一个强大、低折腾的默认选项。通过 BiDi 的 Firefox 支持也已经在那儿,等你需要时可以扩展,而成熟的生态意味着你遇到的大多数问题,别人多半早就遇到过。
如果你的问题是爬取编排,而不是渲染,那就要三思。若你需要带着去重、重试和限速去走几百或几千个 URL,单靠 Puppeteer 会逼你手写一个爬虫——这不适合它的抽象层级。如果页面本身并不需要 JavaScript 才能暴露数据,那就干脆别上无头浏览器;只要 HTTP 请求加解析器就能拿到内容,真实浏览器就是昂贵的过度设计,只会白白消耗内存和启动时间。如果你需要 WebKit 的一致性,或者需要非 JavaScript 语言的客户端,那它也不是这条路上的工具。
替代方案,以及 Thunderbit 的位置
先说清楚最诚实的框架:Puppeteer 是免费的、Apache-2.0 的、自托管的,而且运行它的每一部分都由你负责——浏览器集群、附加的爬取代码,以及持续进行中的反爬对抗。对很多项目来说,这种“自己掌控”正合适,没有任何托管服务能比你手头已有的浏览器更便宜地去渲染一个已授权页面。
在开源世界里,真正有用的比较要按任务来,而不是按品牌名。就爬取规模而言,Crawlee 是最自然的搭档:它的 PuppeteerCrawler 会在 Puppeteer 外面包上一层请求队列、数据集和限速能力,而这些正是库本身刻意省掉的部分,这样你既保留了渲染,又得到了编排。如果你的输出目标是给 LLM 管道用的干净 Markdown,而不是渲染后的 DOM,那么 Crawl4AI 会驱动真实浏览器并直接产出这种格式。如果页面根本不需要浏览器,像 Scrapy 这样的 HTTP-first 框架就是另一类、更轻量的方案。当你同时要比较这些工具时,我们的 开源爬虫总览 会把这些类别并排列出来。
像 Thunderbit 这样的托管服务,会把浏览器操作和数据提取放到 API 后面。这次并没有把它放进 Puppeteer 的测试里,所以这篇评测不会对它的渲染、拦截、提取质量或成本做一一对应的结论。真正的分界线在于运维责任:你是自己维护浏览器和爬取代码,还是付费让服务商来承担这层工作。
使用 Puppeteer 时没有厂商使用费,但计算资源、带宽、浏览器维护、编排和运维都还是你自己的责任。托管方案会按用量收费,并把其中一部分责任转给提供商。本实验并没有比较两者的实际结果。
结论
如果你在 Node 环境中,并且需要以 Chrome 为核心的浏览器自动化,Puppeteer 24.16.0 值得评估。我们手写的样例代码抓回了 12 个静态项、8 个动态项和 10 条公开演示 quote;已知的同源 API 通过 page.evaluate 返回了 8 条记录;截图功能正常;测试中的 HTTP 500 也保持可检查。这些结果只对应我列出的样例和一个较旧的大版本,不应被泛化成“所有提取场景都能有这样的召回”。
不过,还是要准确衡量它的定位。Puppeteer 是渲染器,不是爬虫:我那次 12 页的站点遍历之所以需要手写 BFS,就是因为它没有内置队列,这个缺口在规模化场景里就是实打实的工作——要么交给 Crawlee,要么自己搭机器。它以 Chrome 为核心,虽有通过 BiDi 提供的 Firefox 支持,但没有 WebKit,所以并不适合追求跨引擎广度的场景。它也带着真实浏览器的重量。而且我测试的是 24.16.0,而 npm latest 是 25.3.0,所以在相信精确数字之前,最好先用当前版本重跑。把这四点记清楚,Puppeteer 就是一款非常优秀的 Chrome 自动化库;如果你指望它替你爬完整个站,那你最终会写出自己以为正在下载的那个爬虫。
试用 Thunderbit 进行网页数据提取 Get Started Free
常见问题
Puppeteer 能渲染 JavaScript 页面吗,还是需要插件?
它可以原生渲染,不需要插件。在我的动态样例中,它以 1.0 的召回率拿到了 8/8 个由客户端构建的商品,并保存了整页截图;公开的 Quotes to Scrape JS 页面也以同样方式返回了全部 10 条 quote——就是普通的 goto,然后读取渲染后的 DOM。因为 Puppeteer 是通过 DevTools Protocol 驱动真实 Chrome,页面脚本会在你读取之前先执行完。
Puppeteer 能在不解析 HTML 的情况下抓 JSON API 吗?
可以,前提是端点和请求约定允许。page.evaluate 能从页面 origin 发起请求,并且可能复用符合条件的 cookie,但它不会自动复现应用自带的请求头、token、选项或 service worker 行为。在同源样例中,它无需解析 DOM 就拿到了全部 8 条记录。
Puppeteer 是网页爬虫吗? 不是——它是浏览器自动化库,不是爬虫框架。它没有内置请求队列、数据集写入器或限速器,所以我那次 12 页爬取(深度 {0:1, 1:4, 2:7})必须手写广度优先搜索。这是能力边界,不是缺陷。若要做爬取规模的任务,可以配合 Crawlee 的 PuppeteerCrawler 这类封装,它会补上 Puppeteer 缺少的队列和数据集能力。
Puppeteer 只能跑 Chrome 吗? 现在不是了。它以 CDP 驱动 Chrome 为主,但自 v23 起已经通过 WebDriver BiDi 提供了文档化的 Firefox 支持,而我测试的版本(24.16.0)也远超那个版本。它不支持的是 WebKit,而且它的跨引擎故事比 Playwright 更年轻——这才是准确的限制,而不是“只能 Chrome”。这里我只实际跑了 Chrome,所以对 Firefox/BiDi 的描述是基于文档,而不是基于我的实测。
安装 Puppeteer 实际会下载什么?
默认情况下,npm install puppeteer 会下载兼容的 Chrome for Testing 构建。这个下载可以跳过或重定向,也可以配置其他可执行文件,因此版本匹配取决于你的部署方式。记得为浏览器预留磁盘和带宽,尤其是在没有缓存的 CI runner 上。本评测测试的是 24.16.0;请在发布时对应的最新版本上重新运行核心样例。


