📍 无忆研习社 › 开源解码 › 仅 592 行代码撬动浏览器自动化新范式:Browser Harness 全面拆解

仅 592 行代码撬动浏览器自动化新范式:Browser Harness 全面拆解

摘要:Browser Harness 是一个极简浏览器自动化项目,仅约592行Python代码,直接通过Chrome DevTools Protocol(CDP)连接用户正在运行的浏览器,将控制权交给大模型。其核心颠覆在于两点:AI在执行任务过程中可实时改写helpers.py以补全缺失能力(自愈),并将探索出的网站私有API、稳定选择器等经验自动沉淀为domain-skills/目录下的技能文件供未来复用。该项目不走Selenium、Playwright等传统框架的封装老路,主张“做减法、给自由、留痕迹”,让模型在极薄接口上自主发挥,适合已有强模型编码助手的开发者进行探索性网页操作和自动化任务。
字号 100%
行距 2.05
当前可见 60% 的内容
本文由 莫潇羽@无忆研习社(www.wuyi.wiki) 原创整理,转载请注明出处。

快速摘要

Browser Harness 是 Browser Use 团队最新开源的一个极简浏览器自动化项目,全部核心代码仅约 592 行 Python。它不走 Selenium、Playwright 那条"预置 API、层层封装"的老路,而是直接通过 Chrome DevTools Protocol(CDP)用一根 WebSocket 接入用户正在运行的浏览器,然后把绝大部分控制权交给大模型自己。最颠覆的地方在于两点:一是 AI 在执行任务过程中可以实时改写 helpers.py 把缺失的能力补上(所谓"自愈"),二是完成任务后会把踩过的坑、摸清的选择器、私有 API 自动沉淀成 domain-skills/ 里的技能文件,下次遇到同类网站直接复用。下文会一层层拆开:它到底是什么、底层怎么跑、怎么安装、踩坑点在哪、适合什么场景,建议读完全文再下手动手。


一、先说背景:为什么需要一种"新的"浏览器自动化

做过网页自动化的朋友都知道,这条路走到今天其实相当拧巴。

十年前我们用 Selenium,后来换成 Puppeteer、Playwright,再到近两年流行的 Browser Use、各种浏览器 Agent 框架,表面看工具一代比一代强,但底层的思路几乎没怎么变——开发者提前写好一堆"点击、输入、滚动、截图"这样的原子操作,把能做的动作封装成 API,然后让脚本或者 AI 去按顺序调用。

这种做法在页面结构稳定的年代非常好用。可问题是,现在的网页早已不是十年前那副"静态 DOM + 表单提交"的样子。单页应用(SPA)、Shadow DOM、跨域 iframe、虚拟滚动、懒加载组件、React/Vue 的受控输入、各种异步状态机……几乎每一个现代前端特性都在给自动化工具挖坑。框架作者疲于奔命加适配器,使用者疲于奔命查文档,而 AI Agent 被夹在中间,只能调用那些"被允许"的 API,一旦预置能力覆盖不到真实场景,就只能报错或者绕路。

莫潇羽@无忆研习社 在写这篇拆解之前,自己也用过不下五种浏览器自动化方案,最强烈的感受就是:互联网是活的,但框架是死的。框架的作者永远走在网页变化之后,Agent 的行为永远被框架作者的想象力所限。

Browser Use 团队这次开源的 Browser Harness,本质上是在正面反思这个问题。他们在项目的配套博文《苦涩教训:Agent 框架的幻觉》里写得很直白:Agent 框架失败,往往不是模型不够聪明,而是我们给模型定义的"动作空间"太窄了。大模型经过大量代码、浏览器交互、CLI 工具的训练,本身就具备相当完整的"网页操作直觉",我们真正要做的,不是教它怎么点按钮,而是把浏览器这个"沙盒"原原本本交给它。

二、Browser Harness 是什么

回到主角。Browser Harness 这个名字里,harness 原意是"马的笼头"或者"线束",套在英文语境里通常翻译为"把某样能力驯化成可用的形态"。官方的自我描述是一句话:

The simplest, thinnest, self-healing harness that gives LLM complete freedom to complete any browser task. Built directly on CDP.

翻成人话:它是一层极薄的壳,把一个真实 Chrome 浏览器的控制权直接交到大模型手里,让模型自己想办法完成任何网页任务;而且这层壳具备"自愈"能力——任务过程中缺什么能力,模型自己顺手写上去。

