最近,我在做一件挺有意思的事,今天来给大家分享了:
我为 Tapero 撸了一个网页快照生成器。
一开始的动机其实很简单 —— 网页实在太大了!
你如果直接把一整个页面的 HTML 丢给 AI,让它去理解、定位、操作,基本等于在烧钱。
一个稍微复杂一点的页面,动不动就几千上万行代码,里面还夹杂着各种无关的 div、style、script,对 AI 来说全是噪音,但对 Token 来说全是成本呀。
这就变成了一个很现实的问题:
AI Agent 想做网页自动化,但每走一步都要喂这么一坨东西进去,不仅贵,还不稳定(AI 也会误判)。
所以,网页快照这个东西就变得很关键了。你可以把它理解成 —— 给页面做一层压缩且结构化的描述,只保留对自动化真正有用的信息。AI 不再看原始 HTML,而是看这份精简版的快照。
业界是怎么做的?
其实这个方向已经有人在做了,比如 Playwright MCP(鼻祖),还有后来 Agent Browser 的实现。
它们大多是基于浏览器的 Accessibility Tree 来生成快照。
简单说,Accessibility Tree 是浏览器为了无障碍访问(比如屏幕阅读器)生成的一棵语义树。它会把页面里的元素转成“角色 + 文本 + 状态”这样的结构,比如按钮、输入框、标题之类的。
这种方式有个很大的优点:语义清晰,噪音很少。
它们生成的快照大概长这样(简化示意):
- heading "Welcome to Amazon"
- textbox "Search"
- button "Search"
- link "Today's Deals"
或者更结构化一点:
role=button name="Submit" focused=true
role=textbox name="Email" required=true
我这里有一张 Agent Browser 实际生成快照的截图,咱们来看看:
嗯,看起来很干净,对吧?对 AI 也很友好。
但它也有一个问题:信息被过度抽象了。很多原始 HTML 里的结构信息,比如层级关系、属性细节,有时候就丢了。这就可能造成误判,比如页面有两个一样的按钮,AI 不知道应该点哪个?
我的实现:基于 DOM 的快照
好,一番调研后,终于轮到我动手了,现实却给了我一拳。
我当前的执行环境,拿不到 Accessibility Tree —— 它需要更高的权限(我不想申请这个权限),或者直接在浏览器内核层做。
再加上,我其实也不太满意 Accessibility Tree 的信息损失,它太偏语义了。但在做自动化的时候,有些原始信息却很重要,比如:
- 明确的层级结构、布局结构
- 元素的属性(id、name、type 等)
- 一些不在语义树里的元素(很多非标网页没有注明语义)
所以我走了另一条路:直接基于 DOM 来生成快照。
思路很简单:
- 遍历 DOM 树
- 过滤掉无关节点(script、style、div 等)
- 提取元素的关键属性 + 文本
- 压缩成一个结构化的文本表示层
经过 3 次迭代后,我最后选择 Pug 子集来作为快照的格式,看起来长这样:
page(ref="n1" title="Demo" lang="en" url="https://example.com")
form#feedback(ref="n2" inviewport)
input(ref="n3" type="email" name="email" placeholder="Email" required)
button(ref="n4" type="submit" inviewport) Continue
我也对同一个网页生成了快照,来和 Agent Browser 对比一下:
可以看到,信息更加丰富了,但一点都不乱。当然了,我也提供了选项来输出极简快照。但我更愿意信息丰富一点,牺牲一点点 Token,让 AI 识别起来更准些!
这个格式有几个我自己挺喜欢的点:
- 结构一眼能看懂(缩进就是层级)
- 信息保留得比较完整(标签 + 属性 + 文本)
- 扩展了状态属性(如 inviewport, disabled, hidden 等)
- 对 AI 也很友好(比 HTML 干净太多)
这套实现的优势
- 保留了更多原始信息,AI 做定位更稳
- 不依赖浏览器底层能力,通用性极强
- 可控性高,想加什么字段都可以自己扩展
面临的挑战
-
伪交互元素需要自己推断
比如一个用普通 div 实现的按钮,语义上它不是 button,但行为上是,这类判断需要额外逻辑去补。
-
执行效率需要控制在 150ms 内
页面越大、机器性能越差,遍历和裁剪的成本就越高,这里其实挺考验工程优化的。
我踩过的一些坑
-
隐藏元素要不要保留?
有些下拉菜单默认是 hidden,但点击后会出现,保留则多增加体积,不保留则会影响 AI 的下一步推断。
-
超大数据列表要不要修剪?
比如无限滚动的列表,如果全保留,体积直接爆炸,但裁剪又可能丢关键数据。这就需要做个平衡,增加额外的逻辑来推断,是全保留,还是抽样保留。
-
嵌套 iframe 要不要递归处理?
不处理会丢信息,处理又可能带来性能问题和跨域限制。还有网页中有大量广告(基本都是通过 iframe 注入的)的情况,会严重拖慢快照的生成。
-
In Viewport 判定策略
只保留可视区域可以大幅压缩体积,但有时候关键元素在首屏外。所以,我把它做成状态,让 AI 知道哪些元素在视口中?这样可以做出更好的推断。
-
Shadow DOM 的处理策略
很多现代组件都藏在里面,不展开就“看不见”,展开又会让结构变复杂。还有一些节点是 closed 的,不让看。好在最终都找到了合适的处理方案。
-
节点修剪策略
怎么从 1000+ 节点里筛到 200~300 个,是个很微妙的平衡。需要先对所有节点分类,再通过模式分层控制,决定输出哪些节点。
实测效果
说了这么多,来点实际的。我拿几个典型页面跑了一下,下面我们来看看测结果。
1. X 的个人主页
- 页面结构高度嵌套
- 随着往下滚动,节点数量会越来越多
以下是跑出来的结果:
- 耗时:18ms
- 遍历节点:989
- 保留节点:207
- 压缩率:≈ 2%
对于这个结果,我其实挺满意的,基本把网页信息压到了一个 AI 可以接受的范围。
2. Amazon 商品详情页
- 页面结构非常复杂,节点爆炸
- 需要重点保留:标题、价格、图片、购买按钮等信息
以下是跑出来的结果:
- 耗时:36ms
- 遍历节点:3509
- 保留节点:649
- 压缩率:≈ 4%
这个效果已经远远超出我的期望了,极大的压缩了网页的体积。
3. Google 搜索结果页
- 结构相对规整,但信息密度高
- 重点是结果列表 + 标题 + 链接
以下是跑出来的结果:
- 耗时:24ms
- 遍历节点:1633
- 保留节点:280
- 压缩率:≈ 2%
快照出来之后,基本可以直接用于“抓取搜索结果”的自动化任务。
最后
这一轮做下来,我对网页快照的感受是:
它是 AI Web Agent 的地图,有了它 AI 才能规划出执行路线。
它不能太复杂,体积不能太大,AI 的上下文有限,且 Token 也不便宜。
它也不能太简单,遗漏关键信息,相当于给 AI 一版错误的地图,它再聪明也很难做出正确的事。
好了,今天就到这!感谢有 Codex 和我结对编程,才能把这个快照生成引擎做出来!
我现正在公开做一个 AI Web Agent,我会把它从 0 到 1 的整个过程都记录下来。包括技术选型、踩坑、各种取舍,都会慢慢写出来。
如果你也在做类似的事,或者对这个方向感兴趣,欢迎来聊聊。