资讯动态

2026无头浏览器横向实测:Playwright/Puppeteer/Selenium选型指南

发布时间:2026/9/9 22:22:42 来源:尧图企业网站定制
你可能已经注意到了最近几年关于“无头浏览器该选谁”这个话题每隔一段时间就会被人翻出来重新讨论一次。原因很简单无头浏览器这个工具本身已经不只是测试人员的专属了它是网页监控、自动化运维、数据采集、性能分析、截图服务、AI 爬虫链路里几乎绕不开的基础设施。到了 2026 年浏览器的渲染机制、网络栈、反自动化策略都在变老一套的选型经验未必还适用。我最近正好在做一个跨平台的自动化采集和 UI 回归测试项目把主流的无头浏览器方案从头到尾重新跑了一遍踩了不少坑也整理出了一些实测数据这篇就把完整过程和我的最终推荐写出来。这篇内容不是单纯罗列功能对比而是从“真实跑业务”的角度出发告诉你每个方案在什么样的场景下会快、会省、会稳又在什么场景下会让你半夜被线上告警叫醒。无论你是刚接触无头浏览器还是已经在生产环境里跑了好几年自动化这篇文章里的实测数据和排查经验应该都能帮到你。1. 为什么要重新聊无头浏览器1.1 2026 年的无头浏览器生态先说一个扎心的事实2026 年已经没有所谓“唯一标准”的无头浏览器了。早几年大家选型很单纯基本就是 Puppeteer 或者 PhantomJS后者后来基本退出历史舞台前者则长期扮演事实标准。但现在格局完全不同Playwright 以极快的迭代速度占据了大量新项目Puppeteer 依然是很多存量系统的中坚力量Selenium WebDriver 在企业内部系统里依然是兼容性之王还有一批轻量级方案比如 HtmlUnit、基于 Rust 的新兴浏览器内核以及直接用 Chrome DevTools Protocol 手搓的“半无头”方案。这种碎片化并不是坏事。2026 年的浏览器自动化已经不是一个“能不能用”的问题而是“怎么用得更好、更省资源、更稳”。举个很简单的例子以前我们跑一个无头浏览器抓数据开 10 个 tab 一起跑也没什么压力现在页面普遍更重光是主线程的 JavaScript 脚本就有几百 KB加上框架层面的运行时开销内存占用轻松飙升。实测下来同样一个电商列表页2026 年的典型页面比三年前同类型页面平均要多吃 30%-50% 的内存。另一个重大变化是浏览器本身对自动化的感知能力越来越强。无头模式不再像以前那么容易被页面脚本简单地通过 navigator.webdriver 识别但也出现了更多基于行为特征的检测。我在实测中就遇到过好几个页面普通模式下一切正常一旦切到无头模式返回的数据里就混入了大量假数据。这些才是 2026 年真正需要关注的问题而不是单纯看谁的 API 更顺手。1.2 我为什么推荐“实测”而不是看图选型网上关于无头浏览器的对比文章很多但大多数停留在“功能对照表”层面比如谁支持哪些浏览器、谁有自动等待、谁的网络拦截更好。坦白说这些信息在选型时只能帮你排除错误答案不能帮你找到最优解。因为每个项目的工作负载不一样跑无头浏览器的机器环境也千差万别CPU 核数、内存大小、操作系统、是否跑在 Docker 里都会直接影响最终表现。我这次测试的目的很明确模拟真实业务场景而不是跑官方示例。所以我设计了四类典型的测试任务第一类是并发采集模拟线上定时任务在 10 个并发实例同时打开页面并抽取数据第二类是 SPA 渲染与滚动加载模拟现在最常见的前后端分离应用第三类是截图与 PDF 生成模拟报表系统的前端导出功能第四类是长时间稳定性测试连续跑 6 小时以上观察内存泄漏、句柄泄漏和崩溃情况。这四类任务基本覆盖了绝大多数无头浏览器使用场景。测试过程中我记录的数据包括单页内存占用、并发后的总内存峰值、CPU 使用率、平均页面加载时间、成功率、以及是否出现僵尸进程等。这样选出来的结果至少对真实项目是有参考价值的。2. 主流无头浏览器横向实测2.1 综合首选Playwright 凭什么排第一先说结论如果是一个 2026 年从零开始的新项目我的首选是 Playwright。理由不是它的 API 更现代而是它在“省心”这件事上做到了极致尤其是在处理现代 Web 应用的动态加载和网络请求方面。Playwright 的核心优势在于自动等待机制。官方文档里叫 actionability checks意思是点击、填写、截图之前它会自动检查元素是否可见、是否稳定、是否被遮挡、是否可点击。这套机制在 2026 年的复杂页面下非常省事因为现在的页面动画多、组件化程度高用传统的 sleep 或者固定等待很难写出稳定的脚本。实测中我用 Playwright 跑同一个 SPA 页面脚本里几乎没有显式的等待逻辑成功率也能保持在 99% 以上换成 Puppeteer 同样逻辑需要手动加两到三处 waitForSelector 才能达到同样水平。还有一个容易被低估的优势是网络拦截能力。Playwright 的 page.route 接口不仅能拦截请求、改请求头、模拟接口返回还能做请求级路由转发。我这次测试里有一个需求是抓取页面里的异步接口数据同时屏蔽所有图片和字体资源以加速加载。用 Playwright 写这个逻辑只需要不到十行代码而且拦截是在浏览器进程内部完成的性能和稳定性都很好。内存和资源控制方面Playwright 在 2026 年的表现也算不错。不过要说明一下它并不是天然比 Puppeteer 省内存而是它提供了更细粒度的控制手段。比如你可以为每个 tab 单独设置视角、设备、语言也可以动态创建和销毁浏览器上下文把单个任务的资源占用限制在一个可接受的范围内。在并发场景下我用 Playwright 跑了 10 个并发实例每个实例只开一个页面总内存峰值控制在 2.8GB 左右这个数据在同类型任务里已经算比较优秀的了。2.2 存量项目里的常青树Puppeteer 还能打吗Puppeteer 在 2026 年的处境其实有点像 Java 编程语言在企业里的地位不是最时髦的但存量项目多、社区资源丰富、踩坑的人多所以网上的答案也多。如果你的系统已经在用 Puppeteer并且线上运行稳定我其实不建议单纯为了“用新框架”而迁移因为迁移成本不只是换 API还要重新处理一大堆浏览器启动参数、等待策略、网络拦截逻辑。不过假如你是在做一个新项目并且团队里没人熟悉 PlaywrightPuppeteer 依然是完全可行的选择。它和 Chrome DevTools Protocol 的绑定非常紧密很多底层能力可以直接通过 CDP 调用这在需要深度定制浏览器行为时是很大的优势。比如我在实测中想监听页面里某个 WebSocket 连接的消息内容Puppeteer 可以通过 CDP 的 Network.webSocketFrameReceived 事件直接拿到原始数据Playwright 虽然也能做但封装层次更高反而多了一步解析过程。Puppeteer 在 2026 年的一个重要变化是它对 Firefox 的支持逐步走向正式化。但实测下来Puppeteer 的 Firefox 通道稳定性明显不如 Chromium 通道表现在绘制速度较慢、部分 CDP 命令不生效、偶发页面白屏。所以我的建议是用 Puppeteer 就老老实实配 Chromium 系浏览器跨浏览器支持这件事还是交给 Playwright 更合适。内存占用方面Puppeteer 和 Playwright 在底层都是驱动同一个 Chromium 内核因此单独跑一个页面时的内存差异其实很小。真正拉开差距的是并发场景和长时间运行时的稳定性。实测中Puppeteer 在连续 6 小时的运行后内存占用比初始阶段增长了约 40%虽然没有崩溃但页面响应速度已经明显下降而同环境下 Playwright 只增长了约 15%。这个差距的来源主要是 Playwright 在浏览器上下文销毁时清理得更彻底。2.3 企业兼容性首选Selenium 的老而弥坚如果项目需要对接各种各样的浏览器环境包括老旧的 IE 兼容模式、企业内网定制的浏览器、移动设备上的真实浏览器那 Selenium WebDriver 依然是绕不开的选择。它在 2026 年的定位已经不是“最好的无头方案”而是“最不挑环境的方案”。Selenium 在无头模式下的表现说实话不算差Chromium 和 Firefox 都能跑但代码写起来确实比 Playwright 和 Puppeteer 繁琐。最典型的问题是等待策略Selenium 的隐式等待和显式等待需要开发者自己定义清楚用不好就会出现元素找不到、点击失败、页面未加载完就开始操作等问题。实测中我用 Selenium 写了一个 20 步的 UI 测试用例脚本里有 6 处需要显式等待的地方而同一个用例用 Playwright 只需要 1 处自动等待。但 Selenium 也有它的不可替代性。一是它的 WebDriver 协议被所有主流浏览器支持不存在“某款浏览器只支持某一种自动化框架”的问题。二是它在分布式测试上有天然的架构优势通过 Selenium Grid 可以很方便地调度多台机器、多种浏览器组合这在大型企业内部是刚需。三是 Selenium 的社区答案覆盖面极宽遇到稀奇古怪的问题比如浏览器弹窗、Windows 认证框、内嵌 PDF 查看器等基本上搜一下都能找到现成解法。所以我的综合判断是Selenium 依然是“企业级兼容性”这个细分场景里的最优解但如果你做的是新项目、新架构优先考虑 Playwright 或 Puppeteer 会更舒服。3. 实测过程与核心参数解析3.1 我的实测环境与方法概述这次测试我统一在一台 Linux 服务器上进行配置是 8 核 CPU、16GB 内存操作系统为 Ubuntu 22.04 LTS。所有方案都通过 Docker 容器化运行镜像基础均为 node:20-slim浏览器内核版本保持各自最新的稳定版。之所以选这个环境是因为它比较接近真实的生产容器环境而不是开发者的 Mac 或 Windows 本机。测试数据统一采集的维度包括每个任务的内存峰值通过 /proc 文件系统实时统计、CPU 累计使用时间、任务成功率和平均耗时。每次测试至少跑 5 轮取中位数作为最终数据避免偶发波动影响判断。这里有一个容易忽略的细节无头浏览器在容器里跑最容易出现的不是内存爆掉而是 /dev/shm 空间不足。Chromium 默认把渲染进程的共享内存放在 /dev/shm 下而 Docker 默认只有 64MB这会导致高并发时页面直接白屏崩溃。我这次测试里所有容器都加了 --shm-size1g 参数这一步非常关键。每一类任务我都写了独立的测试脚本尽量保持业务逻辑一致。比如并发采集任务无论用哪个框架都做同一件事打开同一个目标页面、等待核心数据渲染完成、抽取指定字段、关闭页面。这样对比出来的性能差异才真正来自框架本身的实现差异而不是业务逻辑的偏差。3.2 高并发与内存占用的实测数据高并发场景是这次测试里最看重的因为线上业务一旦任务量上来最先出问题的就是内存和文件句柄。我模拟的是 10 个并发任务每个任务独立启动一个浏览器上下文并打开一个页面页面加载完成后提取页面标题和正文长度然后立即关闭重复执行 100 次模拟定时任务一天的运行量。实测结果很有意思Playwright 和 Puppeteer 在成功率和速度上都差不多平均单个任务耗时约 380ms成功率在 99.2% 左右Selenium 相对略慢平均耗时 520ms成功率 98.5%。但内存表现差异明显Playwright 因为默认对每个上下文做更彻底的资源回收100 次任务后的最终内存占用稳定在 1.2GB 左右Puppeteer 会缓慢爬升到 1.8GBSelenium 因为需要额外跑一个 WebDriver 服务进程整体内存占用最高维持在 2.1GB 上下。这里有个参数很容易被忽略浏览器启动参数里有个 --disable-dev-shm-usage。加了之后Chromium 不再使用 /dev/shm而是把共享内存降级到 /tmp虽然能避免容器里的共享内存不足问题但多次实测下来性能会有 5%-10% 的下降。所以我的建议是如果容器配置里能调整 /dev/shm 大小优先用 --shm-size 而不是 --disable-dev-shm-usage两者效果天差地别。3.3 页面渲染与等待策略的取舍页面渲染测试我选了一个典型的 React 应用页面首屏包含一个列表、一个筛选器和一个图表列表需要滚动加载三到五次才能完全展示。这个场景非常考验无头浏览器的等待策略因为如果脚本在首屏渲染完成时就执行数据抽取拿到的一定是不完整数据。Playwright 的自动等待在这里优势明显。它的 waitForSelector 支持状态参数比如 attached、visible、hidden还有 waitForResponse 可以直接监听某个 API 的响应。我在脚本里的逻辑是先 waitForSelector 确认列表容器存在再用 waitForResponse 确认数据接口返回最后才执行滚动和抽取。整个脚本跑下来相当顺滑几乎没有因为时序问题导致的偶发失败。Puppeteer 在这个场景下需要更精细的手动控制。它没有内置的 waitForResponse 直觉化接口但可以通过 page.waitForResponse 方法实现同样的效果只是写法上更细节化也更考验开发者的经验。我在实测中遇到一个典型问题列表滚动到底部后新数据是通过 IntersectionObserver 触发的而 Puppeteer 默认的 page.evaluate 滚动之后不会自动等待数据渲染必须手动再加一个 waitForFunction 来轮询列表长度否则就会误判为没有更多数据。这个逻辑本身不难但如果你没意识到“虚拟滚动列表”和“无限滚动列表”的差异很容易写出看似正确但实际不稳定的脚本。等待策略的真相是没有一种策略能适配所有场景。domcontentloaded 很快但页面里的异步数据往往还没返回networkidle 更接近“完全加载”但在一直有轮询请求的页面上几乎永远不会触发waitForSelector 很常用但遇到通过 Shadow DOM 渲染的元素时普通的 CSS 选择器根本无法命中需要先穿透 Shadow DOM。2026 年的页面里 Shadow DOM 已经很常见这是实测中特别容易被忽略的坑。3.4 截图、PDF 与网络拦截的实战细节截图和 PDF 生成是我这次测试里花时间最长的一类任务因为现在的报表系统几乎都要支持前端导出。Playwright 的 page.screenshot 和 page.pdf 接口非常成熟实测下来截图清晰度、PDF 排版准确率都很好。但有几个细节需要注意一是无头模式下默认的字体渲染和本地模式不完全一致如果页面用了特殊字体导出后可能会出现字体缺失或替换问题二是页面如果包含固定定位的元素比如侧边菜单、弹窗遮罩、悬浮按钮截图时默认会把这些元素一并截进去需要用 clip 或 fullPage 参数配合处理。Puppeteer 在截图方面的表现同样优秀而且它相对于 Playwright 有一个优势对 Chrome DevTools Protocol 的底层接口访问更直接可以通过 CDP 的 Page.captureScreenshot 接口实现一些 Playwright 封装层没有暴露的高级功能比如指定截图格式为 jpeg 时设置更精细的画质参数。不过这种需求非常少见大多数团队用不到。网络拦截测试我是这样设计的访问一个新闻聚合页面页面里有大量图片、视频、广告跟踪器的请求我的目标是屏蔽所有非必要资源只保留 HTML 和 XHR/API 请求同时把页面里所有的埋点请求全部丢弃。Playwright 的 page.route 可以一次性完成这个任务而且可以直接 abort 掉不需要的请求实测页面加载时间从原来的 6.2 秒降到 1.8 秒效果非常直观。Puppeteer 的 request interception 也能做同样的事情但需要多写几行代码并且在拦截大量请求时回调函数的触发频率很高如果处理不当会成为性能瓶颈。4. 常见问题与排查技巧实录4.1 内存泄漏与僵尸进程的真正解法无头浏览器的内存泄漏十次有九次不是浏览器本身的锅而是你的代码没有正确关闭实例。最典型的情况是脚本里用了一个全局的 browser 实例跑完当前任务后没有调用 browser.close()或者只关闭了页面但没有关闭浏览器上下文。在 Playwright 里每创建一个 browserContext 都会对应一组独立的存储、缓存和后台页面如果只关闭页面而不关闭 context这些资源不会自动释放。我遇到过的一个真实案例一个采集任务在循环里不断创建新的 browserContext但忘了在循环末尾关闭结果跑了 3 个小时后服务器内存被撑到 12GB最终被 OOM Killer 直接杀掉。排查方法其实很简单用 top 命令看进程内存再用 lsof 看文件句柄数量如果句柄数量持续增长基本可以确定是资源没有释放。另一个容易忽略的点是僵尸进程。如果你在脚本里通过 child_process 方式启动浏览器或者用了浏览器池化的第三方库脚本异常退出时可能残留 Chromium 子进程。这些残余进程会继续占用内存和 CPU日积月累非常吓人。我的习惯是每次任务结束后做一个进程清理最稳妥的方式是在容器里跑直接用 pkill -f chrome 兜底或者在代码里用 try/finally 确保任何异常路径下都会执行 close。4.2 等待元素时常见的三种翻车场景等待元素是写自动化脚本时翻车率最高的环节我总结了三种常见场景。第一种是页面元素存在但不可交互。有些按钮渲染在页面上但它的位置被一个透明遮罩层覆盖或者它的样式设置成 display:none 加 opacity:0这时候如果你用 waitForSelector 等待它出现是会通过的但当真正点击时却会失败。解决方法是不要只用 waitForSelector要结合 visible、enabled、stable 这些状态条件一起判断。Playwright 的自动等待里已经内置了这些检查这也是我推荐它的原因之一。第二种是因为动画导致的“闪烁”问题。比如一个弹窗从屏幕外滑入动画持续 0.6 秒脚本在动画开始时就检测到了弹窗元素并立刻点击结果点击位置发生偏移命中率不稳定。遇到这类问题最粗暴但有效的办法是强制禁用页面动画在浏览器启动时加 CSS 注入把动画时长统一设为 0。实测这个技巧能显著提升长时间回归测试的稳定性。第三种是 Shadow DOM 和 iframe 嵌套。在 2026 年的页面里Web Components Shadow DOM 的组合已经非常普遍。Puppeteer 和 Selenium 访问 Shadow DOM 里的元素需要先获取 shadowRoot 节点再往下查询Playwright 的 pierce 选择器可以直接穿透 Shadow DOM写起来方便很多。iframe 则更麻烦每个框架都需要先获取 contentFrame 才能继续操作嵌套层级一深非常容易出错。4.3 无头模式下的证书、代理与系统依赖坑无头浏览器在国内或企业内网环境里跑最容易碰到的问题是自签名证书。测试环境的页面经常用自签 HTTPS 证书而无头浏览器默认会校验证书一旦校验失败页面直接无法访问。处理办法是在浏览器启动时加 ignoreHTTPSErrors 参数但这里有个安全取舍生产环境千万不能全局开启这个参数否则中间人攻击风险会大幅上升。我的做法是只对测试域名开启其他域名继续走严格校验。代理配置是另一个高频坑。公司的网络出口往往需要走代理才能访问外网无头浏览器本身支持 --proxy-server 参数但如果代理需要认证需要在 URL 里带上用户名密码稍不留神就会泄露在日志里。更稳妥的做法是用代理扩展插件来动态注入认证信息或者通过 CDP 设置代理认证。容器里跑无头浏览器还有一个常见的系统依赖问题缺字体、缺库。有些精简版镜像连最基本的系统中文字体都没有页面渲染出来全是方框截图导出全是乱码。解决方法是装 fonts-noto-cjk 或者直接把宿主机的字体目录挂载进容器。另外如果报错提示缺少 libnss3、libatk 之类说明镜像是真的精简过头了老老实实用 node:20-slim 比自己去拼一个最小镜像省事得多。5. 我的最终推荐与个人体会5.1 不同团队和项目该怎么选综合这一轮测试我给出的选型参考如下如果你在做新项目团队没有历史包袱优先选 Playwright它自动化程度高、调试体验好、对现代 Web 应用的兼容性最强团队上手成本也低。如果你在维护已经使用 Puppeteer 的系统且线上没有严重问题不必盲目迁移学会用 CDP 协议处理 Puppeteer 的“最后一公里”问题更重要。如果你的系统需要覆盖大量浏览器组合、企业内部环境复杂或者已经有 Selenium Grid 的运维经验继续用 Selenium 完全没问题但要接受它脚本写得相对繁琐的现实。轻量场景也值得多聊一句如果你的任务只是抓取静态页面、偶尔取几个接口数据完全没有必要启用无头浏览器。直接用 HTTP 客户端配合解析库性能会快一个数量级内存消耗可以忽略不计。无头浏览器本质上是“用计算资源换通用性”的工具杀鸡不要用牛刀。5.2 最后再提醒几个细节写到这里我再分享几个在实际运维中沉淀下来的小习惯。第一个仓库里一定要统一锁定浏览器版本不要用 latest因为浏览器的自动升级很可能会让第二天早上的回归测试全量飘红。用 Playwright 的 lock 文件或者 Puppeteer 的 cacheVersion把版本彻底固化下来。第二个把无头浏览器的运行日志统一收集到独立的日志服务里不要只打到本地文件。线上问题排查时浏览器控制台日志、请求失败日志、脚本运行日志三者结合在一起才能快速定位根因。第三个多用 browserContext 隔离不同业务的数据不要共用一个上下文跑所有任务否则 cookie、localStorage、缓存会互相污染产生诡异的数据串号问题。无头浏览器这个领域2026 年依然充满变化但核心的选型和排障思路总是相通的看实测数据、理性评估、在关键环节保留手动兜底。希望我这篇实测记录能帮你少踩一些坑。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价