浏览器自动化给 AI agent 用,眼下就两条路。一条是截图喂视觉模型,一条是 Chromium 全家桶。前者慢,后者重。Moli 走了第三条:一个 Rust 写的无头浏览器,默认只给 DOM 结构,布局和像素按需才渲染。它自己那句口号很准,structure first, pixels on demand。这篇记我下载它踩的坑,和抓页面、出语义树、出截图的过程。

先说结论。它最狠的一手不是快,是把「渲染」从默认动作变成了可选项。大多数 agent 抓页面要的是结构,不是一张图。Moli 把这层账算明白了。

下载踩了个真实的坑

装 Moli 按 README 是一条 install 脚本,Linux 和 macOS 都是 curl 管道 sh。我第一个坑就栽在下载上。GitHub release 的二进制直连,我 curl 下去,HTTP 000,报错是 Error in the HTTP2 framing layer。我重试五次,断点续传,文件一直停在 933888 字节,就是下不动。

$ curl -sL https://github.com/lexmount/moli/releases/download/v1.1.24/moli-aarch64-apple-darwin.tar.gz
curl: (16) Error in the HTTP2 framing layer

这个报错我原文留档了。它不是偶发,是这台机器对 objects.githubusercontent.com 这条链路不稳。我一开始以为是网络断了,试了换 UA、加 --http1.1,都不行。最后绕过去的办法是走 gh-proxy.com 镜像,我本机跑这条,同样一条命令加个前缀,18.2 秒下完 41664768 字节。这段我记下来,因为「装不上」本身也是一手信息:谁在国内网络下 GitHub release 二进制,都可能撞上这一下。

我解压出来一个 99MB 的单文件 moli,不带任何运行时。--version 一打,moli 1.1.24。

抓页面,默认不渲染

它的 CLI 就一个 fetch 子命令管抓取。我照 README 的例子抓 example.com,dump 成 markdown。

moli fetch --dump markdown --wait done https://example.com

输出是整页的 markdown 文本。这条跑通,说明它的 V8、DOM、CSS 这一整套 web runtime 是真的在转,不是给个假的 HTTP 客户端。我还顺手跑了 moli fetch --help,它列出一串输出格式,markdown、semantic_tree_text、screenshot、screenshot_full、pdf,外加页面加载和响应的等待参数。这里有个细节,输出第一行有个 `` 残留,是没清干净的 HTML 标记。我一开始以为抓坏了,仔细看是它的 markdown 序列化还在打磨。这个意外我记下了。

更值得写的是 semantic_tree_text 这个输出,我跑 --dump semantic_tree_text,抓的还是 example.com。它不给人类读的 markdown,给一段机器友好的语义树,每个节点带类型和 backendNodeId,paragraph 下面挂 StaticText,link 下面挂 StaticText。

- RootWebArea: Example Domain [backendNodeId=2000000001]
  - paragraph [backendNodeId=2000000004]
    - StaticText: This domain is for use in documentation examples...

我跑出来的是这段。这个结构是给模型看的,不带视觉噪音。我的判断是,给 agent 做检索流水线,语义树比 markdown 更省 token,因为它把「这段文字是什么角色」直接标出来了。

截图是按需的,不是默认的

这是 Moli 跟 Chromium 最根本的区别。Chromium 每次导航都维护一整套渲染状态。Moli 默认 LayoutPolicy::Mock,几何信息是 mock 的,不真算布局。只有你加 --layout,它才真算布局、真光栅化一帧。

moli fetch --layout --dump screenshot https://example.com > page.png

我跑这条,出来一个 1920x1080 的 PNG,93761 字节。file 命令一看,确实是 8-bit RGBA。这一帧是「按需」产生的,用完就丢。README 的原话是 paint results are never reused,渲染结果从不复用。这个设计对爬虫、检索、评测这些负载划算,它们绝大多数请求根本不需要像素。

那组内存数字,是真的吗

README 里有一组数字我反复打开看了几遍,就是那组 192 个公开 URL 的混合爬取。它还补了一句,WPT 兼容性那套,一轮跑下来过了 1.612 million 个测试,这个我拉原文核过。Moli 有 103 页产出有意义内容,成功率 53.6%,median 1.43 秒,median RSS 73 MiB。对照 Chrome headless,101 页,52.6%,1.43 秒,773 MiB。成功率基本打平,内存差了十倍。这组数是 README 官方标的,不是我测的,出处我标在这里。

更细的一组在 sample agent workload 里,也是 README 官方给的:CDP ready 34.85ms 对 Chromium 的 169.37ms,峰值 PSS 102.46 MiB 对 348.82 MiB,进程线程 1/24 对 11/123。这组数字指向同一件事:它省的不是渲染质量,是「没必要的渲染」。我信这组数字的方向,但留一句:Lexbench 那组 1308 任务里 Moli 是 81.88%,Chrome 是 99.85%,差了 18 个点。省内存是有代价的,兼容性它还没追平 Chrome。

给个判断。你的场景是爬取、检索、评测、浏览器 agent 这种批量活,而且不要像素级还原,Moli 值得上,它 73 MiB 对 773 MiB 的内存差,跑几千页就省下一台机器。你的场景是给用户截图看、要 Canvas 和 WebGL 高清还原,那别碰,README 自己写了它不追求和 Chrome 像素级一致。这个分界不模糊。

一句话。Moli 把「渲染」从默认变成了付费选项,这是浏览器自动化里少见的一次清醒。省内存是结果,真正的判断是:大多数 agent 要的是结构,不是画面。