依据 `DevDocs/reference/chromium-cdp/`(9 份实测+官方源码):旧姿势传 `--remote-debugging-port=0`
会被 Chromium 当成 ChromeDriver 式自动化启动 ⇒ 主动打开 `AutomationControlled`
⇒ `navigator.webdriver=true`;那个 bad-flag 是承重覆盖,但代价是**每次有头弹横幅**。
改用显式端口后 `webdriver` **本来就是 false** ⇒ flag 变纯冗余 ⇒ 去掉它,**两者兼得**。
- `launch.go`:`--remote-debugging-port=<我们选的端口>` 替掉 `=0`;**删掉**
`--disable-blink-features=AutomationControlled`(注释写明理由)。
- 新增 `portrecord.go`:端口来源与落点的**唯一出处** —— 取空闲端口(`net.Listen`
取号后立刻关闭,**本进程不常驻监听**)、写/读/清**我们自己的记录**
`<profile>/cyrene-debug-port`(选它与 Chrome 的 `DevToolsActivePort` 同目录:
天然按 profile 隔离、profile 换掉记录自动消失、内容只有端口号不含凭据);
**兼容老记录**(换二进制时上一版起的浏览器可能还在跑 ⇒ 认不出会把"自己的浏览器"
误报成"profile 被别人占了");就绪判据换成「我们选的那个端口答得上 `/json/version`」;
TOCTOU 处理 = 端口被占**换号重试一次**,重试仍失败则如实报错且不留记录。
- 三条认领路径(复用/清陈记录/如实报错保留现场)与三条失败分类(进程立刻退出 /
等端口超时 / 预算耗尽)**全部保留**;`ErrLaunch` + `errors.Is` 口径未变;
**绝不杀进程**红线未变。
真机自证(子代理跑,默认 Skip 的 `realchrome_test.go`,`CYRENE_BROWSER_CDP_E2E=1` 开闸):
`webdriver=false` · 命令行**无** flag/`--enable-automation`/`--headless` · UA 无
`HeadlessChrome`、`plugins=5`、`languages=zh-CN,zh`、`window.chrome` 在 ·
**UI Automation 控件树 17 个命名节点里没有「信息栏容器/信息栏」⇒ 无横幅** ·
第二次 Dial **复用同端口、浏览器主进程仍是同一个 PID** · 收尾按 profile 路径筛残留 = 0。
验收(主会话复跑 + 亲自变异):整树 `go build ./...` + 两包 vet 绿;browser 包测试绿;
我用 -overlay 把该 flag 加回参数表 ⇒ `TestLaunchArgs_HeadfulCarriesNoAutomationTraces`
变红(逐字报"不许传的 flag")。
⚠️ 未验(如实):无头档的横幅(无窗口判不了)、`chrome-headless-shell`、风控侧是否把
"端口写法"本身当信号、生产 profile 整链路(未碰)。
43 lines
3.0 KiB
Go
43 lines
3.0 KiB
Go
// Package browser 是 design/28(浏览器与页面截图)B2 的自研**薄层**:把「本机真
|
||
// Chromium 有头」跑起来,对它做**出站 CDP** 调用,并把页面截图按 0600 落到
|
||
// `$DATA_DIR/browser/shots/`,供既有 image/vision 管线读取。
|
||
//
|
||
// 为什么这么薄(AGENTS §9.5):
|
||
//
|
||
// - **工具面优先复用 MCP**:`goto` / `click` / `type` / `snapshot` / `extract`
|
||
// 这一整套成熟动作由 Playwright/Puppeteer 的官方 MCP server 提供(design/28 §0
|
||
// 「Playwright MCP / Puppeteer MCP」行、§2 结论);本包**不重造**它们。
|
||
// - **本项目独有的价值只有两处**:① 宿主真 Chromium 的**默认有头 + 持久 profile**
|
||
// 启动(design/28 §2 的过风控三件套);② 截图 → 落盘(0600)→ 进上下文/面板的
|
||
// 闭环入口。本包只做这两处,外加够它们用的**最小 CDP 客户端**。
|
||
//
|
||
// 运行形态(design/28 §2「选型修正」的主路径):
|
||
//
|
||
// 1. **本机真 Chromium 有头**(默认档,`browsercfg.DefaultHeadless=false`):
|
||
// 真显示 + 真 GPU/字体/时区,指纹最像真人;
|
||
// 2. **次选:已跑的 CDP 端点**(容器 + Xvfb + 真 Chromium,配置显式给出):
|
||
// 本包只**出站连接**它。
|
||
//
|
||
// 红线(design/28 §3/§4、AGENTS §9/§10):
|
||
//
|
||
// - **不新开对外监听端口**:调试端口是**我们选号、Chromium 自己绑在 `127.0.0.1`** 的
|
||
// 显式端口(`--remote-debugging-port=<port>`,端口号由 `listen(:0)` 借内核挑一个空闲号
|
||
// 后立刻放掉,并记在 `<profile>/cyrene-debug-port` 里供下次复用,见 portrecord.go)——
|
||
// 本包**不持有任何监听端口**,始终是**出站连接方**;容器路径同理(我们连它)。
|
||
// 为什么不传 `--remote-debugging-port=0`:Chromium 会把「端口=0」当作 ChromeDriver 式
|
||
// 自动化启动、主动点亮 `navigator.webdriver`(reference/chromium-cdp/00 §2)。
|
||
// - **profile 含凭据**(cookie / 登录态):目录 0700、不得位于 git 工作树内
|
||
// (`browsercfg.EnsureProfileDir`),内容绝不进上下文与长期记忆。
|
||
// - **截图可能含凭据/二维码**:文件 0600、默认不进长期记忆(design/28 §4)。
|
||
// - **CDP 层不自曝**:本包**不发** `Runtime.enable`(主流反爬用它判定「被自动化
|
||
// 控制」,见 `DevDocs/reference/browser-stealth/06`);`Runtime.evaluate` 一律走
|
||
// `Page.createIsolatedWorld` 出来的执行上下文。
|
||
// - **失败如实报**:探测不到就不注册工具;跑不起来 / 被拦 / 超限都返回**分类原因**,
|
||
// 绝不假装成功(design/28 §6 坑 5)。
|
||
//
|
||
// 已知未做(本片如实登记,见交付报告):
|
||
// - 面板「看一眼」入口(B4)、搜索降级档(B5)未做;
|
||
// - 除「导航 + 截图 + 页面指纹自检」之外的动作(click/type/extract/wait)**交给 MCP**,
|
||
// 本包不实现。
|
||
package browser
|