从工程角度看,整个项目只有以下几个文件:

  • install.md:首次安装和浏览器 bootstrap 指引
  • SKILL.md:日常使用说明,会被 Claude Code/Codex 读取
  • run.py:约 36 行,负责预加载 helpers 并执行用户的 Python 片段
  • helpers.py:约 195 行,初始的工具函数集合,AI 会动态编辑这个文件
  • admin.py + daemon.py:合计约 361 行,负责后台守护进程、CDP WebSocket 桥接、Unix Socket 通信
  • domain-skills/:面向具体网站的技能目录,例如 github/、linkedin/、amazon/、tiktok/ 等
  • interaction-skills/:面向交互机制的技能目录,例如 iframes.md、shadow-dom.md、dropdowns.md、uploads.md 等

算下来 Python 代码总共大约 592 行,这还是包括注释和空行的口径。对比动辄几万行的传统自动化框架,这个体量几乎可以用"小到不像话"来形容。

项目采用 MIT 协议开源,目前在 GitHub 上位于 https://github.com/browser-use/browser-harness,开源后不久就收获了数千 Star,讨论热度相当高。

三、三大核心理念:极简、自愈、沉淀

读完源码和文档之后,个人总结 Browser Harness 的设计哲学其实就是三件事:做减法、给自由、留痕迹。

做减法:用一根 WebSocket 连通一切

Browser Harness 没有自研抽象层、没有场景 DSL、没有所谓的"Agent Runtime"。它直接使用 Chrome DevTools Protocol(CDP),这是 Chrome 官方提供的远程调试协议,任何一个开启了 --remote-debugging-port 的 Chrome 实例都可以被它接管。通信方式是最经典的 WebSocket + JSON,每次一行请求一行响应,简单到几乎无法再简化。

因为底层就是 CDP,所以理论上 Chrome 能做的一切(打开页面、执行 JS、截图、下载、设置 cookie、监听网络、操作 Service Worker、控制扩展等等),Browser Harness 都能做到,不会被框架作者的能力边界锁死。

给自由:让模型自己写缺失的能力

这是它最不同寻常的一点。传统自动化里,如果某个功能没实现,流程就会卡住,要等开发者去写新代码。而 Browser Harness 默认假设"helpers.py 里没写的,模型可以自己写上去"。

官方 README 里给了一个非常形象的示意:

  ● agent: wants to upload a file
  │
  ● helpers.py → upload_file() missing
  │
  ● agent edits the harness and writes it    helpers.py   192 → 199 lines
  │                                                       + upload_file()
  ✓ file uploaded

模型发现自己需要上传文件,但 helpers.py 里没有 upload_file() 这个函数,于是它就直接打开这个文件,添加几行代码把功能补齐,然后继续执行任务。整个过程不需要开发者介入。这就是"Self-healing"(自愈)这个词的由来。

留痕迹:把经验沉淀成可复用的技能

模型每一次摸清一个网站的"套路"——比如某个私有 API、某个稳定的 data-* 选择器、某个 React 组件的奇怪行为——都可以写进 domain-skills/<site>/ 目录。下次再碰到同一个网站,模型第一件事就是读这个目录,直接复用之前的经验。

官方非常强调一点:这些技能文件应该由 Agent 自己来写,而不是人手工维护。因为手写的"教程"往往是臆想出来的稳定路径,而 Agent 实际跑过一遍后留下的笔记,才反映真正在浏览器里能跑通的路径。

项目目前已经内置了若干示例技能,包括 GitHub、LinkedIn、Amazon、TikTok 等常见站点的片段,感兴趣的朋友可以去仓库里看看它们是什么形状。

四、技术架构拆解:从 Chrome 到 Python 的完整链路

数据链路示意

先把整个系统的数据链路画出来(莫潇羽@无忆研习社 根据 SKILL.md 里的架构图重绘):

┌─────────────────────────────┐
│  Chrome  /  Browser Use 云端  │
└──────────────┬──────────────┘
               │ CDP over WebSocket
               ▼
┌─────────────────────────────┐
│         daemon.py           │  ← 常驻守护进程
└──────────────┬──────────────┘
               │ Unix Domain Socket (/tmp/bu-<name>.sock)
               ▼
┌─────────────────────────────┐
│  run.py  (+ helpers.py)     │  ← 每次任务短连接
└─────────────────────────────┘

整条链路的关键点有三层:

  • 上层 Chrome 通过 CDP 的 WebSocket 和 daemon.py 对话,daemon.py 是唯一一个需要常驻的进程
  • daemon.py 对下暴露一个 Unix Domain Socket,路径按 BU_NAME 命名(默认为 default)
  • 每次用户调用 uv run browser-harness,run.py 就起一个短暂的 Python 环境,预加载好 helpers.py,通过那个 socket 把指令转给 daemon

