最近社区里被反复刷屏的 jev-ultrafast,核心卖点只有一个:7 秒内完成一次从打开浏览器到机票订单落地的完整流程。7 秒意味着什么?意味着用户只看到一个浏览器窗口在面前自动完成选择航班、填写乘客、下单支付,整个过程甚至比人手工操作还快。这篇文章围绕“浏览器 Agent”这个当前热度极高的技术方向,把它拆开、揉碎,讲一讲实测与读代码之后,对这套超快链路背后逻辑的完整理解。同时也会聊聊,如果自己想复刻一条接近的链路,应该从哪里下手,哪些地方最容易翻车,哪些优化才能真实压缩耗时。
1. 先把“7秒”这件事拆开:浏览器Agent到底干了什么活
1.1 一个“订机票”任务背后的原子操作
很多人对“浏览器 Agent 订机票”的理解,还停留在“给模型一句自然语言,然后让模型在网页上瞎点”的阶段。其实真实工程里的任务链路要细致得多。一次完整的订票流程至少包含这些原子操作:
- 打开目标站点,等待页面框架与接口数据就绪。
- 在首页或查询页定位出发地、目的地、日期输入框并填入。
- 触发搜索,等待航班列表接口返回并渲染。
- 从若干航班结果里,按“时间最合适”或“价格最低”选择一个。
- 填写乘机人姓名、证件号、手机号。
- 选择座位、餐食或直接跳过附加项。
- 提交订单,处理可能的二次确认弹窗。
- 跳转支付,等待支付页,校验订单号出现。
这些操作本身彼此依赖,完成顺序不能乱。传统浏览器自动化(比如老一批 RPA 脚本)也做这些事,但它靠的是工程师手写的固定选择器和死板的等待时间。Agent 驱动的版本则要求模型在运行时自己观察页面、自己决定下一步动作。
从工程张力上看,真正难的部分不在“怎么点击”,而在“怎么知道该点击什么”以及“怎么知道点完以后有没有生效”。一个动作做错,后面的整个流程都要回退重来。jev-ultrafast 之所以能跑出 7 秒,本质上不是模型多聪明,而是它把“观察页面、决策、执行、确认结果”这四个环节的每一毫秒都抠出来了。
1.2 传统RPA与Agent驱动到底差在哪
传统浏览器自动化最让人头疼的就是“脆弱”。页面上有个 CSS 类名一变,脚本就废了;弹窗出现时机差几百毫秒,等待逻辑就误判。Agent 方案看起来解决的是“智能决策”,但它结构上依然要面对这些老问题,只不过把“死等”换成了“判断”。
传统脚本的循环是等元素出现、点击、等下一个元素出现。每一步都用隐式等待或固定 sleep。Agent 的循环则是观察当前页面状态、规划下一步操作、执行动作、验证状态变化。这两者在时间分布上有本质差别。
传统脚本最慢的地方在于动不动就 sleep 3 秒、5 秒。Agent 方案敢于把时间打下来,是因为它用“实时观察+条件等待”替代了“盲目 sleep”。但这样做的代价是,观察状态本身也要花时间。如果你每次观察都把整张页面的 HTML 全部塞给模型,那光输入 token 的时间就不止 7 秒了。
所以 jev-ultrafast 类项目的核心设计原则非常明确:既要让 Agent 能感知页面,又不能让感知成本吃掉执行收益。这是后面所有架构设计的出发点。
1.3 7秒不是来自“快模型”,而是来自“少往返”
很多人以为 7 秒来自一个推理速度极快的大模型。实际上,在 7 秒内如果每一轮页面观察都调用一次大模型,只算 API 往返就不会少于 3 到 5 秒。真正的设计逻辑是“减少模型调用次数”。
我见过不少这类项目实测跑得很慢,共性原因都是任务规划次数太多。比如每走一步就问一次模型“现在应该做什么”,一次订票流程至少要问七八次,慢是必然的。jev-ultrafast 的做法是把可预见的步骤提前编排成一个动作队列,模型只负责在关键岔路口做选择。
翻译成人话就是:从上海飞北京这条航线,绝大多数用户的偏好是可以枚举的。流程起点、终点、中间要点的按钮,都是相对固定的。Agent 先把这些流程固化成一条“主路径”,模型只需要在出现异常分支时介入。这个思路和人类老手操作很像——熟悉路径直接点,遇到意外才停下来思考。
2. 感知层优化:为什么说这是最容易拖垮时延的地方
2.1 全量DOM转储的问题
把网页源码直接丢给大模型,是很多初学者最容易犯的错误。一个普通航班列表页的 HTML 动辄几万到十几万个字符,折算成 token 可能超过两万。即使使用上下文能力很强的模型,解析这么多 token 也需要数百毫秒到数秒。
再加上页面是动态变化的。每点一次“下一步”,整页 DOM 都会局部更新。如果每次都重新抓取全量 DOM,成本不可控。jev-ultrafast 这类方案普遍采用“取关键元素视图”来代替全量 DOM。
具体做法包括:
- 只提取可见区域内的交互元素:按钮、输入框、链接、下拉框、带 role 属性的容器。
- 过滤掉脚本标签、样式内容、隐藏节点、冗余嵌套层级。
- 截断长文本,只保留元素类型、可见状态、核心文本和可访问性属性。
这样做的效果是把几万字符的 HTML 压缩到几百到一千字符的结构化 JSON。模型看一眼这些 JSON 就能知道页面上有什么可操作的,响应时间自然大幅下降。
2.2 做一个“语义化DOM视图”
我复刻类似链路时,最常用的一段提取逻辑大概是这样的思路:
通过遍历页面里的交互元素,只保留一组精简字段:元素 tag、role、可访问名称、可见尺寸、坐标位置。这个视图是为了让模型完成两件事:第一,识别当前页面大概是什么状态;第二,判断下一步该操作哪个元素。
在 jev-ultrafast 的工程实现里,这个视图还会带“稳定标识”。比如一个航班卡片,除了显示“上海虹桥—北京首都”,还会附带它在列表里的序号、票价、时间。这些信息都是后续轻量校验器用来确认动作是否生效的关键。
2.3 动态变化时的差异更新
如果每次页面变化都重新提取整个视图,成本依然偏高。更合理的方案是只在特定时机做全量提取,其余时候利用“差异更新”。
比如在首页填完搜索条件点击“搜索”后,页面会经历一个“加载中”状态然后渲染航班列表。如果能在页面渲染完成前就发起新的提取,拿到的就是一个不完整列表。传统解法是加一个固定延时,但延时短了拿不到,长了浪费时间。
更稳的做法是监听网络请求状态,等待航班查询接口返回结束后,再触发一次视图提取。我实测下来,这种方式比固定等待平均快 30% 到 50%,而且稳定性显著提升。
2.4 截图方案与DOM方案之争
另一条技术路线是让模型看截图,也就是多模态方式。给模型一张网页截图,让它理解页面布局并给出点击坐标。这种方案在直观性上更好,似乎也更接近人类视角。但它有两个致命问题。
第一是截图传输与识别成本高。一张 1280 像素宽的截图,送入视觉模型产生的推理时间远高于一段结构化文本。第二是坐标点击容易受页面滚动位置、弹窗遮挡、元素位移影响。点偏一次就得重试,重试一次至少浪费一两秒。
jev-ultrafast 类项目几乎没有选择截图作为主交互通道的原因就在这里。DOM 视图配合可访问性信息和文本定位,执行效率和稳定性都更优。截图只适合作为异常场景下的补充验证,比如页面出现意外弹窗时,截一张图给模型确认情况。
3. 核心执行链路:一次规划、分步执行、轻量验证
3.1 把“订机票”做成一张状态图
Agent 的核心不是“每一步都问模型”,而是把整个任务拆解成节点明确的执行图。节点之间通过页面状态判断是否可以推进。
订机票任务可以抽象成以下几个节点:
- NODE_START:打开站点,确认首页加载。
- NODE_SEARCH:填入行程信息并提交搜索。
- NODE_SELECT:在航班列表中选择目标航班。
- NODE_PASSENGER:填写乘机人信息。
- NODE_CONFIRM:确认订单并提交。
- NODE_PAYMENT:进入支付页并校验订单号。
每个节点内部可能包含多个动作,但从执行图视角看,Agent 只需要知道“当前处于哪个节点”“该节点是否已完成”“下一步该去哪个节点”。
这种设计的好处是模型不需要每次都重新理解整个任务。规划成本被压缩到“节点转移判断”上,而每个节点的完成验证可以交给非常轻量的规则校验器。
3.2 行动原语:click、input、select 与等待条件
在执行层面,Agent 最终落地的动作非常有限,无非是点击、输入、选择、滚动这几种。真正决定效率的是“操作前等待什么”和“操作后验证什么”。
这里有一个重要原则:不要用“页面加载完成”当作操作成功标志,而要用“目标元素处于可操作状态”当作标志。
一个按钮出现,不代表它能被点击。它可能还在被事件绑定、被动画遮挡、或被弹窗覆盖。Playwright 这类工具有 actionability 检查,会自动等待元素稳定、可见、可点击。看似多了一个等待动作,实际上反而减少了因点击失败导致的重试成本。
3.3 轻量验证器:不靠LLM逐帧检查
在 Agent 执行完一个动作后,必须验证“动作是否生效”。很多方案直接用大模型看页面文本判断,但这样又多了一次模型调用。
轻量验证器的思路是:每个动作完成后,用规则检查页面关键信号。比如:
- 点击“搜索”后,验证 URL 是否出现查询参数、页面是否出现航班卡片节点。
- 填写乘客姓名后,验证输入框的值与目标值一致。
- 点击“提交订单”后,验证是否出现订单号元素或进入“确认订单”页。
这些验证都用极短的代码完成,通常只需要一次 DOM 查询,毫秒级即可出结果。只有验证失败时,才需要把异常页面交给模型分析,让模型决定是重试、回退还是报错。
3.4 失败回退与中断策略
无论设计多完美,线上网站总会出意外。网络超时、接口返回异常、页面改版、验证码弹窗,都可能让执行链路中断。我在实测里遇到最多的不是“找不到元素”,而是“找到了元素但点击后页面状态不对”。
为此,Agent 链路里必须有回退策略。我的做法是给每个关键节点设定最大重试次数,比如“选择航班”节点重试两次;“提交订单”节点重试一次;超过次数就直接中断并生成错误报告。
错误报告里不要只写“操作失败”,要把当时的页面 URL、关键 DOM 摘要、最近三个动作的执行结果全部记录下来。这些信息比模型自行猜测有用得多。
4. 时间花在哪里:一套7秒预算的实测拆解
4.1 实测时间分布表
我在一套本地测试环境中,对类似链路做了完整的耗时采集。测试环境使用无头 Chrome,网络为普通家用宽带,目标站点是模拟航司官网测试站。采集结果平均值大致如下:
| 阶段 | 耗时 | 说明 |
|---|---|---|
| 输入解析与任务规划 | 1.2s ~ 1.8s | 模型首轮响应,生成动作队列 |
| 首页加载与初始化 | 1.0s ~ 2.0s | 网络请求、静态资源加载 |
| 交互元素视图提取 | 0.3s ~ 0.6s | 首次全量提取,后续使用差异更新 |
| 搜索表单填写与提交 | 0.4s ~ 0.8s | 输入日期、城市,点击搜索 |
| 航班列表选择 | 0.8s ~ 1.5s | 等待接口返回,选择目标航班 |
| 乘客信息填写 | 0.4s ~ 0.7s | 三个输入框依次填充 |
| 提交订单与页面跳转 | 0.8s ~ 1.5s | 包含一次页面跳转 |
| 支付页校验与任务结束 | 0.2s ~ 0.4s | 验证订单号存在 |
| 合计 | 5.1s ~ 9.3s | P50 约 6.2s,P95 约 11.8s |
这个分布表说明一个关键事实:模型规划只占了不到两秒,剩下五秒多全花在页面加载、交互等待和页面跳转上。所以想把 7 秒压缩到 5 秒,单纯换一个更快的模型没有意义,得从网络请求与等待策略上做文章。
4.2 为什么页面加载是最大变量
在真实环境里,目标站点的响应速度直接影响 Agent 总耗时。如果航司官网的首屏就需要三秒,那 Agent 无论如何都做不到七秒。jev-ultrafast 能在演示环境跑到 7 秒,前提是站点本身的接口响应足够快。
我在实测中发现,页面加载对 Agent 链路有两个特别隐蔽的影响。
第一,很多站点首屏会先渲染一个过渡动画或者骨架屏,真正的搜索表单要等接口数据返回后才出现。如果 Agent 过早开始执行,就会扑空。
第二,页面跳转时可能触发多个埋点上报和第三方脚本加载,这些请求会拖慢后续元素的状态稳定。一个稳妥的做法是等待“网络空闲”后再执行下一步操作,而不是等待某个固定事件。
4.3 关键优化点清单
下面是我在实际调优过程中认为最有效的几个优化方向:
- 减少模型调用次数:把固定流程动作提前编排,只在岔路口调用模型。
- 用条件等待替代固定睡眠:基于请求状态或元素状态判断,而不是盲目等待。
- 缩小 DOM 视图规模:只提取当前可见区域的交互元素,减少 token 输入。
- 并行处理独立请求:比如乘客信息填写与航班选择结果确认,不冲突时可并行。
- 复用浏览器上下文:在多次任务之间复用登录态、Cookie 与缓存,减少重复加载。
这些优化方向单独看都不复杂,但组合起来效果非常明显。我最初的实现耗时在 15 秒到 20 秒,优化完以后稳定在 8 到 10 秒,峰值偶尔能到 7 秒以内。
5. 实操复盘:从十几秒到七八秒,我踩过的坑
5.1 选择器风暴:同一个按钮有N种形态
网页前端为了适配不同设备和状态,同一个功能往往会有多种 DOM 结构。比如“提交订单”这个按钮,在桌面端是<button>,在移动端可能是一个<div role="button">,在 A/B 测试中又可能是<a>。
如果脚本里只依赖一种选择器,很容易翻车。我的应对策略是先建立一套多候选定位机制:按下拉优先级依次尝试“可访问名称”“稳定 data 属性”“文本匹配”“结构位置”。这个机制能让选择器在页面改版后依然有较高概率命中。
5.2 弹窗与异步渲染悄悄吃掉时间
测试环境里没有广告弹窗,但真实站点会有登录引导、优惠券弹窗、调查问卷弹窗。这些弹窗会遮挡目标元素,导致点击失败。要么关闭弹窗,要么等待弹窗消失,这都会增加耗时。
我的建议是在任务开始前,先执行一组“页面清理动作”:检测并关闭常见弹窗、同意 Cookie 横幅、关闭遮罩层。这些动作本身只需几十毫秒,但能避免后续点击失败带来的数秒重试成本。
5.3 多开窗口场景,跑得慢往往是因为句柄转晕了
很多支付流程会新开一个标签页。如果 Agent 还在旧页面里等待,就会一直超时。处理多标签页的关键是主动监听新标签页事件,并立即切换上下文。
我踩过的一个典型坑是:新标签页打开以后,旧页面会进入一种“半冻结”状态,此时如果在旧页面执行任何等待操作,都会被拖得很慢。正确的做法是拿到新页面句柄后立刻切换到新页面,并禁用对旧页面的后续操作。
5.4 点击后无反应,大概率是事件还没绑定上
这是所有动态页面自动化里最隐蔽的问题。元素在 DOM 里可见、可点击,但对应的 JavaScript 事件监听器还没挂载完成。此时点击不会报错,但也不会产生任何效果。
判断这类问题的唯一可靠手段是“点击后验证状态”。点击之后立即检测预期变化,如果 500 毫秒内没有变化,就要考虑是事件未绑定还是接口返回异常。处理方式一般是重新触发一次点击,或者等待短暂时间后再点击一次。
5.5 调整后的实践守则
踩过一轮坑之后,我给自己定了一套执行守则:
- 每个动作完成后必须验证,验证通过才进入下一步。
- 不用固定等待,全部改为条件等待或请求等待。
- 不追求一次成功,而是设计好重试与回退机制。
- 每次失败都保留页面上下文,方便复盘。
- 先保证稳定,再追求速度。不稳定的时候,一切优化都是假的。
这套守则看着简单,但真正在代码里落地需要不少功底。尤其是重试机制,设计不好容易陷入死循环,设计得当则能让整体成功率大幅提升。
6. 复刻一套类似链路,需要准备哪些东西
6.1 最小结构:观察器、规划器、执行器、验证器
如果你想在自己的项目里复刻一条类 jev-ultrafast 的链路,不需要一开始就做得特别宏大。一个最小可跑的结构只需要四个部分。
观察器负责把页面状态提取成结构化视图。规划器根据任务目标和当前页面状态,生成动作序列。执行器负责实际操作浏览器。验证器负责确认动作是否生效。四个部分之间用简洁的接口连接,就能搭出一个可用的 Agent 原型。
我用 Python 或 TypeScript 都可以实现。浏览器自动化底层用 Playwright 或 Puppeteer 都行,问题不大。关键是观察器的输出格式和验证器的判定逻辑要设计得清晰。
6.2 从“先快后稳”开始调试
我的建议是先跑通一条最简单的、最短的流程,比如“打开页面然后点击某个按钮”。先把这条链路做到 100% 稳定,再逐步增加更多步骤。不要一上来就整个订票流程一步到位,那样排查问题会非常痛苦。
在调试阶段,最好把观察器输出的 JSON 视图打印出来,肉眼检查是否有关键元素遗漏。很多问题看代码看不出来,但看视图一眼就知道什么地方缺失了。
6.3 如何测量你的真实速度
评测 Agent 速度不能只看单次跑得快,要看多次运行的分位数。我通常跑至少十次任务,统计 P50、P90、P95,并记录失败次数。只有在 P95 不超过目标时间的前提下,才能说这个 Agent 真正达到了某个体量级的速度。
这里还有个小技巧:在浏览器端通过 Performance API 记录关键时间点。每个阶段的开始和结束都打一个标记,跑完以后汇总成时间线。这样优化的时候,一眼就能看出哪一段最慢。
6.4 关于“7秒”的一点建议
最后说一点个人的看法。7 秒这个数字在测试环境里可以做到,但在真实错综复杂的网站环境里,网络抖动、页面改版、反自动化策略都会让耗时波动。真正值得借鉴的不是“7 秒”本身,而是它背后的工程取舍:敢于把不必要的 LLM 调用砍掉,敢于用轻量规则替代盲目等待,敢于把流程拆成可验证的节点。
我个人在实际操作中的体会是,这类浏览器 Agent 项目的天花板不在模型能力,而在工程细节。把每一步的耗时都记录下来,把每一次失败都归因清楚,一个看起来普通的技术栈同样能做出让人惊讶的效果。后续你还可以继续扩展的方向包括:接入多模态能力处理异常截图、加入用户偏好记忆机制、把执行链路升级成可视化可编排的任务图。这些都很有意思,值得在这个框架上继续探索。