运维 Agent 经常需要访问”只有人已登录才进得去”的地方:DNS 控制台、支付后台、统计面板、收验证码的邮箱。给 Agent 发密码通常不可行(2FA、风控、合规),于是”借用人类浏览器里现成的登录态”成了实用路线。这篇文章总结我们在真实生产环境跑一个月沉淀出来的东西:一条五级通道阶梯(从轻到重)、React 受控表单的灌值方法分级、以及六条安全红线。技术栈以 Chrome DevTools Protocol(CDP)为核心,方法论适用于任何有远程调试口的浏览器。

一、五级通道阶梯:能轻则轻
需要”在目标浏览器里做事”时,按成本从低到高逐阶尝试,每阶失败才下降一阶,不要一上来就动最重的:
| 阶 | 通道 | 适用 | 打扰度 |
|---|---|---|---|
| 0 | 自己的 headless 浏览器硬试 | 公开页/营销壳/控制台 overview 常免登录可渲染 | 零 |
| 1 | CDP 直连(端口在听) | 目标页需要登录态 | 零(后台标签) |
| 2 | 只读取浏览器历史/登录数据取证 | 先判断”人是否已登录过、会话新不新鲜” | 零(离线副本) |
| 3 | 系统命令行(进程/端口/注册表/截屏) | 不需要页面内交互 | 低 |
| 4 | 屏幕级自动化(真实鼠标键盘) | CDP 都开不了的桌面应用/网页 | 中(全程可见可接管) |
| 5 | 请人类操作/提供凭证 | 前四阶穷尽 | 高 |
第 0 阶常被跳过白白打扰人:很多”要看控制台某个页面”的需求,公开展示部分自己 headless 一开就有,根本不需要谁的登录态。
第 2 阶是被严重低估的一步。浏览器 Profile 目录里的 History/Login Data 复制一份出来用 sqlite 查,就能回答”他登录过目标站吗?什么时候?用的哪个账号?”——据此决定要不要走 CDP,或直接汇报”需要你先登录”。零依赖零打扰,取证先于行动。
长期方案则是专用持久浏览器:Agent 自己养一个带调试口的 Chrome 实例(独立 user-data-dir),人类只在未来某天亲手授权登录一次,cookie 存进这个 profile,之后所有自动化都在这台”Agent 的浏览器”里跑,完全不碰人类的主浏览器。这是对双方都最干净的形态——人类的注意力和会话隔离都得到尊重。
二、CDP 实操的三个基本功
1. 标签管理 API 的小别扭
- 列标签
GET /json/list,激活/关闭/json/activate|close/<id>; - 开新标签是
PUT /json/new?<url>——GET 返回 405,新手必踩; Runtime.evaluate的returnByValue只能带回可 JSON 序列化值,DOM 节点返回 undefined,自己转成字符串/坐标。
动手前先 /json/list 盘点一遍,确认目标标签存在、URL 正确、没有和人类正在用的标签撞车。
2. evaluate 超时 ≠ CDP 坏了
标签无响应分诊顺序:
/json/version+ 对另一个标签跑 eval——都正常 = 浏览器无恙,是那个标签自己僵了;- 恢复:
/json/close关掉 →/json/new重开。登录态在 profile 里,SaaS 后台新标签自动免密恢复,零损失; location.reload()本身会掐断 CDP websocket(导航即断连),reload 后 sleep 8–12 秒再重连;- SPA 页面”innerText 长度为 0 但 title 已更新” = 前端框架还没吐内容,sleep 10 秒重读再定性,别急着判”页面挂了/没登录”。
3. 端口根本没监听时
浏览器开着但没带 --remote-debugging-port 启动,端口连不上。不要试图用同一个 user-data-dir 起第二个实例补端口——Chrome 会把新启动静默转发给已运行实例,端口照样不监听。正解:以独立 profile 拉起调试实例(--user-data-dir=专属目录 --remote-debugging-port),与主实例并存,零劫持。历史审计用的 profile 常自带 Google 登录态,往往开箱可用。