这种设计有两个好处:一是 CDP 的长连接只需要建立一次,后续命令几乎零启动开销;二是用户的 Python 代码是"一次性"的,跑完就退出,不会污染全局状态。

通信协议

Browser Harness 的通信协议极其克制,每条消息都是一行 JSON,明确区分三类:

  • 如果是 CDP 调用,格式是 {method, params, session_id}
  • 如果是 daemon 自己的控制指令,格式是 {meta: ...}
  • 响应是 {result} / {error} / {events} / {session_id} 之一

没有 gRPC、没有 protobuf、没有 Thrift,甚至连框架级的序列化都懒得上。好处是任何人读十分钟源码就能搞明白协议长什么样,坏处是极少——因为 CDP 本身就是 Chrome 在用的协议,稳定性不用担心。

为什么坚持用 CDP 而不是 Playwright

这是理解 Browser Harness 设计哲学的一把钥匙。Playwright 是一个优秀的浏览器自动化库,但它本质上也是在 CDP 之上做了一层面向"人类开发者"的抽象:链式 API、自动等待、selector engine、locator 概念等等。这些抽象对于写脚本的人类来说非常舒服,但对大模型来说反而是"多余的中间层"——模型训练时见过的浏览器控制示例,主要是原生 JS、原生 CDP 以及零碎的 Python 脚本,让它使用一个风格强烈的封装库,反而会降低它的准确率。

CDP 的另一个杀手锏是"合成器级别的点击"(Compositor-level click)。官方 SKILL.md 里反复强调:优先用坐标点击(Input.dispatchMouseEvent),因为这种点击事件会直接经过 Chrome 的合成器层,穿透 iframe、跨域、Shadow DOM,几乎不会被前端那些花里胡哨的封装拦截。而 Playwright 的 locator.click() 有时会因为 DOM 变化而失败。

用句大白话总结:给人类用的库求"声明式",给大模型用的库求"回归裸机"。这是 Browser Harness 最聪明的判断。

CDP 到底是什么:顺便补一点背景

CDP 全称 Chrome DevTools Protocol,是 Chrome 浏览器在 2013 年前后就开放出来的远程控制接口。你平时按 F12 打开的"开发者工具",底下其实就是一个 CDP 客户端在和浏览器对话。所有 DevTools 能看到、能操作的东西,CDP 都能通过 API 暴露出来。

它的模型大概长这样:协议里有若干"Domain",比如 Page、DOM、Runtime、Network、Input、Target、Emulation 等等,每个 Domain 下有一组方法(比如 Page.navigate、Runtime.evaluate、Input.dispatchMouseEvent)和一组事件(比如 Page.loadEventFired、Network.responseReceived)。客户端通过 WebSocket 发方法、收事件,就能拿到完整的浏览器控制能力。

Browser Harness 里的 cdp(method, **params) 就是把这套协议原封不动地暴露给大模型。对于训练中见过海量 Chrome 源码、DevTools 文档、Puppeteer 源码的大模型来说,这个接口几乎不需要学习成本——它知道 Page.captureScreenshot 怎么用,知道 DOM.getDocument 会返回什么结构,知道 Network.setExtraHTTPHeaders 能干什么。

换句话说,CDP 对大模型来说是"母语级"的 API,封装得越薄,模型发挥越稳。这是 Browser Harness 把所有高层 API 都当成可选项、把 CDP 当成必选项的根本原因。

合成器级点击的技术细节

前面提到"合成器级别的点击"这个术语,这里再稍微展开讲一下,因为它是整个项目稳定性的基石。

浏览器渲染一个页面,大致要经过几层:DOM 树 → Render 树 → Layout → Paint → Compositor(合成器)→ 显示。合成器位于最底层,负责把各种图层合并成最终画面。用户手指在屏幕上点一下,操作系统把坐标传给浏览器,浏览器的合成器根据坐标找到对应图层,再反向派发事件给上层 DOM。

CDP 的 Input.dispatchMouseEvent 模拟的正是合成器这一层的事件——不是"在某个 DOM 元素上触发 click",而是"在屏幕的 (x, y) 坐标上模拟一次真实点击"。这种做法有两个无可替代的优势:

  • 能穿透任何层级的封装,包括跨域 iframe、Shadow DOM、iframe 嵌套 iframe 这种地狱结构
  • 不依赖元素的 ID、class、aria 属性,不会因为前端改版而失效

代价也有:需要先用截图确认坐标。但在有截图能力的前提下,这其实反而更像人类操作——先看见再点击。这也是为什么 SKILL.md 里把 "screenshot first" 列为最重要的一条实践原则。

