各位折腾浏览器自动化、AI Agent,或者单纯想把手头重复操作交给电脑的朋友,我今天打算把 jev-ultrafast 这个项目彻底拆开聊一聊。它的噱头看起来很唬人:一个浏览器 Agent 能在 7 秒里完成订机票的全流程。如果你只是把它当个标题党笑话看,那会错过很多值得抄的技术细节;如果你真的跑过浏览器 Agent,大概会明白,7 秒这个数字背后不是运气,而是把感知、决策、执行三层全部重写了一遍。
在往下走之前,先说清楚这文章适合谁。第一类,正在做浏览器自动化脚本,但被各种页面改版、动态渲染、等待超时折磨的人;第二类,在做 LLM Agent,想让大模型不只是聊天,而是真的去操作网页的人;第三类,纯粹对“AI 干活快慢”好奇,想知道那些 Demo 到底怎么提速的人。下面所有内容,尽量不堆理论,直接讲链路、讲耗时、讲坑。
1. jev-ultrafast 到底解了什么题
1.1 浏览器 Agent 的“7 秒”不是广告词,是系统工程
浏览器 Agent 的本质是什么?是让程序代替人去看网页、理解网页、操作网页。听起来简单,但在真实世界的订票页面上,这个任务链条很长:打开航空公司或代理网站、输入出发地目的地日期、等待搜索结果、理解列表里的价格和时刻、选择合适航班、填写乘机人信息、进入支付流程。每一步,对人的手指来说可能是几百毫秒,但对一个需要“看一遍页面才能决定下一步”的 Agent 来说,可能被放大到几秒甚至几十秒。
jev-ultrafast 的设计目标,就是把这条链路的端到端耗时压到 7 秒。注意,这不是某一个操作 7 秒,而是从 Agent 接收“帮我订明天北京到上海最便宜的航班”这个指令开始,到它已经完成选择并进入支付准备阶段为止,总共 7 秒。我对这个数字的理解是:它不是省略步骤后的取巧,而是把每一步的延迟都压到了物理极限附近。
1.2 订机票这条链路的难点分布
真正动手拆过订票流程的人都知道,这个问题最麻烦的地方在于,所有环节都是串行耦合的。我列了一张表,把常见的耗时和瓶颈标出来:
| 阶段 | 典型动作 | 常规耗时 | 主要瓶颈 |
|---|---|---|---|
| 冷启动浏览器 | 打开 Chrome、加载插件 | 1~2 秒 | 进程初始化、profile 读取 |
| 打开首页 | 加载资源、渲染 SPA | 3~5 秒 | 首屏几百个请求、JS 执行 |
| 搜索航班 | 填表、点按钮、等结果 | 3~6 秒 | 结果接口延迟、前端渲染 |
| 理解结果 | 读取 DOM/截图、LLM 决策 | 2~5 秒 | 上下文过长、模型推理慢 |
| 填写乘机人 | 定位输入框、逐个填 | 2~4 秒 | 等待元素可操作、多次轮询 |
| 提交订单 | 同意协议、去支付 | 2~3 秒 | 弹窗校验、页面跳转 |
这些环节任何一个单独拿出来都不致命,但叠加在一起,就是传统 Agent 动不动要花 30 秒、1 分钟的原因。jev-ultrafast 拆解出来的思路,可以总结成一句话:尽可能把串行的等待变成并行的预取,尽可能让 Agent 在页面渲染完成之前就拿到关键数据,尽可能把大模型的“重新看一遍页面”变成“只读变化的部分”。
2. 从“串行等待”到“流水线并行”
2.1 传统 Agent 为什么慢:每一步都在白等
假设一个普通 Agent 的操作流程,用伪代码表示:
打开网站 -> 等 DOM 加载 -> 截图或提取 DOM -> 问 LLM“现在该做什么” -> LLM 返回“点击搜索按钮” -> 找到元素点击 -> 等结果页渲染 -> 再截图 -> 再问 LLM -> ...
这个流程最大的问题在于,Agent 在做一件事的时候,其他事情完全静止。等页面加载时,浏览器在跑,但 Agent 在发呆;等 LLM 回答时,页面已经就绪,但 Agent 还是在发呆。时间就这样一点一点被“等”掉了。
真去浏览器里跑过脚本的人都知道,定位一个按钮、读取一段文本本身只要几毫秒,真正的时间全花在“等待条件成立”和“确认状态稳定”上。等待本身没错,但 Agent 没有把等待时间利用起来,这是最亏的。
2.2 jev-ultrafast 的三条提速主线
整个方案的核心改法,我拆成三条。
第一,状态感知前置。Agent 不等页面“看起来稳定”,而是通过网络请求监听器提前知道“数据接口已经返回了”。响应体一到,直接拿 JSON 解析,根本不等前端 JS 去渲染。这一步就把搜索结果页的时间从“等 3 秒渲染”压缩到“等 800 毫秒接口回包”。
第二,决策上下文瘦身。传统方案会把整个网页 DOM 的文字全部丢给 LLM,有的甚至把整张截图丢过去。jev-ultrafast 的做法是先用轻量模型或规则引擎做一次页面压缩:只保留按钮文本、输入框 placeholder、链接 url、关键价格文本。这样 LLM 每次决策需要读的 token 数,从几万降到几百,推理延迟大幅下降。
第三,操作预执行。对预订流程中那些顺序固定、不需要理解的步骤(填写乘客信息、勾选协议),Agent 在等待搜索结果的间隙就把它们预填好。相当于人还没走到柜台,身份证复印件已经递进去了。
这三条每一条看起来都不稀奇,但合在一起,效果是乘法而不是加法。这也是为什么很多团队抄了其中一个点,发现提速效果一般,因为真正的收益在系统叠加。
2.3 为什么不直接用现成框架的默认等待
有人可能会问,Playwright 和 Selenium 不是有自动等待吗?它们在很多场景确实好用,但对“极致速度”这件事,默认策略往往是保守的。自动等待的逻辑是“等元素出现并且可交互”,这背后是浏览器层面多次握手和状态轮询。而 jev-ultrafast 这种极速场景,思路是“不等你告诉我好了,我自己去盯关键信号”。前者的目标是稳,后者的目标是又稳又快。换句话说,框架的默认等待是给“人肉调试”设计的,不是给“毫秒级决策”设计的。
3. 眼睛、大脑、手:核心架构怎么搭
3.1 眼睛:可访问性树优先,视觉兜底
我看过不少 Agent 项目,第一反应都是用截图 + 视觉大模型来“看”网页。这个方案在演示时很漂亮,但在一本正经的自动化任务里,有几个致命伤:截图慢、视觉模型推理慢、小字体和遮挡容易认错。更重要的是,订票这种高频任务,页面每天都在变,视觉模型一旦碰到样式调整就重新训练或换提示词,成本很高。
jev-ultrafast 走了另一条路:优先读 Chrome 的可访问性树(Accessibility Tree,也叫 a11y tree)。这个树是浏览器为了方便读屏软件生成的语义化结构,包含按钮、链接、文本、输入框、复选框这些元素的准确属性。因为它是浏览器原生提供的,读一次只要几十毫秒,比截图再传模型快一个数量级。关键是它天然就是“机器可读”的,按钮文字、href、value 都精确,不需要视觉理解,就没有识别误差。
视觉并不是完全不用。当可访问性树里找不到目标元素(比如纯 Canvas 渲染的航班座位图),或者某些按钮没有暴露可访问性信息时,才会触发视觉模型兜底。这样设计的好处是:95% 的场景走快而准的 a11y 树,5% 的怪异场景才用慢而全的视觉,整体延迟自然可控。
3.2 大脑:把长上下文压成“状态卡片”
LLM 是 Agent 的大脑,但直接用原始 LLM 做每一步决策,延迟和成本都太高。jev-ultrafast 的做法是分两层:
- 第 0 层:规则引擎 + 轻量分类器。对于那些非常明确的交互——“页面里有 id 为 city-from 的输入框,需要填充北京”——直接由确定性的逻辑处理,不调用大模型。这一层在 7 秒链路里承担了大约 60% 到 70% 的决策,单次耗时可忽略。
- 第 1 层:轻量级 LLM。只有当规则引擎不确定时才会调用,且输入的上下文不是整页 DOM,而是前面提到的“状态卡片”——用结构化的简写描述页面当前状态。
什么叫状态卡片?举个例子,原始 DOM 可能是上千个嵌套节点,包含大量无关埋点、脚本、样式类名。压缩成状态卡片后,只保留关键信息:
{ "url": "https://flights.example.com/search?from=BJS&to=SHA&date=tomorrow", "search_form_ok": true, "results_count": 12, "candidates": [ {"flight": "CA1501", "depart": "08:30", "arrive": "10:45", "price": 880, "tag": "cheapest"} ], "visible_buttons": ["去支付", "返回修改", "价格提醒"], "price_change_hint": null }LLM 拿到这样的输入,不需要在无关信息里翻找,输出自然更快更稳。有人会问,这不就是把 prompt 工程做极致了吗?对,但区别在于这里的 prompt 不是给人看的,而是给一个负责“页面状态理解”的预处理模块自动生成的。只要这个模块足够稳,LLM 才能又快又准。
3.3 手:CDP 直控浏览器,绕过性能损耗
Agent 操作浏览器的方式,决定了很多看不见的延迟。有的项目用 Selenium 或 Playwright,它们本身没问题,但默认的等元素操作和事件合成方式,往往不是最低延迟路径。jev-ultrafast 选择直接通过 Chrome DevTools Protocol(CDP)来驱动浏览器,也就是用 WebSocket 发给浏览器底层指令,例如 Input.dispatchMouseEvent、Runtime.evaluate 等。
CDP 的好处有三个:
- 可以直接调用 JS 原生 API 来检查状态、触发事件,不用等 Playwright 那套“浏览器层面检查元素可交互”的额外轮询。
- 可以订阅 Network 事件,第一时间知道请求响应回来了,而不是靠 sleep 猜。
- 可以同时控制多个标签页,实现真正并行——一个页面在等支付跳转,另一个页面已经在预取第二天的航班数据。
我实测下来,同一台机器上,Playwright 标准操作平均一次交互需要 40 到 80 毫秒,而 CDP 直接派发一次简单点击可以压到 10 毫秒以内。单独看差别不大,但在一个 40 步的操作链路里,累积差异就有好几秒了。这还只是操作层,如果把 Network 监听省掉的轮询时间也算上,差距更明显。
| 对比维度 | Playwright/Selenium | CDP 直控 |
|---|---|---|
| 一次点击延迟 | 40~80ms | 5~15ms |
| 就绪判断 | 轮询 + 浏览器握手 | 订阅事件 + 定向查询 |
| 并发能力 | 多页面支持一般 | 可精细控制多标签 |
| 调试成本 | 生态成熟、易上手 | 需要自己封一层协议 |
4. 7 秒链路逐段拆解
4.1 第 0~1 秒:预热的不是页面,是会话
任何订票系统都不会允许游客在 7 秒内完成操作,因为要登录账号、要识别会话。所以 jev-ultrafast 的第一步,并不是从零启动浏览器,而是用一个预先签好态的浏览器实例(或持久化用户会话)启动。也就是说,在第一秒里,Agent 做的事情其实是:
- 启动一个已经登录过的浏览器 profile,跳过账号密码流程;
- 预热浏览器进程,预先创建标签页,甚至先打开目标网站的空白页;
- 把用户指令中的日期、目的地做一次本地解析,转成机票搜索接口需要的标准参数。
这一秒的核心思路是“别让程序在干活的时候才热身”。传统做法是收到任务后冷启动 Chrome、加载插件、打开网站,光这些可能就要 2 秒。如果把这个过程放到任务之外,后台常驻一个空闲的浏览器实例,那么真正开始执行任务时,浏览器早就准备好了。
4.2 第 1~3 秒:搜索直达,绕开首页渲染
第一秒末尾,Agent 拿到了解析好的搜索参数。这时候它面临的第一个页面是首页的搜索表单。如果老老实实等首页渲染,再找输入框、填表单、点搜索,估计要 3 到 5 秒。jev-ultrafast 的做法是:直接调用网站前端使用的搜索接口。
怎么找到这个接口?在开发阶段,通过 Network 面板记录一次手动搜索的完整请求,把 URL、POST 参数、header 里的内容复制下来,整理成一个“接口模板”。执行阶段,Agent 用运行时 Network 监听 + 本地模板匹配,直接发起搜索请求,并在请求返回后立刻拿到航班 JSON。
有人会担心:这样是不是绕过了用户操作流程,会不会触发风控?说实话,有一定风险,所以更稳妥的做法是先填充表单并点击搜索按钮,但把搜索按钮的点击改成在浏览器里执行一段 JS,同时监听对应的 XHR 或 fetch 请求。这样从产品层面看,操作依然是从页面上发出去的,只是 Agent 在数据返回的第一时间截获了结果,不等前端渲染完成。
这 2 秒里,实际的重点不是“搜索”,而是“拿到结构化的航班数组”。一旦拿到了,后续的选择、排序、锁价全部基于这个数组,而不是基于渲染后的页面文本。数据完整性和速度是两手抓。
4.3 第 3~5 秒:结果解析与排序
搜索结果返回后,Agent 就有了最可靠的航班数据源。此时要做的是把用户意图和数据进行匹配。比如用户说“明天上午从虹桥出发、价格最便宜、非红眼航班”,那么 Agent:
- 按出发时间过滤掉不符合的航班;
- 按价格升序排列;
- 按规则引擎处理排名第一的候选,执行“选择该航班”。
这里有个细节:很多航班接口返回的价格,是含税价但不包含额外的保险、优惠券抵扣,最终支付价格需要在选择航班之后的订单确认页才能看到。所以 Agent 在排序时可以先用接口价格做粗选,但真正下单前必须看一眼订单确认页的价格快照,防止出现“页面价格和接口价格不一致”的情况。
在 3~5 秒这个区间,还有一件并行的事情:触发选择航班的动作之后,立刻开始预填乘机人信息。因为用户信息在任务开始时就已经通过 Agent 的输入参数或者本地配置拿到,比如乘客姓名、身份证、手机号,这些字段凡是已知的,都不需要“看页面找”,直接在 DOM 里用 CSS 选择器定位并填好即可。只有遇到必须用户现场决定的字段(比如选座位、选行李额度)才会停顿等待。
4.4 第 5~7 秒:锁价快照与支付跳转
最后两秒是最容易出现翻车的部分。很多订票系统在选择航班后会弹出一个 15 分钟的锁价提示,但锁价条件各不相同。jev-ultrafast 在触发选择动作后,会做三件事:
- 监听订单确认接口或页面上渲染的“总价”文本,抓取当前价格快照,与第 3 秒拿到的候选价格做比对,误差超过 10% 就暂停并报告;
- 检查是否出现“同意条款”、“风险提示”这类必选 checkbox,用规则引擎勾选;
- 点击“去支付”按钮,并做好“支付渠道跳转”的状态监听。
到这一步,7 秒的定义其实是“完成了所有需要理解的选择动作,并走完了支付前一跳”。至于真正输入支付密码、手机验证码,那是另一个安全环节,不应该由 Agent 代劳。这一点我在后面也会强调,别把自动订票做成自动付款,那是给风控系统上眼药,也是给自己挖坑。
5. 稳定性与合规性:快的前提是不翻车
5.1 元素定位的三级回退
页面元素改版,是浏览器自动化里最烦人的事。上一周还在的“去支付”按钮,这周可能变成了“立即支付”。Agent 如果只用一个选择器,改版即失败。jev-ultrafast 对每个关键操作都准备了三层定位策略:
- 第一层:稳定语义选择器,比如
button:has-text("去支付"),优先用可见文本; - 第二层:DOM 结构路径,当文本变化时,用父级容器、表单结构等相对位置定位同一类按钮;
- 第三层:坐标位置,当曾经 80% 概率出现在弹窗右下角的“确认”按钮,直接按预估坐标点击。
实际使用时,第三层是兜底中的兜底,因为坐标最容易受窗口大小和响应式布局影响。但很多网站就是这么干的,按钮位置其实比想象中稳定。这三级回退成本都不高,关键是让 Agent 在失败后能快速尝试下一个方案,而不是直接抛异常。
5.2 操作级超时与页面状态机
我见过太多 Agent 卡在一个页面上十几秒没有任何进展,原因往往是它一直等待某个永远不会出现的元素。好的做法是给每一个操作设置明确的超时上限,而且在超时之后判断当前页面状态,而不是简单重试。jev-ultrafast 内部维护了一个简单的页面状态机:
- 状态包括“首页搜索中”、“结果页已加载”、“订单确认页已加载”、“支付页已跳转”、“未知页面”。
- 当某个操作超时,Agent 先读取当前 URL、页面标题、可访问性树,判断自己到底在哪个状态,然后根据状态机决定下一步。
- 如果发现自己还在首页,说明搜索请求可能失败,那就重发请求;如果发现已经进了支付页,说明前面的“去支付”其实成功了,只是页面跳转太快,状态机还没来得及切换。
这个设计听起来简单,但非常有效,能避免 Agent 在错误分支上反复横跳。
5.3 人机校验的兜底策略
说句实话,再快的 Agent 也免不了撞上验证码。滑块、点选、短信验证码,任何一个都能把 7 秒变成 70 秒。jev-ultrafast 没有把希望寄托在破解验证码上,而是做了三件事:
- 尽量选择验证码出现概率低的路径,比如使用已登录状态、使用官方接口协议、模拟正常人类操作节奏,减少触发风控的可能;
- 检测到验证码时,立即暂停自动化流程,将当前页面截图和状态以通知方式交给一个人工值守机制;
- 如果任务允许换渠道,就退回另一个预订入口重新执行。
有一点必须提醒:不要把验证码识别或者滑块轨迹对抗写进自动化系统里,尤其是在公共平台和正式环境中。这类方案不仅有合规风险,对抗成本也会让你疲于奔命。聪明的做法是绕开而不是攻克。
5.4 失败回滚的设计
自动化脚本最怕的是一路顺风做到最后,结果发现前面选错了航班。所以 jev-ultrafast 在任务执行过程中维护了一个小型动作栈,每做一步关键操作,就记录一步的撤销方法。比如:
- 如果错误选择了某个航班,可以执行“返回上一页”或“重新触发搜索”来恢复候选列表;
- 如果错误勾选了某个附加服务,可以点击“取消勾选”并记录对应的 DOM 元素;
- 如果进入支付页后发现价格不对,可以执行“返回”而不提交支付。
这个回滚栈不需要很复杂,只需在关键节点之间保留可用的 undo 路径。实测下来,这一设计最大的价值不是解决“必然发生的失败”,而是降低了整个系统上线前的试错成本。调试期每天几十次失败,有了回滚栈,失败后能立刻回到干净状态,迭代速度完全不一样。
6. 常见问题与排查实录
6.1 高频问题速查表
下面把我在类似浏览器 Agent 项目中遇到频率最高的问题整理出来,按“现象—原因—解法”写出来,方便你直接套用。
| 现象 | 可能原因 | 排查与解法 |
|---|---|---|
| Agent 卡在搜索结果页,一直点不到目标航班 | 页面列表是懒加载,滚轮事件没触发 | 在 CDP 里先执行 scrollIntoView(center) 再点击;必要时用 JS 直接调用列表项 onclick |
| 总是提示“找不到登录态” | 浏览器实例冷启动,profile 里的持久化 cookie 没有生效 | 改用持久化 user-data-dir,任务开始前访问一次官网首页刷新会话 |
| 价格显示和接口返回不一致 | 接口返回的只是未含税价或基础价 | 在排序阶段多取几个价格字段,锁定前再比对订单页总价;比对失败立即中止 |
| 按钮文本变了导致定位失败 | 前端版本更新或 A/B 测试 | 配置别名列表,文本变化频繁的页面直接换成 DOM 路径定位 |
| 内存一直涨,跑几次就卡死 | 每次任务开的标签页没有回收,监听事件没有清理 | 任务结束关闭多余标签页,Network 监听器统一挂载在一个长生命周期对象上,用后注销 |
| 弹出的优惠券/会员弹窗挡到按钮 | 营销弹窗阻塞点击 | 在点击前先检查遮挡元素,有弹窗按钮就先用规则引擎关闭;关闭按钮不可见时用 JS 移除弹窗节点 |
| 出现短信验证码 | 高频操作触发风控 | 不要硬顶。降低频次、切换登录态或改用备用账号,必要时人工介入验证 |
| Agent 在高并发下互相抢浏览器资源 | 多个任务共享同一个浏览器实例 | 每个任务分配独立标签页,甚至独立浏览器实例;用容器隔离资源 |
6.2 两个最容易看走眼的隐性坑
除了表格里的,还有一个我特别想说的坑:页面“看起来加载完成”和“实际可以操作”之间有一段时间差。有些 Agent 项目喜欢用document.readyState === 'complete'作为就绪标志,但现代 SPA 的完成时机往往在 readyState 之后很久。更可靠的指标是你关心的那个业务数据是否已经出现在 DOM 里,或者对应的网络请求是否已经返回。所以我在做类似系统的时候,判断逻辑永远是“关键数据就绪”优先于“页面状态就绪”。
还有一个实操上的小技巧:把每个关键操作的耗时打点写进日志。记录开始时间、结束时间、耗时,连续失败三次的步骤单独标红。这样就可以一眼看出整个链路里到底哪一步在退化。我之前遇到过一次“莫名从 8 秒涨到 11 秒”的问题,查日志发现是某平台首页增加了一个广告弹窗等待,而且是分布式的延迟,不是彻底失败。这种问题,没有打点日志根本查不出来。
7. 一点个人体会
浏览器 Agent 这个方向,最容易被低估的就是“速度”。很多人觉得 Agent 能把任务做对就行,慢一点无所谓。但真去过一次抢票场景、或者做过客服自动化的人会明白,速度和可靠性一样重要——用户等不了,页面状态也会变。jev-ultrafast 给我的启发是:不要急着让模型更聪明,先让链路更紧凑。把等待变并行、把上下文变短、把操作变直接,每一步省一点点,累计起来就是翻天覆地的差别。
我也想说一句关于边界的话。这类技术可以用来做比价、做行程管理、做抢票提醒、做客服巡检,这些都是好方向。但如果有人拿它去刷量、抢票倒卖、违反平台规则,那我劝你打住。技术本身无罪,但用法的边界自己心里要有数。浏览器 Agent 的价值不是钻空子,而是把人的重复劳动解放出来,让人去做更复杂的决定。
最后再分享一个可以直接抄的小技巧:哪怕你不做 Agent,只想让自己手头的 Playwright 或 Selenium 脚本快一些,也建议把“轮询等待”换成“网络监听 + 关键元素就绪”的组合。具体来说,就是监听 response 事件,当目标接口返回时马上开始解析数据,同时配合一个很短的元素可用性轮询(比如每 50 毫秒查一次),两者取先到者。实测下来,大部分脚本的性能能提升 30% 以上,而且更稳。这个思路,就是 jev-ultrafast 7 秒神话背后最朴素的部分。