← 返回列表
未验证
驱动本地 Chrome 提取 Markdown,绕过机器人检测
尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/9/2 · 已提供中文文档
为AI代理打造的加固型本地浏览器——零依赖CDP客户端、CLI、MCP服务器及Cordis插件。令牌高效的Markdown,可通过标准机器人检测检查。无远程服务。
综合分
29.3
GitHub 分
29.3
用户评分
—
★ Stars
1
周下载量
—
兼容 / 相关生态插件(非 dsh 原生,请按其对应运行时安装)
git clone https://github.com/TrueNix/agent-browser.git数据截至 2026/9/16(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动
README
agent-browser
一个面向 AI 代理的加固本地浏览器。零运行时依赖。 通过 DevTools 协议驱动你已有的 Chrome,提取 token 高效的 Markdown,并且不会被轻易标记为自动化工具。
| 界面 | 安装 | 用途 |
| --- | --- | --- |
| MCP 服务器 | npx -y @truenix/agent-browser mcp | Claude Code、Cursor、Codex、任何 MCP 客户端 |
| CLI | npx -y @truenix/agent-browser markdown | shell、脚本、CI |
| 库 | import { withBrowser } from '@truenix/agent-browser' | 你自己的 Node 代码 |
| DSH / Cordis 插件 | 组合行 | DSH 框架中的原生工具 |
npx -y @truenix/agent-browser markdown https://news.ycombinator.com
一切都在本地运行。无需账户、无需 API 密钥、无需远程服务、无配额限制。
为什么
将完整的渲染后 DOM 喂给代理会浪费其大部分上下文。在真实页面上的测量结果:
而一个自我暴露为自动化的浏览器会被屏蔽、降级,或被提供不同的内容——这会悄然污染代理得出的任何结论。
渲染页面基准测试(实时 DOM 对比 Markdown)
每个单元格为 UTF-8 字节数 / 精确的 o200k_base token 数。第一个载荷是
JavaScript 和页面 load 事件之后的序列化 DOM,而非 curl
响应。缩减量仅比较页面输入 token。
| 站点 | 渲染后 DOM | Markdown | --links text | 精确 token 缩减 |
| --- | ---: | ---: | ---: | ---: |
| en.wikipedia.org/wiki/WebAssembly | 861 kB / 282k tok | 82.6 kB / 21.9k tok | 48.0 kB / 12.2k tok | 12.87× / 23.14× |
| react.dev | 273 kB / 108k tok | 11.3 kB / 3.1k tok | 8.6 kB / 1.9k tok | 34.73× / 57.23× |
| nextjs.org | 337 kB / 124k tok | 5.3 kB / 1.2k tok | 4.2 kB / 0.9k tok | 100× / 131.05× |
| github.com/trending | 666 kB / 218k tok | 7.7 kB / 2.2k tok | 4.2 kB / 1.1k tok | 100.96× / 200.63× |
| apple.com | 362 kB / 143k tok | 4.1 kB / 1.1k tok | 3.1 kB / 0.9k tok | 124.4× / 166.69× |
| news.ycombinator.com | 34.5 kB / 11.7k tok | 10.3 kB / 3.4k tok | 3.2 kB / 1.1k tok | 3.42× / 10.86× |
从源代码检出开始,使用 npm run charts 重新生成两张图表和此表格的底层
测量数据——它会实时测量,然后使用 agent-browser 自身绘制 PNG、更新表格,并将完整的机器可读快照写入
benchmark-results.json。
精确分词器是一个开发依赖;已发布的浏览器包
仍然保持零依赖。
减少页面输入 token 消耗
在导航密集的页面上,链接可能主导提取输出。当代理
是在阅读而非导航时,去掉它们的链接目标:
agent-browser markdown https://github.com/trending --links text
agent-browser markdown https://en.wikipedia.org/wiki/Rust --links relative
| 模式 | 渲染内容 | 使用场景 |
| --- | --- | --- |
| inline (默认) | text | 智能体将导航到下一页 |
| relative | text | 同站点爬取;保留目标,丢弃源 |
| text | text | 阅读、摘要、问答 |
重型站点基准测试
页面输入 token:渲染后的 DOM 对比 agent-browser
Hacker News + YC Blog 的精确 o200k_base 页面输入 token。这不包括提示词、工具 schema、重试、缓存、模型输出和提供商定价;这不是端到端的成本估算。
加载后渲染的页面
导航会等待 load 事件,由 CDP 生命周期事件驱动,而非轮询循环加固定延迟。在真实页面上,对于字节完全相同的输出,这要快 2–3 倍:
| 页面 | --wait load(默认) | 旧的固定 250 ms 稳定等待 |
| --- | --- | --- |
| example.com | 7 ms | 258 ms |
| github.com/trending | 115 ms | 340 ms |
| nextjs.org | 160 ms | 365 ms |
| news.ycombinator.com | 218 ms | 466 ms |
客户端渲染的应用,其内容在 load 之后才到达,需要的是真实信号,而不是更大的猜测——旧的 250 ms 稳定等待也错过了那些内容:
agent-browser markdown https://some-spa.example --wait idle
| 模式 | 等待 | 使用场景 |
| --- | --- | --- |
| domcontentloaded | DOM 解析完成 | 你只需要 HTML 中自带的标记 |
| load (默认) | load 事件 | 几乎总是适用 |
| idle | 网络静默 | 结果看起来像空壳 |
任何其他框架
如果你的框架使用 JSON Schema,它就能驱动 agent-browser,而无需本项目知道该框架的存在。agent-browser tools 会以你需要的任意格式打印工具目录:
agent-browser tools # MCP: {name, description, inputSchema}
agent-browser tools --format openai # OpenAI: {type:"function", function:{...}}
agent-browser tools --format anthropic # Anthropic: {name, description, input_schema}
然后每个工具都能以任意一种方式运行,取决于你的框架能做到哪种:
import { findTool, TOOLS } from '@truenix/agent-browser/tools';
import { withBrowser } from '@truenix/agent-browser';
// 进程内:框架可以持有 CDP 会话
const tool = findTool('browser_markdown');
const text = await withBrowser({}, async (session) => {
await session.navigate('https://example.com');
return tool.run(session, { url: 'https://example.com', links: 'text' });
});
// 进程外:框架只能运行命令(沙箱、shell)
tool.cli({ url: 'https://example.com', links: 'text' });
// => ['markdown', 'https://example.com', '--links', 'text']
安装
作为 MCP 服务器
Claude Code
claude mcp add browser -- npx -y @truenix/agent-browser mcp
Cursor / Windsurf / 通用 mcpServers JSON
{
"mcpServers": {
"browser": {
"command": "npx",json
"args": ["-y", "@truenix/agent-browser", "mcp"]
}
}
}
工具:browser_markdown、browser_text、browser_html、browser_links、browser_screenshot、browser_evaluate、browser_accessibility_tree、browser_pdf、browser_probe。
每个接口都从同一个共享目录中暴露相同的九个工具,因此 CLI、MCP 服务器和 DSH 插件永远不会彼此偏离。
作为库使用
bash
npm install @truenix/agent-browser
js
import { withBrowser } from '@truenix/agent-browser';
const md = await withBrowser({}, async (session) => {
await session.navigate('https://example.com');
return session.markdown();
});
作为 DSH / Cordis 插件使用
一行命令(推荐——当你确实想要 DSH 时会自动接线):
bash
npx -y @truenix/agent-browser install # adds the bundle to ~/.dsh/profiles/web/package.json, then pnpm install (the mount ships in the package's own cordis.patch.yml layer)
npx -y @truenix/agent-browser install --profile web --dry-run # preview
npx -y @truenix/agent-browser uninstall # remove again
重启 dsh —— 所有九个 browser_ 工具都会作为原生工具出现。在单纯的 npm install 时不会运行任何处理程序;需要有意的 install。
手动方式(如果你更愿意自己编辑组合):
bash
npm i -g @truenix/agent-browser
or inside the harness checkout: pnpm add @truenix/agent-browser
需要 Node ≥ 18 以及 Chrome/Chromium 安装。然后添加到宿主组合中(工具注册表位于宿主上,而非每个 agent):
yaml
~/.dsh/profiles/web/cordis.patch.yml — persists for every web session
- insert:
- id: agent-browser
name: '@truenix/agent-browser/cordis'
config:
timeoutMs: 180000 # per-tool call budget; default respects AGENT_BROWSER_BIN / ENDPOINT
cli: 'npx -y @truenix/agent-browser' # override only if needed
简写形式(当组合已经包裹了 insert 时):
yaml
- '@truenix/agent-browser/cordis':
timeoutMs: 180000
环境变量覆盖:AGENT_BROWSER_BIN(Chrome 二进制文件)、AGENT_BROWSER_ENDPOINT(通过 --endpoint 附加到长期运行的浏览器),或 config.cli。
CLI
agent-browser [options]
markdown Extract the whole page as Markdown (main content by default)
text Visible text only
html Full serialized DOM after JavaScript runs
links Every anchor as JSON
screenshot PNG/JPEG (-o file, --full)
pdf PDF (-o file)
a11y Filtered accessibility tree
eval Evaluate JS, return only its value (cheapest)
probe Browser, GPU and capability report
mcp Run as an MCP server on stdio
tools Print tool schemas (--format mcp|openai|anthropic)
选项:--headful、--no-stealth、--block-images、--gpu/--no-gpu、--width、--height、--viewport WxH、--main、--raw、--links、--max-rows、--limit、--wait、--settle、--world isolated|main、--full、--endpoint 、--timeout、--json、-o。
用 markdown 阅读,用 eval 查找
智能体使用这个工具时最昂贵的错误,就是为了回答一个单行问题而渲染整个页面。在三个重量级页面上提出三个有针对性的问题,通过 eval 只需 235 字节,而 Markdown 则需要 77 kB:
bash
agent-browser eval https://github.com/trending \
"JSON.stringify(Array.from(document.querySelectorAll('article h2 a')).slice(0,3).map(a=>a.innerText.trim()))"
["openai / codex","mattpocock / skills","affaan-m / ECC"] -> 58 bytes
请使用 innerText,而不是 textContent。textContent 返回的是原始源码中的空白("openai /\n\n codex");innerText 返回的是实际渲染的内容("openai / codex")。搞错这一点的智能体要付出整整一次重试往返的代价,这远比它节省的字节数昂贵。
当你确实需要阅读、总结或搜索页面时,请使用 markdown。
每次调用都会启动自己的浏览器(约 1.4 秒)。如果连续进行多次查找,请保持一个浏览器存活,并将 --endpoint 指向它——同样三个问题,三次冷启动需要 5.9 秒,而针对一个热浏览器只需 3.2 秒。
--endpoint 会附加到一个已经运行的浏览器,而不是启动一个新浏览器——这对于在多次调用之间复用一个长期存活的浏览器很有用。
内存
Chrome 在加载任何内容之前,其下限大约是 18 个进程共 420 MB PSS,而且这个下限是 Chrome 的,不是这个包的——调整标志只能让它变化约 5%,而能让它进一步变化的标志(--enable-low-end-device-mode)会在 24 核的机器上报告 navigator.deviceMemory: 2,这是一台不可能的机器,也正是那种会让浏览器被标记的不一致。所以杠杆是更少的浏览器,而不是更小的浏览器。
MCP 服务器保持一个浏览器,并为每次工具调用提供自己独立的上下文。并发获取三个重量级页面:
| | 峰值 PSS | 进程数 | 墙钟时间 |
| --- | --- | --- | --- |
| 每次调用一个浏览器 | 1289 MB | 44 | 2114 ms |
| 一个浏览器,3 个上下文 | 654 MB | 20 | 2049 ms |
隔离性没有改变——每次调用的浏览器上下文一直是提供隔离的机制。浏览器在空闲 30 秒后关闭(AGENT_BROWSER_IDLE_MS,设为 0 则立即关闭),因此长期存活的服务器不会在对话之间一直占用 420 MB。在它处于热状态时重复调用会完全跳过启动,运行速度大约快一倍。
代价是:任务共享一个进程树,因此浏览器级别的崩溃会带走所有正在进行的任务,而不是只影响一次调用。浏览器死亡会被检测到,并在下一次调用时重新启动。如果你需要按任务进行爆炸半径隔离,请使用 withBrowser,它仍然为每次调用提供自己的浏览器。
js
import { withPooledSession, shutdownPool } from '@truenix/agent-browser/pool';
javascript
await withPooledSession({}, async (session) => {
await session.navigate('https://example.com');
return session.markdown();
});
await shutdownPool(); // 或者让它闲置超时退出
对于 CLI,每次调用都是独立的进程,因此复用意味着将
--endpoint 指向一个你自己保持存活的浏览器。
守护进程:为每次 CLI 调用共用一个浏览器
每次 CLI 调用都是独立的进程,因此默认情况下每次调用都会冷启动自己的
Chrome —— 十个并发调用意味着十个浏览器。--daemon 改为共用一个常驻
浏览器,每次调用仍然获得自己独立的上下文:
bash
agent-browser markdown https://example.com --daemon
agent-browser daemon --status
agent-browser daemon --stop
| | 实际耗时 | Chrome 进程峰值 |
| --- | --- | --- |
| 3 次顺序查询,无守护进程 | 5330 ms | — |
| 3 次顺序查询,--daemon | 3254 ms | — |
| 10 次并发调用,无守护进程 | 1443 ms | 140 |
| 10 次并发调用,--daemon | 1144 ms | 32 |
它是选择性启用的(--daemon,或 AGENT_BROWSER_DAEMON=1),因为启动一个
比你的命令存活更久的后台进程是一种值得事先询问的副作用。它按需启动,
并在闲置五分钟后退出(AGENT_BROWSER_DAEMON_IDLE_MS)。设置该环境变量
还会让 MCP 服务器也路由到它,当多个 MCP 客户端共用一台机器时值得这样做。
--headful、--width、--height、--block-images 和 --gpu 在浏览器
启动时就被固定,因此共用的浏览器无法满足它们。传入其中任何一个都会
优先:该调用会悄悄地获得自己的私有浏览器,并在 stderr 上说明这一点。
它围绕四种失败模式构建,每一种都经过测试验证:
- 恰好一个守护进程。 十个同时的首次调用者都会尝试启动一个;其中九个
在绑定套接字的竞争中失败,并在启动任何东西之前退出。绑定套接字就是*
锁,因此没有会过期的锁文件。
- 打开的套接字就是引用计数。 客户端在工作期间保持其连接,因此即使
客户端被 SIGKILL 杀死也仍会释放 —— 内核会关闭套接字。一条“请释放”
的消息反而会永远泄漏一个引用。
- 闲置退出,这样它不会在对话之间一直占用约 420 MB。
- 被 SIGKILL 杀死的守护进程不会遗留任何东西:它的 profile 标记会
记录其 pid,因此下面的清扫会在下次启动时回收该浏览器。
自我清理
每次启动都会向其临时 profile 写入一个所有者标记,并清扫那些所有者已
死亡的 profile,杀死仍附着在它们上面的浏览器。结合 SIGINT/SIGTERM/SIGHUP
处理器,就得到:
| 调用如何结束 | 孤儿进程 | 下次启动后 |
| --- | --- | --- |
| 正常 | 0 | 0 |
| SIGTERM / SIGINT | 0 | 0 |
| SIGKILL(无法捕获) | 1 个浏览器 | 0 |
因此一次硬杀死最多代价是一个被遗留的浏览器,而不是每次被中断的调用一个。
十次并发 CLI 调用,反复进行,不会留下任何东西。存活的浏览器永远不会被
清扫 —— 它的所有者进程仍在运行,而一个无法归属的
profile 会一直保持原样,直到没有任何东西打开它,并且它已空闲 60 秒。
机器人检测
自己运行:npm run test:bot。最新结果:
| 检测器 | 结果 |
| --- | --- |
| bot.sannysoft.com | 31 通过,0 失败 |
| bot-detector.rebrowser.net | 8 绿色,0 不安全;活动隔离探针保持灰色 |
| deviceandbrowserinfo.com | isBot: false,22 项检查中 0 项被标记 |
| arh.antoinevastel.com | SKIP,检测器返回 HTTP 502 |
普通无头 Chrome 在 sannysoft 的四行上失败(HEADCHR_UA、CHR_MEMORY、WebGL SwiftShader、旧 UA),并被报告为机器人。
网络故障或检测器侧的 5xx 计为 SKIP,绝不计为通过。
已加载的 4xx、挑战、不完整和无法识别的页面按失败关闭处理。该门禁
要求三次可达的通过,因此测试站点宕机的一天不能被
误认为成功。
环境比打补丁更重要
Release 2.0.1,在两个地方测量:
| | 此工作站 | GitHub Actions runner |
| --- | --- | --- |
| IP | 住宅 | 数据中心 |
| GPU | 真实(NVIDIA) | 无 → SwiftShader |
| bot.sannysoft.com | 31 通过,0 失败 | 30 通过,1 失败(WebGL Renderer) |
| bot-detector.rebrowser.net | 6 绿色,0 红色 | 6 绿色,0 红色 |
| deviceandbrowserinfo.com | isBot: false | isBot: true(hasSuspiciousWeakSignals) |
在那次运行中,这些检测器套件所检验的每个 CDP 级信号在两者中
都保持非阳性。改变判定的是环境:数据中心 ASN 加上软件渲染触发了一个
弱信号组合,而这是任何数量的指纹补丁都无法解决的。
这就是问题的真实形态。强化浏览器消除了
琐碎的破绽。你在哪里运行它决定了其余部分。
强化做了什么,以及为什么
每一项都来自一个检测器告诉我们我们错了:
- 没有受自动化控制的 Blink 特性。 Chrome 的无头路径和 --remote-debugging-port=0 路径通常会暴露 navigator.webdriver = true。启动器禁用了 AutomationControlled,并且不添加 --enable-automation,因此该值保持 false。
- 没有 Runtime.enable。 它是最响亮的 CDP 破绽,并支撑着经典的 console/Error.stack 检测器。Runtime.evaluate 在没有它的情况下也能正常工作。
- 默认隔离求值。 读取和 Markdown 提取共享 DOM,但不触碰页面安装的全局变量或原型钩子。eval --world main 是给需要页面定义 JavaScript 的代码使用的显式逃生舱。
- 窗口和屏幕一起移动。 --window-size 不带 --ozone-override-screen-size 会给出 outerWidth > screen.width,这在物理上是不可能的——这是一个比普通无头更强的信号。
- 不覆盖默认设备指标。 通常的 1280×720 是 Playwright 的默认视口,检测器会按名称标记它。只有在需要时才设置 --viewport。
- 在可用时使用真实 GPU,从而获得真正的 ANGLE (NVIDIA …) 渲染器,而不是 SwiftShader。
- 在启动时设置 UA,而不仅仅通过 CDP。 Emulation.setUserAgentOverride 无法触及 Web Worker,因此 worker 会继续报告无头模式的 UA,而页面报告的是干净的 UA(hasInconsistentWorkerValues)。
- 不覆盖 acceptLanguage。 CDP 通过拆分该 header 来推导 navigator.languages,因此 "en-US,en;q=0.9" 会变成 ["en-US","en;q=0.9"]——一个本不可能合法存在的 q-value,并且还会造成另一个页面/worker 不匹配。--lang 可以正确地完成这件事。
- Client Hints 从二进制文件自身的版本派生,因此 Sec-CH-UA 不可能与 navigator.userAgent 不一致。
- 每个会话使用隔离的浏览器上下文,在关闭时释放——每个任务都有干净状态,而无需第二个浏览器进程。
反复出现的教训:一致性胜过覆盖范围。 其中四种情况是部分伪装反而让检测更容易,只有运行真实检测器才能发现。
为什么 WebGL 伪装默认关闭
spoofWebgl 存在,并且实现得很谨慎——围绕原生 getParameter 的 Proxy,因此 Function.prototype.toString 仍然报告 [native code]。它默认关闭,因为测量表明它会适得其反。 来自 test/webgl-spoof-experiment.mjs:
| 分支 | 声称的渲染器 | maxTexture | extensions | sannysoft | 结论 |
| --- | --- | --- | --- | --- | --- |
| 真实 GPU,无伪装 | NVIDIA | 32768 | 37 | 0 failed | isBot: false ✅ |
| SwiftShader,诚实 | SwiftShader | 8192 | 35 | 1 failed | isBot: false ✅ |
| SwiftShader + 伪装 | NVIDIA | 8192 | 35 | 0 failed | isBot: true ❌ |
声称拥有你并不具备的硬件,只能修复一个表面项,却会让复合检测器失败:注入的脚本无法触及 Web Worker,因此 worker 仍然报告 SwiftShader,而 MAX_TEXTURE_SIZE 仍停留在软件值,同时渲染器字符串却声称是独立 GPU。
诚实的 SwiftShader 能通过。一个令人信服的谎言不能。相反,给浏览器一个真实 GPU——这是免费的。
这不能做什么
指纹级检测是全部范围。它不能击败,也不试图击败:
- TLS/JA3-JA4 和 HTTP/2 指纹识别——在任何 JavaScript 运行之前就已决定
- IP 信誉——数据中心与住宅 ASN,通常才是真正的阻碍
- 行为分析——鼠标轨迹、时序、停留时间
商业挑战产品依赖这些,因此“通过关卡”意味着不会被轻易标记为自动化,而绝不是无法被检测。适用于你自己的网站、测试、无障碍工作和普通代理浏览。
内存
一个 Chrome 栈大约占用 450 MB。关键在于架构,而不是标志:运行一个浏览器和多个隔离的上下文,而不是每个任务一个浏览器。启动一次浏览器,然后用 --endpoint / AGENT_BROWSER_ENDPOINT 将每次调用都指向它。对于文本工作,--block-images 会有帮助。
零依赖
dependencies 是空的,包括 WebSocket 传输。
Node 的全局 WebSocket(WHATWG)无法发送请求头,而任何经过身份验证或代理的 CDP 端点都需要请求头,并且 undici 无法独立导入。因此 src/ws.mjs 直接在 node:http(s) 之上实现了 RFC 6455——握手、掩码、延续分片、64 位长度、ping/pong——这正是 CDP 所需的全部内容。
Markdown 转换器使用显式栈遍历 DOM,无论嵌套多深,都将 JS 调用深度保持在 O(1),并对叶子级内联节点使用原生 innerText。这使其在深度嵌套的文档上既递归安全,又在大型页面上显著更快。
环境
| | |
| --- | --- |
| AGENT_BROWSER_BIN | Chrome/Chromium 二进制文件的路径 |
| AGENT_BROWSER_ENDPOINT | 附加到此 CDP 端点,而不是启动新浏览器 |
要求
Node ≥ 18 以及 Chrome/Chromium 安装。无需构建步骤。
致谢
本项目的加固几乎完全源自他人已发布的检测研究——参见 CREDITS.md。特别感谢 rebrowser-bot-detector、bot.sannysoft.com、deviceandbrowserinfo.com 和 Camoufox 展示了如何正确地做到这一点。
许可证
MIT同作者(TrueNix)的其他插件
扫码进群