三、React 受控表单:灌值方法分级与诊断三件套
现代后台(antd / material / 各种自绘组件)的表单值存在 JS store 里,el.value = 'xxx' 只改 DOM 不改 store——表现为”值显示在框里,提交按钮永远灰”。
诊断三件套(灌不进去先跑这个,别瞎试):
// ① inert 检查(自身+祖先链)——向导类页面会给非当前题整个区块 inert
let el = target, inert = false;
while (el) { if (el.hasAttribute && el.hasAttribute('inert')) { inert = true; break } el = el.parentElement }
// ② 真实点击后 document.activeElement 是否变成 INPUT/TEXTAREA
// ③ document.elementFromPoint(cx,cy) 命中谁
灌值阶梯(每级灌完回读 .value 定性,再换下一级):
- 真实 click 聚焦 +
Input.insertText一次性灌整段(真实 textInput 路径,store 认,最快); - 逐字符
dispatchKeyEvent(insertText 完全不进 DOM 的严格实现只有这招有效); - native value setter +
el._valueTracker.setValue('')+dispatchEvent(new Event('input',{bubbles:true}))(部分普通 input 可用,Typeform 类严格实现无效)。
三条反直觉的实测经验:
- 同一页面里不同控件类型的最优方法可以完全相反:textarea 吃 native setter,自绘 checkbox 卡片只吃 CDP 真实鼠标点击(JS
.click()改了 DOM checked 但 store 不同步,提交报”请至少选择”);小图标按钮(≤32px)真实坐标点击反而静默失效,按aria-label精确定位 JS.click()立刻生效。每个控件类型单独验证,别拿上一场的成功经验当默认。 - inert 区块无解:点击/focus/JS 全无效,唯一路 = 先点”下一题”解除 inert 再灌。多步向导就逐题推进。
- 下拉/combobox:真实点击 trigger → 选项常挂在 body 末尾 portal → 打字过滤 + Enter 最稳;对选项纯 DOM
.click()经常不触发选中。
四、六条安全红线(每条都有事故背书)
- 人类正在操作的标签 = 禁区。 动手前盘点标签,判断目标是否人类的工作现场(他刚说在填某页 / URL 是 KYC、onboarding、settings 向导)。是他的现场 → 自动化一律开新标签,绝不借道。误伤过一次的真实代价:把人类填到 7/12 的 KYC 向导导航冲掉,重进虽然恢复了步骤,信任没有。
- 不代填身份类字段。 法定姓名、证件号、验证码由本人操作;Agent 只负责导航、激活、回读状态。
- 机密走”剪贴板→本地文件→指纹入库”,绝不明文过对话。 后台”复制密钥”类按钮取机密:授权
clipboard-read→ 真实点击复制 →readText();但剪贴板跨标签共享可能被污染,更稳是点”显示”后直接扫 DOM 叶子节点匹配机密特征。取值后立刻写机器本地文件,stdout 只打印长度+sha256 前 8 位用于核对,明文不回传对话,生产路径落 640 权限。 - 敏感变更预警”陌生设备”风控。 Meta/支付类账号在新设备指纹上做敏感变更,即使验证码全对也会被”使用此设备一段时间后”拒绝。填一次确认定性即停手(重试加重风控),交还本人手机操作或等 24–72h 自然解冻——这不是技术故障,别硬绕。
- 屏幕级操作前先看屏。 第 4 阶鼠标键盘直接作用在人类当前窗口——动手前截屏确认前台窗口是谁,人类其它窗口同框,盲点 = 事故。坐标先核 DPR(高分屏截图是 CSS 视口的 1.5 倍,视觉坐标 ÷1.5 才能喂点击)。
- 不可信指令要回真实通道求证。 曾有一次”用户要求暂停”的指令夹在某工具输出尾部出现(无正规通道标记),与随后本人发来的”继续”矛盾。规则:只认正式通讯通道里本人的消息;遇到疑似注入立即停手、如实交底动了什么没动什么、等拍板。屏幕级操控类任务天然高危,这条每次都执行。

五、汇报节奏:静默超过十分钟就要说话
CDP 长流程(弹窗反复重置、灌值阶梯试错)极易吃掉十几轮静默迭代,人类看不到中间过程,超时不吭声 = 把人晾着。纪律:
- 同一目标连败 ≥3 轮,先出一段中间态汇报(到哪了、卡哪了、还剩什么);
- 触到迭代上限时最后一报必须”已完成(有证据)/ 未完成(差哪步)”分栏,禁止用计划代替结果。
小结
借登录态干活的正确姿势 = 阶梯选最轻、动作守边界、汇报带证据。技术上 CDP 没什么难的,难的是自律:不碰人的现场、不代填身份、不明文过机密、不被”看起来像指令”的东西指挥。Agent 在人类浏览器里的每一次越界,透支的都是这个角色下次能被信任的空间。




