郭立 (leeguoo)

# 给 agent 取网页数据,我试过三种笨办法,最后只留下一种

塞整页 HTML 烧上下文、一个字段一次 find/get 往返、手搓 eval,这三种取数办法我都试过,都别扭。留下的是 chrome-use 的 site 和 extract,背后是同一个判断:取数该是一次声明式求值,不是一串命令式往返。这篇从真实输出讲清两者怎么用、extract 的 schema 到底怎么写(我一开始就写错过),以及它取不到、不该用的地方。

2026年7月2日 · 文章 · 公开

本页目录

给 agent 取网页数据,我前后试过三种笨办法。

最直接的是把整页 HTML 塞给模型让它自己读。一个中等页面几万 token,数据还没取到,上下文先烧掉一半,模型还经常读串行。第二种是自己写 CSS 选择器,一个字段一次 find 再一次 get,十几个字段就是十几次往返,慢又啰嗦。第三种是手搓一段 JS eval 塞进页面跑,能用,但每个页面都得重写,取属性、取多值全得自己招呼。

绕一圈留下的,是 chrome-use 的两个命令:已知站点用 site,任意页面用 extract。它们背后是同一个判断——取数应该是一次声明式的求值,不是一串命令式的往返。

一条命令从真实已登录 Chrome 拿到干净 JSON,整页 HTML、失效的 selector、反爬墙这三条旧路都被划掉

site:已知站点,别自己写选择器

打开一个 chrome-use 认识的站点,它自己会提示有 adapter 可用:

$ chrome-use open https://news.ycombinator.com
💡 site adapters for news.ycombinator.com — prefer these for structured data:
   hackernews/thread, hackernews/top

adapter 是针对具体站点预先写好的取数逻辑,你不用管 HN 的 DOM 长啥样、改过几版:

$ bash
chrome-use site hackernews/top --json

拿回来每条是完整的,不只是标题:

$ json
{ "rank": 1, "title": "ZCode – Harness for GLM-5.2", "url": "https://zcode.z.ai/en",
  "author": "chvid", "score": 329, "comments": 258, "id": 48753715,
  "hn_url": "https://news.ycombinator.com/item?id=48753715" }

adapter 真正的价值,是把"页面结构会变"这件事从你身上挪走:HN 改版,adapter 跟着修,你的 site hackernews/top 一个字不用动。代价是它只覆盖内置的那些站点(先 chrome-use site 看有哪些)。

两个真会踩的小地方:数据在 .data.result.posts,不在返回顶层,配 jq 得写全路径;chrome-use 会往 stderr 打一行版本提示,它不影响 stdout 的 JSON,想要绝对干净加个 2>/dev/null

extract:任意页面,你给一份选择器 schema

没现成 adapter 的站,用 extract。关键先纠正一个误解(我一开始就写错过):它不是"用自然语言描述你要什么",而是给一份 CSS 选择器的 schema:

{
  "rows": "<css>",           // 有 rows:重复容器 → 返回数组;没有 → 对整页返回一个对象
  "fields": {
    "name": ".title",                          // 简写:选择器 → trim 后的文本
    "href": { "sel": "a", "get": "@href" },    // 取属性
    "tags": { "sel": ".tag", "get": "text", "all": true }  // 每个匹配都要 → 数组
  }
}

get 可选 text(默认)/ @属性 / html / value。抓 HN 首页每条的标题和链接,行容器是 .athing:

$ bash
chrome-use extract --schema '{"rows":".athing","fields":{"title":".titleline a","url":{"sel":".titleline a","get":"@href"}}}'
$ json
[ { "title": "ZCode – Harness for GLM-5.2", "url": "https://zcode.z.ai/en" },
  { "title": "Oomwoo, an open-source robot vacuum you build yourself", "url": "https://makerspet.com/blog/..." } ]

不写 rows 就对整页返回一个对象,适合抓页面级字段(h1link[rel=canonical] 这种)。

值得多想一层的是它的执行方式:整份 schema 被编译成一次页面求值,不是前面那种一个字段一次往返。十几个字段也是一趟拿回来,快,而且行为确定,同一份 schema 打在同一个页面上,结果稳定可复现。这就是声明式在取数上比命令式顺的地方:你描述结构,它一次算完。选择器不会写?先 snapshot -i 看结构,或在 DevTools 里点一下目标元素拿它的 selector。

它取不到的,和它不该用的

单容器模型也有够不着的时候。HN 的分数就不在 .athing 这一行,而在紧跟的下一行 .subline,所以上面那个例子只拿到了标题和链接。凡是"一条数据拆在两行 DOM"的结构,rows/fields 这套就取不全,这恰恰是 adapter 存在的理由(site hackernews/top 已经把两行拼好了)。

反过来,有些场景根本不该开浏览器:一个公开静态页的几个字段,curl + jq 更快;想知道"有哪些页面"该用搜索引擎。extract / site 真正的主场,是要登录、要跑 JS、会被反爬拦的页面。因为 chrome-use 操作的是你此刻登录着的真实 Chrome,那些数据本来就在你眼前渲染好了,它只是把屏幕上的东西读成 JSON。CreepJS 实测机器人分 0%,不是靠伪装,是因为那本来就是你的浏览器。

取数这件事,难的从来不是解析,是先到能解析的那一步。前半段登录、渲染、过检测交给你自己的浏览器;后半段,site 给你拼好的记录,extract 让你用一份选择器一次取干净。

← 上一篇
给 agent 选浏览器:Playwright 那套和 chrome-use 不是一回事
下一篇 →
改网络请求和响应:chrome-use network route 详解

评论

评论发布后会立即公开,如触发规则可能被审核下架。

最多 1000 字。