五、自愈机制具体是怎么工作的

这部分很多人最好奇,我单独拎出来细说。

helpers.py 是一份"活的"工具箱

初始版本的 helpers.py 只有大约 195 行,预置了一些最基础的能力:打开页面(goto)、等待加载(wait_for_load)、截图(screenshot)、坐标点击(click)、执行 JavaScript(js)、读取页面信息(page_info)、批量 HTTP 请求(http_get)、以及兜底的原生 CDP 调用(cdp)。

这些函数覆盖了 90% 的日常场景,但面对某个具体任务总会有漏洞。比如:

  • 需要把文件拖拽到某个 drop zone 上
  • 需要批量下载资源并重命名
  • 需要在特定时刻截获一个 XHR 响应
  • 需要绕过 beforeunload 对话框
  • 需要处理一个奇怪的 Web Component

这时候 Agent(Claude Code 或 Codex)会做一件事:直接编辑 helpers.py,把这个能力写成一个新的函数,然后马上在下一条命令里调用。项目作者特意把 helpers.py 设计成一个"鼓励被改写"的文件,注释里明确写着 the agent edits these。

自愈不是玄学,而是工程选择的结果

为什么这个机制能跑起来?因为现代大模型(尤其是 Claude、GPT 系列)在"写 Python + 调用 CDP"这件事上几乎是天才选手。让模型自己读文档、自己写适配代码,比让人类开发者去写预置 API 更快、更准、更契合当下这次任务。

再叠加一个关键细节:daemon.py 是常驻的,helpers.py 是被 run.py 每次重新加载的。这意味着 Agent 改完 helpers.py 后,下一次执行任务时新函数就会自动生效,完全不用重启任何东西。工程上非常优雅。

一个写出来的真实例子

以官方 domain-skills/tiktok/upload.md 为例(这是项目自带的)。一开始,helpers 里根本没有"上传视频到 TikTok"这种函数。但当一个 Agent 跑完第一次上传流程后,它会把这一趟发现的坑整理成一份 Markdown:TikTok 的上传表单其实是一个挂在 Shadow DOM 里的 input[type=file],需要用 CDP 的 DOM.setFileInputFiles 注入,而不能用坐标点击;发布按钮挂在 React 组件的 portal 里;草稿自动保存有一个 10 秒延时,必须等它保存完才能跳转……

下一个 Agent 再拿到"上传 TikTok"任务时,会先 rg --files domain-skills 看一眼有没有现成技能,看到 tiktok/upload.md 直接读完,省掉自己从零摸索的时间。如此一来,这个开源库在每一次使用中都会"变得更聪明",这是传统自动化框架完全做不到的。

一段完整的自愈流程模拟

为了让大家直观感受"自愈"到底怎么发生的,莫潇羽@无忆研习社 把一次典型会话的过程还原一下。假设用户给 Claude Code 的指令是:"帮我在某个文件共享网站上传一份本地的 PDF"。

整个过程大致是:

用户 → Claude Code:
  "打开 xxx.com,上传 /Users/moxiaoyu/files/report.pdf"

Claude Code 读 SKILL.md、helpers.py,决定先尝试:

> uv run bh <<'PY'
> goto("https://xxx.com")
> wait_for_load()
> screenshot("step1.png")
> PY

截图显示:首页右上角有个 "Upload" 按钮。Claude 继续:

> uv run bh <<'PY'
> click(1240, 88)        # 基于截图的坐标
> wait_for_load()
> screenshot("step2.png")
> PY

截图显示:弹出了一个带 input[type=file] 的表单。Claude 发现 helpers.py
里没有 `upload_file` 函数,它不报错、不停下,而是直接:

> 读取 helpers.py
> 在文件末尾追加:

def upload_file(selector, path):
    """把本地文件注入到指定 input[type=file] 上。"""
    backend_id = cdp("DOM.getDocument", depth=-1)["root"]["backendNodeId"]
    node_id = cdp("DOM.querySelector", nodeId=backend_id, selector=selector)["nodeId"]
    cdp("DOM.setFileInputFiles", nodeId=node_id, files=[path])

> uv run bh <<'PY'
> upload_file("input[type=file]", "/Users/moxiaoyu/files/report.pdf")
> screenshot("step3.png")
> PY

截图显示:文件已经成功上传,文件名出现在页面。任务完成。

Claude 最后把这次新增的逻辑,总结成 domain-skills/xxx/upload.md,
说明"这个站点的上传是通过直接 setFileInputFiles 完成的,
  不要尝试用坐标点击",留给下一次任务复用。

