进阶 / Advanced

单次成型脚本

逐条命令驱动浏览器时,模型卡在循环里:每走一步都要读结果、想下一步、再发一条命令。 chrome-use script 把整个观察、判断、动作、验证流程一次交给守护进程执行, 决策逻辑贴着浏览器跑,一次 tool call 拿回结构化结果。两种写法:JSON op-list(可机器生成、可 dry-run) 和真正的同步 JScu.* 助手函数)。实测 HN 翻 3 页采集高分故事,从 13+ 次调用降到 1 次。

逐条命令的循环有多贵

chrome-use 的基本用法是一条命令一个动作:snapshot 看页面,click @e8 点按钮, eval 读数据。每条命令都是一次完整往返:进程启动、Unix socket 到守护进程、扩展 relay 到浏览器、 结果原路返回,然后 agent 的模型读输出、决定下一步。单步任务这样没问题,代价在多步流程上:翻页采集、 按条件重试、拿上一步结果决定下一步,模型被迫留在循环里,每一步花一次推理,每一步一次 tool call

固定的动作序列已经有 batch 可以一次往返跑完(press --holdwait 在守护进程内阻塞,时序精确),但 batch 的步骤之间不能传值,也没有循环和条件。script 补的就是这一层。

🪜 四层阶梯
单条 verb(一步一往返)→ batch(固定序列,一次往返)→ script(步骤间传值 + 循环 + 条件 + 断言,一次往返)→ stream WebSocket(持续实时驱动)。往下每一层能表达的逻辑更多, 往返和 tool call 更少;选到刚好够用的那层就行。

JSON op-list 形式

第一种写法是 JSON 语句数组,适合让模型直接生成、让程序拼装。后面的 op 通过 {{name.path}} 值总线读前面 op 的结果:

bash
chrome-use script - <<'JSON'
[
  {"do":"navigate","url":"https://example.com"},
  {"do":"evaluate","script":"document.title","bind":"t"},
  {"assert":{"contains":["{{t.result}}","Example"]},"msg":"wrong page"},
  {"return":"{{t.result}}"}
]
JSON

语句一共八种(cli/src/native/script.rs):

{ "do": "<action>", ...params, "bind"?: "name", "optional"?: bool }   // 跑一个命令,结果存进变量
{ "waitUntil": <cond>, "timeoutMs"?: n, "pollMs"?: n }                 // 轮询到条件成立
{ "forEach": "{{list}}", "as"?: "item", "max": n, "body": [ ... ] }   // 循环(max 必填,硬上限)
{ "assert": <cond>, "msg"?: "..." }                                   // 断言,失败即止损(退出码 1)
{ "set": "name", "value": <v|"{{expr}}"> }                            // 赋值
{ "push": "name", "value": <v|"{{expr}}"> }                           // 往数组追加
{ "return": <v|"{{expr}}"> }                                          // 返回并结束
{ "log": <v|"...{{expr}}..."> }                                       // 记一行进度

条件(<cond>)有 {"eq":[a,b]}{"ne"}{"contains"}{"matches":[a,rx]}{"exists":"<selector>"}{"gone"}{{expr}} 值总线:整串就是 {{x}} 时取到原类型的值(数字还是数字),嵌在文本里 ...{{x}}... 时做字符串插值;路径带下标,如 rows.0.refrows[0].url

💡 提示
evaluate 的返回值裹在 .result 里,所以引用页面求值结果写 {{t.result}} 而不是 {{t}}。 用 --dry-run 只校验程序结构、不碰浏览器;用 --arg k=v 预置变量。退出码:0 成功、1 运行失败或断言不过、2 程序非法。

JS 程序形式

第二种写法是一段真正的 JavaScript,用 cu.* 助手函数直接驱动你已登录的真 Chrome。 这是 ego-lite 的 code base 写法,控制流就是 JS 本身的 for / if / break不用 await:每个 cu.* 调用会阻塞到浏览器返回,脚本是同步的。

bash
chrome-use script --timeout 120000 <<'JS'
  cu.open('https://news.ycombinator.com');
  const out = [];
  for (let page = 0; page < 3; page++) {
    const rows = cu.eval("[...document.querySelectorAll('.athing')].map(r=>({id:r.id,title:r.querySelector('.titleline a')?.innerText}))");
    for (const r of rows) {
      const pts = cu.eval(`+(document.querySelector('#score_${r.id}')?.innerText.match(/\\d+/)?.[0] ?? 0)`);
      if (pts >= 100) out.push({ title: r.title, points: pts });
    }
    if (!cu.visible('a.morelink')) break;
    cu.click('a.morelink');       // 整页硬跳转,脚本状态不丢
    cu.waitFor('.athing', 8000);
  }
  return { count: out.length, top: out.slice(0, 5) };
JS

助手函数一览:cu.snapshot / cu.eval / cu.open / cu.click / cu.fill / cu.type / cu.press / cu.hover / cu.select / cu.check / cu.scroll / cu.find / cu.visible / cu.waitFor / cu.wait / cu.extract / cu.text / cu.logcu.eval 直接返回页面里的值;其余失败即抛异常,脚本快速失败。程序的返回值就是命令的返回值,cu.log(...) 收集进度行。

引擎跑在守护进程里

ego 也能写一段 JS 跑完整个任务,chrome-use script 反超它的点在引擎的位置

扛得住硬跳转。注入到页面世界的运行时,页面一跳转整个执行环境就没了,多页流程正好死在翻页那一下。 chrome-use 的 JS 引擎跑在守护进程里、页面外面:上面例子里 cu.click('a.morelink') 触发整页导航, 脚本状态(out 数组、循环计数)原地不动,等 cu.waitFor 确认新页加载完继续跑。

纯 Rust 引擎,全平台。JS 引擎是 boa,纯 Rust 实现,编译进 chrome-use 二进制本体, 不依赖 Node 也不依赖系统 V8。macOS / Linux / Windows 都能跑;ego 只有 macOS。

每个 op 走同一条正门。script 的每一步(两种形式都是)重新进入守护进程的 execute_command,和你手敲单条命令走完全相同的路径:策略检查、隐身传输、--humanize@ref 自愈、跨进程会话恢复。没有任何东西被重新实现,所以脚本里的行为和命令行里一模一样。 整条链路怎么走见 它怎么工作

实测:HN 翻 3 页

基准任务:Hacker News 翻 3 页,收集所有 ≥100 分的故事,输出 JSON。上一节的 JS 程序就是完整题解, 已在真 Chrome 上跑通(3 页、34 条、约 4.8 秒)。

逐条 verbchrome-use script
tool call 次数13+1
模型推理次数每步一次,翻页、逐条判分都要回模型2 次:写程序 + 读结果
中间输出每步的 snapshot / eval 结果都进上下文只有 return 的最终 JSON
翻页(硬跳转)天然没问题(每步独立)没问题,引擎在守护进程里

「≥100 分」这类判断在脚本里用一行 if 解决,旧写法里每条故事都要把分数带回模型判一次。 步数越多、判断越密,差距越大。全景对比和 ego-lite 逐项实测见 对比其他方案

该用哪一层

script 不取代单条命令,它是多步流程的上层。选择标准:

✅ 先看这些
纯抓数据的任务先看 extract --schema(一次调用把页面结构化成 JSON); 要在多页之间累积、按条件筛选,再用 script 把 extract 包进循环。