从 Selenium 到 Playwright:我的浏览器控制切换之旅
最近在折腾一个远程浏览器自动化的项目,最开始是用 Selenium 控制浏览器执行一系列开锁流程,后来因为种种问题,最终还是换成了 Playwright。这里简单记录一下整个过程中遇到的一些坑、思考和切换过程。
初始方案:Selenium 控制浏览器
一开始整个系统的架构是:
- 多线程管理浏览器池(初始化多个浏览器放进浏览器池)
- 每个浏览器执行任务:打开页面 → 登录 → 操作页面 DOM
- 控制流程主要使用
find_element
,click
,send_keys
等操作
Selenium 用着也没啥大问题,配合 Chrome + chromedriver(chromedriver版本很重要),流程基本能跑通。但随着任务量增加、并发变多、页面交互越来越复杂,问题也逐渐暴露出来:
- 有时候页面切换 iframe 很慢,Selenium 报错找不到元素
- 弹窗不稳定,处理逻辑需要各种
try/except
- 多线程下的浏览器状态管理很混乱,有时线程没结束浏览器还开着
于是我开始思考,有没有更现代、更轻量的替代方案?
Playwright 更香?
Playwright 这个名字其实早听说过,一直没仔细研究。这次真碰到实际问题了,就开始试着改一下逻辑,把 Selenium 整体替换成 Playwright。
我用了它的 同步模式(sync_playwright()
),主要是因为现有代码结构基于同步逻辑,异步改起来太麻烦。
改造的重点包括:
- 启动浏览器流程:从
webdriver.Chrome()
换成p.chromium.launch(...)
- 页面控制逻辑:换成 Playwright 的
locator
,click
,fill
,frame()
等 API - 最关键:不再直接返回
page
对象,而是封装成LockIndex.enter()
返回 browser,保持浏览器常驻
最大的坑:Playwright 不能跨进程传 browser 对象
这点一开始没注意。在 Selenium 模式下,浏览器 driver 可以传来传去(虽然也不推荐)。但 Playwright 的 browser 对象不能跨进程传递,哪怕你 pickle 了也会报错。
我原本在主进程调度任务,然后把 browser 对象传过去执行操作,结果直接报错。
于是我重构了架构:
新方案:多进程 + Playwright 控制
最终的架构大致如下:
- 主进程 管理多个任务队列(一个队列对应一个浏览器进程)
- 每个子进程 启动自己的浏览器,进入锁控页面,接收任务 → 执行 → 回传结果
- 调度器 轮询分发任务,比如当前是第 0 个浏览器空闲,就发给它
def dispatch_task(task):
task_queues[current_index].put(task)
current_index = (current_index + 1) % len(task_queues)
任务本质上是“给某个 code 和 id 做一次 DOM 操作”。浏览器常驻,执行完一个任务继续待命。
这个结构的好处:
每个浏览器独立管理,状态更清晰
多进程隔离,互不干扰
Playwright 控制页面的效率、容错能力都明显强于 Selenium
最后一点感慨
其实如果不是这次遇到实际需求,我也不会下定决心从 Selenium 迁移。现在的架构逻辑更清晰了,浏览器也更稳定了,维护起来也更方便。
小结
- 如果你还在用 Selenium 控制浏览器,建议试试 Playwright。
- 使用多进程时,不要跨进程传 page、browser 对象。
合理设计任务调度和进程架构,能让系统更稳定。
希望这篇文章能帮到也在做浏览器自动化的朋友们。如果你也在做类似项目,欢迎留言交流。