整个过程没有任何一行代码是用户手写的,也没有任何"适配器"被专门为这个站点写。Agent 利用自己对 CDP 协议的了解,临场写了一个新函数,补齐了 helpers 的短板,并把经验留在仓库里。这就是 Browser Harness 的工作流。

为什么 helpers.py 最终不会臃肿

有人可能担心:这样一直加函数下去,helpers.py 是不是会越来越胖,最后变成一个怪物?

作者对这个问题其实也给出了答复:helpers.py 应该保持"短而通用",Agent 加的函数如果只对某个站点有意义,应该以"站点技能"的形式写在 domain-skills/ 里,而不是塞进 helpers。真正被加到 helpers 的函数,必须是"在多数网站上都有用"的基础原语。

换句话说,helpers.py 是基础设施,domain-skills/ 是业务逻辑,两者的边界要守住。这就是为什么项目运行一段时间后,helpers.py 也许会从 195 行慢慢涨到 300 行左右,但不会失控——多出来的能力应该流向技能目录而不是核心代码。

六、592 行代码都干了什么:逐文件解析

run.py(约 36 行)

这是用户真正会"敲"的入口。它的职责极简:

  • 读取环境变量(BU_NAME、BU_CDP_WS 等)
  • 调用 admin.ensure_daemon() 确保守护进程在线
  • 把用户传进来的 Python 代码片段,在一个已经预加载好 from helpers import * 的命名空间里 exec 掉

整个文件不到 40 行,作者在 SKILL.md 里甚至特意叮嘱:"run.py 保持小巧,不要加 argparse、不要加子命令、不要加控制层"。

helpers.py(约 195 行)

这就是我们前面说的"活工具箱"。初始版本提供的函数,按用途大概分几类:

  • 页面生命周期:goto(url)、wait_for_load()、ensure_real_tab()
  • 视觉交互:screenshot()、click(x, y)、type_text(s)、press_key(key)
  • DOM 读写:js(code)、page_info()
  • 批量抓取:http_get(url)(配合 ThreadPoolExecutor,官方举例 249 个 Netflix 页面 2.8 秒跑完)
  • 兜底:cdp("Domain.method", **params)

这些函数绝大多数都是 CDP 原语的薄封装,读源码时你会发现它几乎是"把 CDP 手册翻译成 Python 名字"。这正是作者的审美:给模型留最大空间,封装层越薄越好。

admin.py + daemon.py(合计约 361 行)

这两个文件承担了最脏最累的活:

  • 发现本地正在运行的 Chrome(通过读 DevToolsActivePort 文件,而不是 /json/version,因为 Chrome 144+ 后者已经停止服务)
  • 建立并维护和 Chrome 之间的 CDP WebSocket 长连接
  • 开一个 Unix Domain Socket,监听来自 run.py 的请求
  • 做消息转发、事件分发、session 管理
  • 支持通过 BU_BROWSER_ID + BROWSER_USE_API_KEY 接入 Browser Use 提供的远程浏览器

值得一提的是,daemon.py 不做任何"管理员"式的工作——没有重试框架、没有日志框架、没有配置中心、没有 session manager。作者在 SKILL.md 里专门写了一条设计约束:Don't add a manager layer。这种极端的克制其实很难得。

多说几句 daemon 的设计动机。Chrome 的 CDP 连接虽然强大,但建立一次连接的耗时不容忽视——要发现端口、要走 WebSocket 握手、要枚举 target、要 attach session。如果每次用户敲一段代码都重新连一次,整体体验会糟糕到无法忍受。常驻 daemon 的方案把这些开销摊销到一次启动里,后续每条命令只走 Unix Socket 的本地通信,延迟几乎可以忽略。

另一方面,daemon 也负责"会话归一化"。CDP 的 target(可以简单理解为标签页、iframe 这类可附着的对象)会随着用户打开/关闭标签而变化,daemon 会自动维护一个"当前活动 target"的概念,这样上层代码不用每次都显式指定 session_id。对应的 helper 函数 ensure_real_tab() 就是在这里发挥作用:当你发现自己不小心操作到一个内部 target(比如 devtools 自己的面板、omnibox 弹窗),调一下这个函数就能重新指向一个真实页面。

pyproject.toml 与 .env.example

项目根目录还有两个小文件常被忽略:

  • pyproject.toml:定义了少量 Python 依赖和 uv run bh、uv run browser-harness 两个脚本入口。依赖极少,基本只有 websockets、cdp-use 等几个核心包,这也是它能做到 592 行总代码的前提——能用标准库就用标准库
  • .env.example:示意了 BROWSER_USE_API_KEY、BU_NAME、BU_CDP_WS、BU_BROWSER_ID 等环境变量,方便用户一键配置

