浏览器自动化的第三种形态:常驻 Chrome + CDP,比每次起新进程稳得多

举报
yd_219023509 发表于 2026/10/30 10:41:41 2026/10/30
【摘要】 会话 Cookie 一关进程就没了,反复登录还容易触发风控。这篇讲怎么让 Chrome 常驻、脚本通过 CDP 接管真人登录态,以及 fetch 劫持、CSRF、自定义元素这几个常见坑的解法。

做站点自动化,常见的写法是每次跑一个 launch(),登录、干活、关浏览器。短任务没问题,但一旦要跨多次调用、跨多个站点,就会撞上两个坑:会话 Cookie 一关进程就没了,而且每次都重新登录很容易触发风控。

我现在的做法是让一个 Chrome 常驻,所有脚本通过 CDP 连上去。这篇把具体做法和踩过的坑写清楚。

一、先分清你要的是「启动」还是「接管」

browser.launch() 是启动一个新的浏览器进程,独立的 profile、独立的 Cookie 存储、独立的 session。

connect_over_cdp() 是接管一个已经存在的浏览器,拿到的是你手动登录过的那个会话。

差别在这:

# 每次都要重新登录,而且关掉之后 session cookie 就没了
with sync_playwright() as p:
    b = p.chromium.launch(headless=True)
    page = b.new_page()
    ...
    b.close()   # session cookie 一起没了
# 拿到的是当前正在运行的 Chrome,登录态是真人登出来的
with sync_playwright() as p:
    b = p.chromium.connect_over_cdp("http://127.0.0.1:9222")
    ctx = b.contexts[0]
    page = ctx.pages[0]

第二种形态下,人可以在窗口里扫码登录,脚本立刻就能用;脚本跑完不关浏览器,人还能接着操作。两边交替干活,不用反复登录。

二、启动命令

"C:\Program Files\Google\Chrome\Application\chrome.exe" ^
  --remote-debugging-port=9222 ^
  --user-data-dir="D:\auto\profile" ^
  --no-first-run ^
  --no-default-browser-check ^
  "https://developer.huaweicloud.com/grow"

注意 Chrome 156 之后有个变化:默认 profile 加 --remote-debugging-port 会被直接拒绝,必须显式给一个 --user-data-dir。

--user-data-dir 指向一个你独占的目录,不要指向你日常在用的那个 profile——不然你正常上网时打不开浏览器。

三、连上去之后怎么选页面

一个常驻浏览器可能开了几十个标签页,别直接用 ctx.pages[0]:

# 按 URL 找
pages = [x for x in ctx.pages if "devstation" in x.url]
page = pages[-1] if pages else ctx.new_page()

我用的时候开过 124 个标签页,因为每次调试都新开一个。清理可以直接打 CDP 的 HTTP 接口:

import json, urllib.request
targets = json.load(urllib.request.urlopen("http://127.0.0.1:9222/json/list"))
for t in targets:
    if t["type"] == "page" and should_close(t["url"]):
        urllib.request.urlopen("http://127.0.0.1:9222/json/close/" + t["id"])

不需要真的关掉浏览器,关标签页就够了。

四、页面里的 fetch 被劫持怎么办

有些站点会注入 SDK 劫持 window.fetch 做埋点或代理。这时候在 page.evaluate 里 await fetch(...) 会直接抛异常,堆栈指向一堆压缩过的 sourcemap,根本没法查。

换 XMLHttpRequest 最省事:

() => new Promise(res => {
  const x = new XMLHttpRequest();
  x.open('GET', url, true);
  x.withCredentials = true;
  x.onload = () => res([x.status, x.responseText]);
  x.onerror = () => res([-1, 'network error']);
  x.send();
})

XHR 不走 window.fetch 那条链,劫持不到。

五、登录检测不要只看 URL

跳回首页不等于登录成功。直接打一个需要鉴权的接口:

() => new Promise(res => {
  const x = new XMLHttpRequest();
  x.open('GET', 'https://devdata.huaweicloud.com/rest/developer/fwdu/rest/developer/user/hdcommunitysoservice/v1/growth/bonus-beans', true);
  x.withCredentials = true;
  x.onload = () => res([x.status, x.responseText]);
  x.send();
})

返回 200 且 body 不是 not login,才是真的登录了。

把这个探测挂成一个轮询,每 2 秒一次,一旦通过就立刻干活:

while time.time() < deadline:
    status, body = page.evaluate(PROBE)
    if status == 200 and body != "not login":
        break
    time.sleep(2)

这就是「用户扫码,脚本等着接管」的实现方式。

六、CSRF 要先取再带

很多网关需要先 GET 一个接口,从响应头里拿 token,再塞进后续每个请求的同名请求头。

const x = new XMLHttpRequest();
x.open('GET', 'https://devdata.huaweicloud.com/api/get-ainfo', true);
x.withCredentials = true;
x.onload = () => {
  const csrf = x.getResponseHeader('csrf') || '';
  // 后续每个请求都带 csrf 头
};
x.send();

注意这个响应头默认对前端不可见,服务端必须在 Access-Control-Expose-Headers 里把它列出来,否则 getResponseHeader 拿到的是 null。

七、SPA 不要用 networkidle

很多站点有长连接或者轮询,networkidle 永远等不到。用 domcontentloaded 加显式等待:

page.goto(url, wait_until="domcontentloaded", timeout=60000)
page.wait_for_timeout(3000)          # 或者 wait_for_selector

八、自定义元素的文字抓不到怎么办

现在不少站点用 Web Components,按钮可能是 <xhs-publish-btn> 这种自定义元素,文字在闭合的 shadow DOM 里,get_by_text 抓不到。

两个办法:

# 1. 直接按标签名点
page.locator("xhs-publish-btn").first.click(force=True)

# 2. 按坐标点,先滚进视口
btn.scroll_into_view_if_needed()
box = btn.bounding_box()
page.mouse.click(box["x"] + box["width"] / 2, box["y"] + box["height"] / 2)

弹窗里的按钮容易在视口外,必须先 scroll_into_view_if_needed 再点坐标,不然点的是别的东西。

九、最后

自动化真正的难点不在元素定位,在状态管理。

会话什么时候会丢、登录态谁负责维护、Cookie 要不要落盘、脚本和真人怎么交替用同一个窗口——这些想清楚之后,剩下的都是体力活。

把 Chrome 当成长驻资源,用 CDP 接管而不是每次启动,是我现在最省心的形态。登录一次,后续所有任务复用同一份状态,风控触发频率也低得多。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。