
很多刚接触爬虫的工程师在遇到数据异步加载、接口参数加密、DOM被动态渲染这些站点时第一反应就是把 Selenium、Playwright、Puppeteer 拉出来让浏览器替自己把页面跑出来再从 DOM 里抠数据。这个思路我能理解毕竟能看到就能抓到是最直觉的方案。但作为常年把爬虫放到生产环境跑的人我得说一句浏览器自动化工具的定位是端到端自动化测试不是数据采集。以爬虫为目标去使用它们会在性能、反爬对抗、稳定性、扩展性四个维度上遇到一堆结构性麻烦。这篇文章就围绕 Selenium、Playwright、Puppeteer 做爬虫的真实弊病展开掰开揉碎讲清楚为什么它们不适合当主力爬虫引擎以及哪些场景下你仍然可以把它们当一个兜底方案来用。1. 动态渲染页面逼出来的万能钥匙其实是一把错配的锁1.1 是什么把我们推到了浏览器自动化这条路上现在的网页早就不是请求 HTML 就拿全量数据的时代了。一个页面经常是空壳 HTML 加一堆 JavaScript打开之后前端再异步请求接口、渲染列表、填充数据。你要是直接分析接口会发现请求头里带着签名、时间戳、加密参数甚至整个响应体都是加密的。很多爬虫新手看到抖音 js 逆向接口参数加密这类词就头大于是转头去学 Selenium、Playwright、Puppeteer理由很简单我不逆向接口我让浏览器自己执行 JS渲染完我再抓 DOM。这套路在 Demo 阶段确实爽。写几十行脚本打开页面等两秒取文本数据到手。但问题恰恰出在等两秒和浏览器自己执行这八个字上。浏览器自动化工具的本质是替你模拟一个完整的浏览器环境它最擅长的是验证页面交互、跑测试用例而不是高吞吐、低延迟、稳定地批量取数。用爬虫的指标去要求它处处都是错配。1.2 定位不同代价自然不同拿 Selenium、Playwright、Puppeteer 本身来说它们的设计初衷非常明确。Selenium 历史最久核心价值是跨浏览器兼容、驱动真实浏览器跑自动化脚本Puppeteer 和 Playwright 则是基于 DevTools 协议操作 ChromiumAPI 更贴近前端工程Playwright 还内置了多浏览器支持和自动等待机制。这些设计在自动化测试场景里非常出色但测试脚本跑几十个用例就结束了爬虫却要一秒一秒地持续跑几小时、几天、几百万个页面。需求模型不一致后面所有弊病都是从这里长出来的。2. 性能账本一个浏览器实例在爬虫工作里有多重2.1 一次完整渲染比一次 HTTP 请求贵一个数量级用 requests 爬一个静态页面成本是一次 TCP 连接、几百毫秒的网络往返用浏览器自动化爬同一个页面成本是启动或复用浏览器进程、加载 HTML、解析 CSS、执行所有 JavaScript、等待网络请求、完成布局和绘制。一个普通动态页从发起导航到 DOM 可操作通常需要 2~5 秒如果页面里还带视频预加载、第三方统计脚本、字体文件时间还会往上走。我做过一组对比同一个新闻站点用 requests 并发 20 个线程抓接口数据单机一小时能处理几万条用 Playwright 开 4 个 context 顺序跑页面一小时能处理两三千个页面就已经算不错了。这不是代码优化能抹平的差距因为浏览器内部从 JS 引擎到渲染管线的开销是固定成本页面只要渲染这笔钱就省不掉。2.2 并发爬取时的内存和 CPU 账单如果你想靠多开浏览器实例来提升吞吐很快就会撞到内存墙。一个 Chrome 进程通常会再派生渲染进程、GPU 进程等就算无头模式也不能完全避免。我见过很多团队的 Selenium 爬虫试图开 20 个 WebDriver 实例结果单机内存直接飙到十几 GB还没开始稳定抓取OOM 先来了。Puppeteer 和 Playwright 在资源控制上会好一些因为它们支持同一个浏览器进程里创建多个 context每个 context 有自己的 Cookie、UA、存储空间相当于轻量级的独立会话比 Selenium 一个个 WebDriver 省内存得多。但 context 也不是免费的。每个 context 仍然带独立的 JS 执行环境几十个 context 同时跑CPU 的消耗一样很吓人。真正做生产的团队一般会把浏览器并发放到 5~10 个 context 以内再多就靠分布式节点横向堆机器。相比之下普通 HTTP 爬虫单机跑几百个并发连接是很常见的事。单位数据获取成本完全不是一个量级。2.3 等待策略浪费时间又是另一个隐形黑洞浏览器自动化爬虫最浪费时间的环节不是渲染而是等。页面渲染的完成时间受网络抖动、第三方资源、服务器负载影响极不稳定。许多初学者写固定time.sleep(3)结果有时候页面 5 秒才出数据脚本白等有时候 1 秒就出数据脚本又白白空耗 2 秒。Playwright 的自动等待机制解决了一部分问题比如wait_for_selector会等到元素出现才继续Selenium 也有 WebDriverWait 可以用。但请注意等待目标元素出现并不代表页面后续的数据都稳定了列表类页面常常是前端分页异步加载你抓完第一屏第二屏还没渲染完。更明智的做法是把关注点从等页面加载完改成等网络响应。Playwright 的page.on(response)可以直接监听网络响应一旦拦到目标 JSON 接口就直接拿到结构化数据完全不需要等 DOM。这个技巧能把单页面耗时从 2~3 秒压到 800 毫秒左右但依然比纯 HTTP 请求慢得多。至于 Selenium 想做类似拦截就得靠浏览器日志或代理复杂度又上一个台阶。方案单页面典型耗时单机可行并发内存占用特征requests 接口分析0.1~0.5s数百线程/协程几百 MBSelenium 多实例2~5s5~10 个实例上限每个实例 300~500MBPlaywright context 池1~3s5~20 个 context单浏览器进程 每个 context 额外开销3. 反爬识别逻辑自动化痕迹其实藏不住3.1 浏览器环境层面的检测点比你想的多很多人以为用了 Playwright、Puppeteer 就等于真实浏览器实际上反爬的 JS 能检测的维度远超你的想象。Selenium 早期版本注入的navigator.webdriver属性是最典型的暴露点后来的 ChromeDriver 可以通过 excludeSwitches 藏掉一部分但 Canvas 指纹、WebGL 参数、AudioContext、window.chrome对象、插件列表、语言时区组合都会暴露自动化特征。Puppeteer 和 Playwright 也会在这些细节上有痕迹比如 CDP 连接、navigator.plugins数组内容的差异。我见过一个项目所有环境指纹检测都改了还是被反爬识别成自动化流量最后排查发现是 WebGL 的渲染结果跟真实用户分布有细微差异。这种问题没有标准补丁只能一个点一个点地打烦不胜烦。3.2 行为模型比环境指纹更难装近两年的反爬方案很多已经不只是判断你是不是浏览器而是判断你像不像真人。鼠标移动轨迹、点击间隔、滚动速度、页面停留时间、请求顺序都会被前端埋点收集。浏览器自动化脚本的交互通常非常模板化鼠标从一点直线移动到另一点点击间隔均匀停留时间死板滚动频率固定。这些行为特征一旦叠加进风险画像很容易形成一个独立于 IP 的自动化流量标签。就算你换了代理 IP只要行为特征不自然照样被限流或弹验证码。目前不少团队在浏览器自动化脚本里加入随机等待、随机滚动、模拟贝塞尔曲线鼠标轨迹目的就是让行为看起来更像人。但这也意味着你的爬虫脚本里混进了一套行为模拟逻辑开发、测试、维护成本持续上涨。3.3 对抗升级是一场无底洞的军备竞赛有自动化检测就有人做对抗检测方也在升级。某天反爬加了一个新的 DOM 埋点你的脚本就得跟着改某天它开始返回假数据你的脚本可能还傻乎乎地抓了半天。市场上有一些专门针对商业反爬产品的讨论但我得说句实话用浏览器自动化去过强反爬本质上是在跟别人的安全团队赛跑赢一次不代表能赢一年。与其把精力耗在这里不如回到接口层去分析真正的参数生成逻辑接口层虽然也有逆向成本但它单位数据成本低、可批量验证、不容易被行为模型误伤。4. 依赖 DOM 和版本的稳定性噩梦4.1 选择器是爬虫脚本里最脆弱的依赖用浏览器自动化爬数据最常用的取数手段还是 CSS 选择器或 XPath。页面元素的 class 改名、DOM 结构调整、React 或 Vue 的渲染节点变化都会让你的选择器一夜之间失效。热词里总会看到playwright 定位元素这类问题答案往往是元素在 iframe 里元素在 Shadow DOM 里元素在懒加载之后。今天写好的定位器明天可能就定位到别的东西更恐怖的是没有立刻报错而是抓回来一批错误数据。Playwright 提供的get_by_role、get_by_text这类语义化定位器能缓解一部分问题因为它不依赖 class 和 id但语义化定位在复杂的业务页面里也不总是可靠。做数据采集我个人的原则是能通过接口拿到结构化数据就绝不要依赖 DOM 结构。DOM 是给人看的不是给程序做数据交换的。4.2 动态加载、iframe、Shadow DOM 叠加出来的不确定性动态站点里最折磨人的是渲染时序。你打开页面脚本以为加载完了但列表数据还在另一个异步请求中你费劲处理完 iframe发现 Shadow DOM 里的节点普通选择器根本抓不到好不容易数据出来了页面又弹了一个遮罩广告把元素位置顶走了。热词里存在scrapy playwright 动态 iframe这类搜索正是因为这些问题太普遍。即便你用 Playwright 的frame_locator处理 iframe、用 pierce 方式处理 Shadow DOM不同浏览器、不同机器上的渲染时序仍然可能不一致。同一个脚本在 A 机器上跑一万次没问题在 B 机器上可能第 200 次就出现一次元素找不到。对爬虫来说这种偶发失败意味着要写大量的重试、兜底、告警逻辑稳定性成本很高。4.3 浏览器版本和驱动依赖的版本地狱Selenium 要做爬虫先得处理 ChromeDriver 和 Chrome 版本的匹配。Chrome 一升级ChromeDriver 没跟上整个脚本就挂了。Puppeteer 和 Playwright 把浏览器下载做进了安装流程比 Selenium 省事但每次升级也要重新下载或匹配浏览器。如果爬虫部署在几台 CentOS 服务器上或者 CI 容器里还可能出现本地能跑线上装不上的问题。遇到过一次容器环境缺系统依赖库Chromium 启动不了排错排了一整天。这种问题在普通 requests 爬虫里根本不存在。5. 从单机 Demo 到分布式采集的扩展性断层5.1 任务调度多了一层浏览器实例资源池普通 HTTP 爬虫做成分布式很简单把 URL 列表丢进 Redis 队列每个 worker 循环取任务、发请求、存结果挂了就重启几乎不需要额外的资源管理。浏览器自动化爬虫则要额外考虑浏览器实例怎么分配、context 怎么复用、页面会话怎么保持。一个 worker 是持有一个浏览器进程还是多个 context任务失败后要不要重开浏览器登录态存在哪个 context 里这些问题会让你的架构复杂度直接翻倍。热词里有s QLAlchemy 储存爬虫数据这类讨论说明很多爬虫项目需要把数据落库。如果采集和入库逻辑耦合在一个浏览器自动化脚本里任务一多就会遇到瓶颈。更合理的结构是上层用 Scrapy 这类成熟的采集框架负责调度、去重、持久化下层把 Playwright 之类的浏览器工具封装成一个渲染服务只负责把页面转成结构化的 HTML 或 JSON数据怎么处理让上层来做。这样职责清晰故障也容易隔离。5.2 故障爆炸半径大于普通 HTTP 爬虫一个 requests 爬虫挂了通常只是当前请求失败重试就行。一个浏览器进程卡死或崩溃影响的是挂在这个进程上的所有任务如果多个 worker 共享浏览器实例故障还会传染。遇到内存泄漏、僵尸进程、端口不释放这些问题时你甚至没办法靠简单的重启任务解决因为浏览器进程不会自己跟着任务结束而退出。我在生产环境的经验是浏览器自动化爬虫必须要有独立的进程守护和资源回收机制每处理完一批页面就主动清理 context定时重启浏览器进程。否则跑上几天系统里的僵尸 Chrome 进程会多到让你怀疑人生。5.3 千万级数据规模下的成本几乎不可接受把账算到千万级页面时浏览器自动化的劣势基本是致命的。假设单页渲染加采集 2 秒单 worker 一小时最多 1800 页一台 64G 内存的服务器最多跑十几个 context一天能处理二三十万页已经算不错了。如果是纯接口爬虫一天处理千万级页面并不夸张。两者在服务器成本、带宽成本、维护成本上的差距非常大。浏览器自动化工具适合的是小流量精抓不适合大流量广撒网。6. 该用的时候怎么用降低弊病的工程化手段6.1 先判断你的场景是不是真的适合浏览器自动化需要明确的是我上面说了一堆弊病不代表 Selenium、Playwright、Puppeteer 在爬虫领域完全没有用。它们合适的场景很清晰小规模、低频的采集需要保持登录态执行真实操作比如下单、发布、查询的任务目标站点接口逆向成本过高、而采集量又不大以及做页面分析和故障排查。在这些场景里浏览器自动化的快速、直观、兜底能力强是值得保留的。一句话总结我的选型逻辑能用 HTTP 请求解决绝不用浏览器必须用浏览器时尽量把流量控制在很小的范围里并且把浏览器只当作渲染器不要让它承担调度、存储、去重这些它不该干的活。6.2 我实践下来比较有效的几个优化手段第一不要每个任务都新建浏览器。用 Playwright 或 Puppeteer 维护一个浏览器实例内部开 5~10 个 context每个 context 独立负责一批页面页面之间顺序访问避免内存暴涨。Selenium 如果实在要用就手动控制 WebDriver 实例数量别无脑开线程。第二用page.route拦截图片、字体、视频、统计脚本等非核心资源。这些资源对抓取页面数据没有价值但会白白消耗带宽和 CPU拦截之后单页面渲染耗时会明显下降生产实测能省 30% 以上的加载时间。第三能监听响应就别等 DOM。Playwright 的page.on(response)可以直接拿到 JSON 接口数据Selenium 做不到这一点可以通过配置代理或复用浏览器 DevTools 协议来弥补但成本很高。这也是我推荐的新项目优先考虑 Playwright 而不是 Selenium 的原因之一。第四把数据持久化拆出去。很多人在爬虫脚本里直接写 SQLAlchemy 同步写入数据库这会进一步拖慢采集循环。正确做法是采集线程只负责生成数据对象放入内存队列单独的后台任务批量异步落库数据库抖动也不会直接影响采集主链路。弊病维度影响表现缓解思路性能差单页渲染耗时高、并发受限拦截静态资源、监听网络响应、context 复用反爬暴露环境指纹、行为模板化随机化行为、保持真实浏览器更新、减少自动化流量稳定性差选择器失效、版本不匹配、偶发渲染失败语义化定位、浏览器定时重启、完整重试框架扩展性差资源池调度复杂、任务故障传染爬虫框架与渲染器分离、进程守护、独立 context 隔离6.3 什么时候应该果断放弃浏览器自动化如果目标站点有公开或半公开的接口层哪怕逆向要花一两周长远看也比浏览器自动化划算。接口层请求轻、可并发、数据结构稳定运维成本低得多。如果目标站点对行为模型和浏览器指纹的检测非常严格浏览器自动化无论如何模拟都可能陷入持续的对抗循环。如果数据规模是千万级往上单位成本会直接告诉你这条路走不通。还有个现实问题是团队的经验偏好。有的团队擅长写 JavaScript用 Puppeteer 做小工具很顺手日常维护没问题有的团队主栈是 Python搞 Scrapy 加 Playwright 集成反而更顺。工具选择要结合团队能力和业务规模来做别因为某个工具热门就无脑上。我在实际项目里最终都是按能用请求坚决不用浏览器的原则做取舍。浏览器自动化工具是工具箱里非常有价值的一把工具但它更适合做操作型自动化而不是量级型采集。拿它当主力爬虫引擎就像拿一把瑞士军刀每天去劈柴活儿确实能干完但绝对谈不上顺手。每次新接采集任务我会先花时间看接口、分析网络请求确认实在拿不到数据了才会请出 Playwright 来兜底并且把它的流量控制在很小的范围内同时备好完整的监控和重启机制。爬虫项目能不能长期跑得稳往往不取决于你用了多炫的自动化工具而取决于你对数据链路的理解有多深。