整个项目读源码的时候给人的感觉非常清爽:没有一层套一层的目录,没有到处飞的 import,没有难以追踪的动态派发。这一点哪怕不打算用它,也值得借鉴。

domain-skills/ 与 interaction-skills/

这两个目录不是代码,而是由 Agent 写出来的 Markdown 笔记:

  • domain-skills/:按网站分目录,比如 github/、linkedin/、amazon/、tiktok/,记录每个站点的 URL 规律、私有 API、稳定选择器、框架坑
  • interaction-skills/:按交互机制分文件,比如 dialogs.md(对话框)、downloads.md(下载)、drag-and-drop.md(拖拽)、iframes.md(内嵌框架)、shadow-dom.md(影子 DOM)、uploads.md(上传)、cross-origin-iframes.md(跨域框架)等

一个新 Agent 接到任务后,会先 rg --files domain-skills 搜一下有没有现成资料,再决定怎么干。这个"先翻笔记再动手"的习惯,是整个项目智能涌现的核心。

七、怎么上手:从零到跑通一个任务

基本前提

动手之前,机器上需要准备这几样:

  • 一个可以在终端用的 AI 编码助手,官方主推 Claude Code 或 Codex(这两者都有 CLI 版本,可以通过订阅或 API Key 接入)
  • 安装了最新版的 Google Chrome(不是 Chromium,也不是 Edge)
  • Python 环境,推荐用 uv 做包管理器(项目的运行入口就是 uv run ...)

一句话安装

最快的方式是官方那段 setup prompt,直接粘到 Claude Code 或 Codex 里:

Set up https://github.com/browser-use/browser-harness for me.

Read `install.md` first to install and connect this repo to my real browser.
Then read `SKILL.md` for normal usage. Always read `helpers.py` because that is
where the functions are.

翻译成中文意思是:帮我把这个仓库装上,先读 install.md 把它和我真实浏览器连通,然后读 SKILL.md 看怎么用,每次都要读 helpers.py 因为函数都在那。

看到这段 prompt 你应该就能领悟到:作者默认用户是让 AI 来帮自己装,而不是人手动折腾。这也是为什么整个项目没有 setup.py、没有 install.sh、没有花哨的安装脚本——因为 AI 读 markdown 比读脚本更可靠。

连接真实浏览器

安装过程中,Claude Code 或 Codex 会指导你做一件事:让 Chrome 进入可被调试的模式。具体做法是打开一个特殊的 chrome:// 内部页面,勾选一个允许远程调试的复选框。首次出现 Chrome 的"Allow"对话框时必须手动点允许,之后就不用再操作了。

如果你以前折腾过 Puppeteer / Playwright,你会知道它们默认会自己启动一个独立的浏览器实例。Browser Harness 反其道而行之——它要求接入你自己平时在用的那个 Chrome。原因也很明确:你自己浏览器里的登录状态、cookie、扩展、书签、历史记录,全都能被 Agent 直接利用,不用再为每个网站重新登录。

跑第一个 Hello World

装完之后,最小可运行片段长这样(直接在终端敲):

uv run bh <<'PY'
goto("https://www.wuyi.wiki")
wait_for_load()
print(page_info())
PY

上面这段代码做了三件事:打开无忆研习社的首页,等页面加载完毕,然后打印页面信息。page_info() 会返回当前页面的标题、URL、视口大小等基础信息。

这里用到的 goto、wait_for_load、page_info 都是 helpers.py 预置的函数,uv run bh 就是前面说的短连接调度入口。

让 AI 来驱动

真实使用中,你通常不会自己写 Python 片段,而是直接把任务用自然语言告诉 Claude Code,让它代写。比如:

帮我打开 GitHub,搜索 "browser-harness",把第一页的前 10 条仓库名、描述和 Star 数导出成 CSV。

Claude Code 读完 SKILL.md 和 domain-skills/github/ 后,会自己组织一段类似下面的 Python:

goto("https://github.com/search?q=browser-harness&type=repositories")
wait_for_load()
repos = js("""
    Array.from(document.querySelectorAll('[data-testid="results-list"] > div'))
        .slice(0, 10).map(el => ({
            name: el.querySelector('a[href]').innerText,
            desc: (el.querySelector('p')||{}).innerText || '',
            stars: (el.querySelector('a[href$="/stargazers"]')||{}).innerText || '0'
        }))
""")
import csv, sys
w = csv.writer(sys.stdout)
w.writerow(["name", "desc", "stars"])
for r in repos:
    w.writerow([r["name"], r["desc"], r["stars"]])

