用 CDP 驱动真实浏览器做日常运维自动化:一次 CloakBrowser 实践

举报
yd_230653297 发表于 2026/11/03 02:20:02 2026/11/03
【摘要】 把浏览器当成有状态的执行器,而不是无状态的 HTTP 客户端:用 CDP 常驻实例接管扫码登录后的网页操作,抓接口代替点按钮,Cookie 备份保活,附五个实际踩过的坑。

背景

家里的实验环境里跑着 PVE、若干 LXC 容器、几十个自建服务。日常巡检大部分能用 curl 和 systemd 解决,但总有一类事情卡住:只有网页能做的操作——网站签到、社区任务、控制台配置、需要扫码登录的后台。

传统做法是写 Playwright 脚本。问题在于:

  • 网站风控识别 `navigator.webdriver`,扫码直接提示"设备不安全";
  • 会话只活在前端 JS 里,脚本重启就掉登录态;
  • 一旦遇到验证码或二次确认,脚本就死在那里。

这篇记录一个更省事的思路:让一个"真浏览器"常驻桌面,agent 通过 CDP 接管它。

一、架构:常驻浏览器 + CDP 端口

CloakBrowser(Chromium 内核,带 humanize 与指纹噪声)以 headed 模式跑在 Xvnc 的 :1 桌面上,同时开放调试端口:

DISPLAY=:1 node cloak_remote.mjs --remote-debugging-port=9222

关键点:

  1. **必须 headed**。headless 模式下指纹特征明显,扫码类风控直接拒。
  2. **端口只监听 127.0.0.1**。Chromium 的 `--remote-debugging-address` 实际不生效,要对外暴露得用 socat 转发:`socat TCP-LISTEN:9223,bind=0.0.0.0,fork TCP:127.0.0.1:9222`。
  3. **用 systemd 托管**。`Restart=always` + `Environment=DISPLAY=:1`、`HOME`、`XAUTHORITY` 三个环境变量,缺一个就是 `Missing X server` 闪退。

一个常见的坑:桌面快捷方式用的是 demo 脚本,不开 CDP。看着浏览器在跑,curl /json/version 却是空——因为进程用的是 --remote-debugging-pipe。确认方法:

ss -tln | grep 9222
pgrep -af 'remote-debugging-port'

二、为什么不用 requests 直调接口

把浏览器 Cookie 导出来,用 Python requests 直连接口,是很多人的第一反应。实测并不总行得通:某次带全 64 个 Cookie + 自定义 csrf 头请求,服务端返回 403 not login。

原因通常不是 Cookie 没带,而是:

  • 会话藏在 HttpOnly Cookie 里,导出时容易漏;
  • 服务端校验了非 Cookie 的因素(TLS 指纹、WAF 会话、请求头顺序);
  • csrf token 每次页面加载轮换,和导出的 Cookie 不是同一代。

结论:能脱离浏览器的场景优先脱离(省资源),但只要服务端把会话绑死在浏览器上下文,就在页面里 fetch,把 Origin、Cookie、csrf 全部交给浏览器自己解决:

const csrf = document.cookie.split('; ')
  .find(x => x.startsWith('csrf-eco=')).split('=')[1];

await fetch(api, {
  method: 'POST',
  credentials: 'include',
  headers: { 'csrf': csrf, 'Content-Type': 'application/json' },
  body: JSON.stringify(payload)
});

这一段在 CDP 里用 Runtime.evaluate 执行,awaitPromise: true 就能直接拿到结果。

三、抓接口而不是点按钮

页面上的"去完成""立即签到"按钮,背后都是一个 REST 请求。与其用坐标点击(脆弱、容易被浮层挡住),不如先抓包:

// 注入 hook,记录所有 fetch/XHR 的 url、headers、body、response
window.__reqs = [];
const of = window.fetch;
window.fetch = function (input, init) {
  const p = of.apply(this, arguments);
  p.then(async r => window.__reqs.push({
    url: String(input.url || input),
    method: init?.method || 'GET',
    headers: Object.fromEntries(new Headers(init?.headers || {})),
    body: init?.body, resp: (await r.clone().text()).slice(0, 800)
  }));
  return p;
};

手动点一次按钮,完整的请求就落进 window.__reqs。之后写脚本只需要复现这一个请求,不再依赖 DOM 结构。

这里要注意:hook 只活在当前 document。页面一刷新就没了,需要重装的必须走 Page.addScriptToEvaluateOnNewDocument,在文档创建前注入。

四、会话保活:定时导出 Cookie

常驻浏览器最大的隐患是临时 profile。Playwright 的 launch() 用的是 /tmp/playwright_chromiumdev_profile-*,进程一退,profile 连同登录态一起被删。

两条建议:

  1. 需要长期登录态,就用 `launchPersistentContext` 指到固定目录;
  2. 至少在每次任务执行后导出一次 Cookie 备份:
cookies = await page.send("Network.getAllCookies")   # 含 HttpOnly,比 document.cookie 全
json.dump({"saved_at": now, "cookies": cookies}, open("cookies.json", "w"))
os.chmod("cookies.json", 0o600)

配合 cron 每几小时跑一次,既不打扰日常使用,也能在浏览器重启后有一份可回滚的凭证。

五、几个踩过的坑

现象 真因 处理
浏览器一闪就没 `/tmp` 是 tmpfs 且被塞满 `df -h /tmp`,清理后再启动
systemd 起不来,status=203/EXEC ExecStart 里的 node 路径不存在 写绝对路径 `which node`
服务 `Missing X server` 没有 `XAUTHORITY`/`HOME` 补 Environment
CDP 连上但页面不动 用的是 pipe 模式实例 改用 `--remote-debugging-port`
点击按钮无反应 同名节点里有个 `display:none` 的模板 过滤 `offsetParent != null`

小结

把浏览器当成一个有状态的执行器,而不是无状态的 HTTP 客户端,很多"只能人工点"的流程就能自动化:登录态天然复用、风控不需要绕、验证码留给人工扫一次即可。

真正的分界线在于:这一步的凭证是 Cookie,还是浏览器上下文。前者可以脱离,后者就老老实实在页面里跑 JS。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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