温嘉琪的博客 / BUILDING SOMETHING FUN

Playwright 多进程:我的理解哪里错了

我当时想做一件事:用两个 Playwright 进程,同时控制同一个账号,各自做不同的操作。

这个想法背后有一个隐含的假设:只要两个进程都挂载了同一个 userDataDir,它们就各自独立地持有了那个账号的"登录状态",可以并行工作。

这个假设是错的。但我一开始没意识到它错在哪。


我当时的心智模型是这样的:userDataDir 里面存着 cookie、localStorage、各种认证 token,相当于"这个账号的凭证集合"。两个进程各自读取这个凭证集合,就像两个人拿着同一把钥匙的复印件,各自去开门。

这个模型在某些场景下是对的——比如你导出一份 cookies.json,发给另一台机器,那台机器确实可以用这份 cookie 以同一个账号登录。"凭证可以复制"这件事本身没有问题。

但 userDataDir 不是凭证的导出格式,它是一个浏览器实例的运行时状态。


Chromium 在启动的时候会对 userDataDir 加文件锁。第二个进程试图挂载同一个目录,会直接被拒。

我当时的第一反应是:这是 Playwright 的限制吗?

不是。是 Chromium 的有意设计,再往下是操作系统文件锁的基本原理。userDataDir 里不只有静态的认证凭证,还有浏览器运行时的大量动态状态:IndexedDB、Service Worker 注册表、HTTP 缓存、session storage……这些东西如果被两个进程同时读写,会出现写写冲突,导致数据损坏。文件锁是防止这件事发生的机制,不是多此一举。

"两个进程挂载同一个 userDataDir"这件事在根本上就不成立,不是权限问题,是架构问题。


认识到这一点之后,问题就变了:我要的不是"共享 userDataDir",我要的是"两个操作都以同一个账号身份执行"。

最简单的办法是一个进程开两个 Page。同一个 BrowserContext,两个并发的标签页,共享同一套 cookie 和认证状态,各自执行不同的操作。进程和任务之间没有 1:1 的关系,一个浏览器进程开几十个标签页是很正常的事。大多数情况下这就够了。

另一种是导出 session,两个进程各自加载。先用一个进程登录,把 cookies 导出成 JSON 文件,第二个进程用独立的 userDataDir 启动,然后把那份 cookie 加载进去。两个进程都持有同一个账号的认证凭证,但各自有独立的运行时状态,互不干扰。代价是:某些服务不允许同一账号在两处同时登录,会踢掉其中一个——这是服务端的策略,跟 Playwright 无关。


这个错误背后是把两件不同的事混在一起想了:

认证凭证(cookie、token)是可以序列化、复制、传递的东西。userDataDir 是一个正在运行的浏览器实例独占的状态,不是凭证的存储位置,是运行中的浏览器本身。

我以为 userDataDir 是一把可以复制的钥匙。它其实更像是一个进程的内存,只能有一个进程持有它。