它会当场跑起来,拿到结果再整理给你。如果过程中某个选择器失效、某个按钮拦不住,Agent 会自己改方案,甚至在 helpers.py 里加新函数。

Claude Code 与 Codex 的细微差别

虽然官方 setup prompt 对两者一视同仁,但在实际使用中 莫潇羽@无忆研习社 发现它们表现略有差异,给大家提前打个预防针:

  • Claude Code 在"读长文档"这件事上更耐心,会把 install.md、SKILL.md、helpers.py 都仔细过一遍再动手,所以首次安装往往一次就能跑通;但它会问不少确认问题,适合喜欢"边聊边做"的用户
  • Codex 更倾向"直接上手试",有时会跳过部分文档先开始执行,失败了再回过头来读。这种风格在大多数情况下速度更快,但偶尔会在一些 Chrome 权限细节上栽跟头
  • 两者处理截图反馈的能力都不错,但 Claude Code 对截图的理解通常更细,能指出页面上一些 Codex 容易忽略的视觉线索

建议新手先用 Claude Code 完整跑通一次安装流程,熟练之后可以按任务类型切换使用。

把任务拆成"粗颗粒指令"更省心

这是一个 prompt 工程的小技巧。很多人第一次用 Browser Harness 时,会把指令写成"打开 A,点 B,填 C,再点 D"——这种把步骤都写死的方式其实会限制 Agent 自主决策的空间,反而更容易因为一次选择器失效就全盘卡壳。

更好的方式是交代目标:

帮我去 https://example.com 的商品页,找出所有满足条件的商品,
按某个维度排序,把前 20 条导出成 Markdown 表格。

这样 Agent 会自己规划路径——先看截图判断页面结构,再决定是走前端排序还是调私有 API,中间哪一步卡住它会自己换思路。这才是极简自由式框架的正确打开方式。

一份 domain skill 应该怎么写

前面说到技能文件是 Agent 自己写出来的,那它到底应该长什么样?官方 SKILL.md 在这块给了相当细致的指引,我摘出几条关键点,顺便用 莫潇羽@无忆研习社 的理解解释一下。

一份好的 domain skill 应该捕捉的是网站的"持久形态"——也就是未来几个月乃至一年内基本不会变的那部分事实。值得写进去的内容包括:

  • URL 模式和关键查询参数,比如"列表页永远是 /search?q=...&page=...,跳过 loader 的技巧是加 ?th=1"
  • 站点私有的 XHR/Fetch 接口:请求路径、方法、请求体结构、鉴权字段。这一条最值钱——很多时候调一个私有 API 比在 DOM 里一层层点快 10 倍
  • 稳定的选择器,比如 data-testid、aria-label、语义化的 class 名;以及要避免使用的选择器,比如被 CSS Module 混淆过的哈希类名
  • 站点的框架特性,比如"这个下拉框是 React Combobox,只有按 Escape 才会提交值""这个列表是 Vue 的虚拟滚动,行级 scrollIntoView 会失效,必须滚动它自己的容器"
  • 必要的等待与原因:wait_for_load 不够用的地方和背后的机制
  • 陷阱:过期草稿、旧版 ID 现在返回 null、Unicode 特殊字符、beforeunload 对话框、验证码出现的触发条件等

而不应该写进去的内容则更明确:

  • 绝对不要写原始像素坐标。坐标会随视口、缩放、布局变化而失效,要写的是"如何定位目标",不是"我上次点的位置在哪"
  • 不要写这一次任务的流水账。技能是给下一个 Agent 看的地图,不是这一次的日记
  • 绝对不要写任何 cookie、session token、账号密码或和特定用户绑定的状态,因为 domain-skills/ 是公共目录,会进 PR

这几条规则其实放在任何技术文档写作上都是金科玉律,但在 Agent 协作的语境下尤其重要——你没法指望一个只读了笔记就来干活的 Agent 能够自己判断哪些内容是"昨天对今天不对"的。

人能手改这些技能文件吗

理论上可以。但官方非常不鼓励这样做。原因有三:

  • 人手写的技能经常带着"理想化假设",比如"这个按钮的 class 就叫 submit-btn",而实际上今天它叫 _3Xf91aB
  • Agent 自己写的技能,是在真实 CDP 调用跑通后才写下的,每一条都是"实测可行"的事实
  • 如果大家都手写,仓库很快就会被低质量的"看起来是对的但其实不对"的 markdown 淹没,整个技能系统就失效了

