用 Playwright + CDP 复用已登录 Chrome:多账号会话隔离的 5 个坑
为什么不用 launch,而用 CDP attach
很多自动化脚本习惯 browser.launch(),每次运行都开一个全新的临时 profile:没有登录态、没有已保存的密码,每次都要重新登录一遍。对需要长期维持多个账号登录态的场景(每日例行任务、运营后台巡检、多租户验证),更省事的做法是先让用户手动登录一次,之后用 CDP attach 到同一个 Chrome 实例,直接复用 profile 里的 Cookie。
from playwright.async_api import async_playwright
async def attach():
async with async_playwright() as pw:
browser = await pw.chromium.connect_over_cdp("http://127.0.0.1:9222")
for ctx in browser.contexts:
for page in ctx.pages:
print(page.url, await page.title())
前置条件是 Chrome 带调试端口启动:
google-chrome \
--user-data-dir=/path/to/profiles \
--profile-directory="Profile 1" \
--remote-debugging-port=9222 \
--no-first-run --no-default-browser-check
这里有个容易混淆的点:--user-data-dir 指向的是浏览器实例级目录,--profile-directory 才是实例内部的账号位。两者组合起来,才唯一确定一个登录身份。
坑 1:靠标题区分账号,一定会串号
connect_over_cdp 连上的是整个浏览器,contexts 通常只有一个,所有 profile 的窗口都平铺在同一个 context 的 pages 里。
于是靠 title 区分账号几乎必然失败——同一个站点的两个账号,标签页标题往往一字不差。可靠的做法是从 URL 里找一个每会话唯一、又跟账号绑定的参数:
def pick(pages, marker):
for p in pages:
if marker in p.url:
return p
return None
华为云 SSO 回跳会在 URL 上带 ticket=ST-...,这个 ticket 就是天然的账号指纹。开新 tab 前先读一次 ticket,确认落在哪个账号上,再决定要不要操作。
坑 2:手工造 profile 目录,Chrome 会静默回落到 Default
手动 cp -a Default "Profile 1" 造目录是可行的,但必须同步改 Local State 里的 profile.info_cache,否则 Chrome 不认这个名字,仍然落到 Default:
import json
p = "/path/to/profiles/Local State"
d = json.load(open(p))
d["profile"]["info_cache"]["Profile 1"] = {
"name": "account2",
"is_using_default_name": False,
}
json.dump(d, open(p, "w"), ensure_ascii=False, indent=2)
改完必须完全退出 Chrome(包括后台残留进程)再启动,否则 Local State 会被内存里的旧值覆盖回去。我第一次就是改完直接开窗口,结果两个窗口登的都是同一个账号。
坑 3:同一个 user-data-dir 起不了两个进程
如果想让两个账号"各占一个浏览器进程",会直接失败:Chrome 对 user-data-dir 加了单实例锁,第二个进程只是把 URL 转发给第一个进程然后退出。
所以"两个账号"只能是同一实例的两个窗口,不能是"两个独立的 Chrome"。这一点决定了自动化的写法:收尾不能调 browser.close(),那会把用户正在用的窗口一起关掉;只关自己新开的 tab。
坑 4:登录态落在 profile,不在浏览器
Cookies、Login Data、Local Storage 全部位于 ~/.config/google-chrome/<Profile>/ 下。这意味着三件事:
- 想在 A 账号下做事,就必须在 A 的 profile 窗口里操作;
- 只要 profile 目录还在,重启 Chrome 登录态就还在;
- 反过来,删掉 profile 目录就等于登出,不需要别的清理动作。
顺带记一个反直觉的细节:密码末尾的标点算在密码里。少一个句点,站点返回的是"账号或密码错误",而不是"格式错误",很容易误判成密码泄露或账号被锁。
坑 5:人机验证不要试图自动化
密码验证通过后,站点常会弹滑块拼图。我试过按缺口做边缘检测算出精确偏移、按 DOM 校准拖动比例、模拟人类加减速轨迹——连续九次全部被判失败,还触发了一次"失败过多"。
原因是这类验证判的不只是轨迹,还有环境指纹:无 GPU 的虚拟桌面、--remote-debugging-port 暴露的调试特征、合成事件缺少的真实输入设备属性。在自动化环境里,它本来就是"机器人环境",拖得再准也没用。
正确的处理是换通道,而不是硬刚:
- 优先用邮箱验证码;
- 没有邮箱就用短信验证码;
- 两者都不通,把窗口交回人工,拖一次就过。
同一套登录流程,密码登录必弹滑块,手机号加短信验证码一次就过——这不是运气,是风控策略的差异。
一张 checklist
--user-data-dir与--profile-directory成对使用- 改
Local State前先彻底退出 Chrome - 靠 URL 里的唯一参数识别账号,不靠标题
- 只关自己开的 tab,不动用户的窗口
- 验证环节换通道,不硬拼轨迹
- 每次敏感操作前读一遍页面上的账号标识,确认没串号
小结
CDP attach 的价值在于复用真实登录态,代价是要接受浏览器是共享资源、不能用进程隔离来做账号隔离。把隔离维度从"进程"换成"profile 加 URL 指纹",这套方案在长期运行的任务里是稳的;真正必须人工介入的,只剩人机验证和二次验证码这一步。
- 点赞
- 收藏
- 关注作者
评论(0)