所以建议的贡献方式非常独特:跑你自己的任务,让 Agent 发现新规律,Agent 自己把技能文件写好,你直接发 PR 把这个 domain-skills/<site>/ 目录提交上去。这种"让使用者同时成为维护者"的模式,在开源世界里也算一股清流。

八、典型使用场景盘点

替代浏览器扩展做浏览器内工作流

一些原本需要写 Tampermonkey 脚本或浏览器插件才能完成的重复动作,现在可以一次性用自然语言交给 Agent。莫潇羽@无忆研习社 试过一个典型场景:把某个资讯站点的订阅列表批量导出到本地 Markdown,整个过程从说"想要"到拿到结果不到 3 分钟,全程没敲一行代码。

表单填充与数据录入

凡是涉及"把一份已有数据搬到某个 Web 系统里"的活,都是 Browser Harness 的菜。它可以对着你自己的 Chrome 登录状态干活,不用处理任何 OAuth、SSO、双因素认证。

爬虫 + 静态资源混抓

helpers.py 里内置了 http_get,官方还特意提示:静态页不要用浏览器去抓,用线程池直接 HTTP 拉比浏览器快一个数量级。这是一个非常务实的建议——Browser Harness 不是只有浏览器自动化,它是一个"以浏览器为中心的网页操作瑞士军刀"。

网页测试与回归

虽然官方没把测试作为主打场景,但把一个测试用例写成一段 Python 放进 uv run bh,再加上截图验证,其实已经是一套极简的 E2E 测试框架。对于小团队来说,比引入完整的 Playwright 测试栈轻得多。

子 Agent 分发

官方支持通过 BROWSER_USE_API_KEY 启动远程浏览器,免费层提供 3 个并发浏览器。这让"一个主 Agent 派发若干子 Agent 去不同网站并行干活"变得很自然,相关做法可以在 cloud.browser-use.com 查看,但本文不深入商业侧,只提工程上的可行性。

研究型工作流:抓取 + 分析 + 产出报告

这是 莫潇羽@无忆研习社 个人觉得最有想象力的一个方向。传统做研究要自己写爬虫、自己清洗数据、自己写报告模板,流程割裂。用 Browser Harness 配合 Claude Code 后,可以把"打开若干目标站点 → 结构化抽取内容 → 本地做文本聚类 → 生成 Markdown 报告"写成一条自然语言指令,全程 AI 自动走完。过程中如果某个网站需要特殊处理,Agent 顺手更新 domain-skills/ 就行,下次还能复用。

浏览器内的私有工作台

不少团队都会有一些"只在浏览器里能做"的私有操作:ERP、CRM、某些 SaaS 后台、内部工单系统。这些系统往往没有像样的 API,传统自动化要么反复模拟登录要么写浏览器插件。Browser Harness 天然对接真实 Chrome,登录一次就够了,非常适合把这些"后台苦力活"打包成一个个 Agent 可调用的能力。

三个实战小例子

光讲原理容易让人云里雾里,莫潇羽@无忆研习社 挑三段自己实际跑过的代码片段,给大家直观的感觉。

例一:批量截取一组页面的首屏做素材整理

urls = [
    "https://example.com/a",
    "https://example.com/b",
    "https://example.com/c",
]
for i, u in enumerate(urls):
    goto(u)
    wait_for_load()
    screenshot(f"shot_{i}.png")
    print(f"saved shot_{i}.png, title = {p
🔒
该内容仅对更高等级社区用户开放
请谨慎解锁时效性强且发布日期较早的文章
单篇解锁后若未显示全文请刷新页面
您当前:游客 · 可见 60% 内容 · 升级至 注册用户 可见 70%
👀
游客
可见 60%
✓ 当前
注册用户
注册用户
可见 70%
社区精英
社区精英
可见 100%
社区守护
社区守护
可见 100%
仅解锁本文,永久有效。如需PDF珍藏版,请联系站长获取。 当前单篇价格 ¥9.9
✏️ 发表评论

请先登录后发表评论

前往登录
📊 站点统计
今日发布0 篇
文章总数1107 篇
昨日发布1 篇
本月发布31 篇
建站时间456 天
🔍 搜索
站长微语

联系站长

QQ:2805463528
AIGC 技术社区
致力于解码 AI前沿技术 与经验分享
纯粹的技术应用社区

💡 欢迎您的建议与反馈,让社区变得更好

快速通道
联系站长
站长QQ二维码
AI交流群
AI交流群
仍在路上

那些寒夜里追赶过的方向

那些冷眼下没放弃的理想

一篇一篇写到现在

仍在路上

"不羁放纵爱自由"

—— 致敬 Beyond
持续创作中 莫潇羽 · 无忆研习社
▲