1. 从7.1秒订机票说起:浏览器Agent到底在解决什么真问题
第一次看到“7.1秒完成机票预订”这个数字,我的反应是:这大概率又是一个精心挑选过的Demo。做过自动化的人都知道,网页自动化最不稳定的环节从来不是“点按钮”本身,而是页面加载、元素定位、异步渲染、验证码、风控拦截这一连串的连锁反应。7.1秒能跑完一次完整的机票预订流程,意味着这个系统在“感知页面”和“决策动作”这两个环节上,一定做了和传统方案完全不同的取舍。
传统浏览器自动化,无论是Selenium、Playwright还是Puppeteer,本质上是“脚本驱动”:你提前把每一步写死,元素选择器、点击顺序、等待时间全部硬编码。这套东西在页面结构稳定的内部系统里很好用,但一旦面对携程、去哪儿、航司官网这种频繁改版、A/B测试、动态加载的页面,维护成本会指数级上升。而浏览器Agent的思路完全不同——它不预设动作序列,而是让模型在每一步“看”当前页面,然后从一组可执行动作里挑一个最合理的执行。这就是标题里“动态索引动作空间”的核心含义。
所谓动态索引动作空间,拆开看是三个词:动作空间、索引、动态。动作空间指的是Agent在当前页面状态下所有可执行操作的集合,比如“点击某个按钮”“在某个输入框填入文本”“滚动到某个区域”“提取某段文本”。索引指的是给这些动作编号,让模型输出一个离散的token而不是自由文本,这样既降低了输出难度,又提高了可解析性。动态则是指这个动作集合不是固定的,而是随着页面状态实时变化的——登录页的动作空间和搜索结果页的动作空间完全不同。
这套机制解决的核心问题是:让Agent在不确定的页面环境里,用最小的决策开销找到下一步该做什么。7.1秒这个数字之所以有意义,是因为它把“感知-决策-执行”这个循环压缩到了人类几乎无感的程度。下面我会从动作空间的设计、动态索引的实现、机票预订这个具体场景的拆解,以及实测中踩过的坑几个角度,把这套东西讲透。
2. 动作空间不是菜单:动态索引背后的设计取舍
2.1 为什么不能把整个页面的可点击元素直接丢给模型
最直觉的做法是:把页面上所有可交互元素(按钮、链接、输入框)全部提取出来,连同它们的文本、位置、属性一起塞进prompt,让模型选一个。这个方案在元素数量少的时候能跑通,但机票预订页面动辄几百个可交互元素,光是把这些元素的描述塞进上下文就要消耗大量token,而且模型在几百个选项里做选择的准确率会明显下降。
更致命的是,很多元素的文本是重复的或者无意义的。比如一个航班列表里,每个航班卡片都有一个“选择”按钮,文本完全一样,模型无法仅凭文本区分它们。这时候就需要引入位置信息、父节点信息、相邻文本等上下文,才能让模型做出正确判断。但上下文一多,token又爆炸了。
所以动态索引动作空间的第一层设计,是先做一轮粗筛。系统会根据当前任务阶段(比如“正在搜索航班”还是“正在填写乘机人信息”),只保留和当前阶段相关的动作类型。搜索阶段只保留输入框和搜索按钮,选择阶段只保留航班卡片上的选择动作,填写阶段只保留表单输入和提交动作。这一层筛选把候选动作从几百个降到十几个,模型的选择难度大幅下降。
2.2 动作的粒度:点一下还是点哪里
动作粒度是另一个容易被低估的设计点。同样是“点击”,可以设计成三种粒度:
- 元素级点击:模型输出一个元素ID,系统点击该元素的中心点。
- 坐标级点击:模型输出一个(x, y)坐标,系统直接点击该位置。
- 语义级点击:模型输出一个语义描述,比如“点击价格最低的航班的选择按钮”,由系统负责解析成具体元素。
元素级点击最稳定,但依赖元素提取的完整性;坐标级点击最灵活,能处理Canvas渲染或元素提取失败的情况,但准确率依赖模型的视觉定位能力;语义级点击最接近人类表达,但解析层需要额外的逻辑,容易出错。
Jev Ultrafast这类系统通常采用元素级为主、坐标级兜底的混合策略。正常流程走元素级,当元素提取失败或者模型置信度低时,切换到坐标级。这个切换逻辑本身也需要设计,不能简单地“失败就切”,否则会在两个模式之间反复横跳,浪费时间。
2.3 动态索引的实现:给动作编号而不是给动作命名
动态索引的关键操作是:把筛选后的动作列表按顺序编号,然后让模型输出编号。比如当前有8个候选动作,模型输出“3”,系统就执行第3个动作。这样做的好处是输出空间被严格限制在有效范围内,模型不可能输出一个不存在的动作。
但这里有个细节:编号的顺序会影响模型的偏好。如果总是把“搜索”按钮排在第一个,模型可能会形成“优先选第一个”的偏见。所以索引顺序需要每步随机打乱,或者在训练时加入顺序扰动,让模型真正基于页面内容做选择,而不是基于位置。
另一个细节是动作的参数。点击动作不需要参数,但输入动作需要知道填什么内容。这时候索引只负责选“在哪个输入框输入”,具体输入的内容由另一个模块(通常是任务规划器)提供。这种职责分离很重要,否则模型既要选动作又要生成内容,输出复杂度会翻倍。
3. 机票预订场景拆解:7.1秒里到底发生了什么
3.1 从打开页面到搜索结果页:感知与决策的循环
假设任务是“帮我订一张明天从北京到上海的最便宜机票”。Agent的第一步是打开航司或OTA的首页。这时候页面上的动作空间包括:出发地输入框、目的地输入框、日期选择器、搜索按钮,可能还有登录弹窗、Cookie同意弹窗、促销广告遮罩。
一个设计良好的Agent会先处理干扰项。Cookie弹窗和促销遮罩如果不关掉,后续的元素定位会被遮挡,点击会失败。所以动作空间里需要包含“关闭弹窗”这个动作,而且它的优先级应该被动态提高——系统检测到有遮罩层时,自动把关闭动作排到候选列表前面。
处理完干扰项后,Agent依次在出发地输入框填入“北京”,在目的地输入框填入“上海”,选择日期,点击搜索。每一步都是一次“感知-决策-执行”循环。7.1秒的时间预算里,页面加载和异步渲染占了大头,真正的模型推理时间可能只有几百毫秒。这意味着系统的瓶颈不在模型,而在页面交互的物理延迟。
3.2 航班列表页:动态索引最吃力的地方
搜索结果页是动态索引动作空间压力最大的场景。一个典型的航班列表页有20到50个航班卡片,每个卡片上有航班号、起降时间、价格、舱位、选择按钮。如果直接把所有选择按钮丢给模型,模型需要在几十个几乎一样的选项里选出“最便宜”的那个,这要求模型不仅能理解“最便宜”这个约束,还要能比较各个卡片上的价格数字。
这里的做法通常是两阶段决策:第一阶段,系统提取所有航班卡片的结构化信息(航班号、价格、时间),用一个轻量级的排序或筛选逻辑找出候选航班;第二阶段,只把候选航班对应的选择按钮放进动作空间,让模型做最终确认。这样既降低了模型负担,又保证了决策质量。
但这里有个坑:有些OTA会把“推荐”航班排在前面,价格并不是最低的,但视觉上更突出。如果Agent只依赖视觉显著性做决策,很容易选错。所以结构化信息提取这一步不能省,必须把价格、时间这些关键字段解析出来,作为决策依据。
3.3 填写乘机人信息和支付:动作空间最复杂的阶段
选完航班后进入填写页面,这里的动作空间最复杂。表单字段包括乘机人姓名、证件号、手机号、邮箱,可能还有保险选项、座位偏好、餐食偏好。每个字段的输入方式不同:文本框直接输入,下拉框需要先点击再选择,日期选择器需要点日历。
这个阶段动态索引的策略是按字段分组。系统先识别出当前页面有哪些必填字段,然后逐个处理。处理一个字段时,动作空间只包含和这个字段相关的动作(点击输入框、选择下拉项、确认日期),其他字段的动作暂时不放进候选列表。这样模型每一步只需要在3到5个动作里做选择,准确率和速度都有保障。
支付阶段是最敏感的,通常不会让Agent完全自动执行,而是走到支付页面就暂停,交给人工确认。这既是安全考虑,也是合规要求。7.1秒的计时通常不包括支付环节,到“生成订单”或“进入支付页”就算完成。
4. 实测中那些文档不会告诉你的坑
4.1 页面加载完成的判断:等元素出现还是等网络空闲
自动化脚本最常见的失败原因是“页面还没加载完就操作”。Playwright提供了waitForLoadState('networkidle'),但这个策略在机票页面上经常失效,因为OTA页面有大量轮询请求和广告追踪请求,网络永远不会真正空闲。
更可靠的做法是等待目标元素出现,而不是等待网络状态。具体来说,在每一步动作执行前,系统先确认目标元素已经可见且可交互(isVisible()和isEnabled()都为true),再执行动作。如果超时,就重新感知页面,更新动作空间。这个重试逻辑必须要有,否则一次加载慢就会导致整个流程失败。
4.2 元素定位的稳定性:ID会变,文本也会变
OTA页面的元素ID通常是动态生成的,每次刷新都不一样。class名也可能被压缩混淆。相对稳定的定位依据是文本内容和相对位置。比如“搜索”按钮的文本通常是固定的,“选择”按钮虽然文本重复,但它在航班卡片内的相对位置是固定的。
所以元素提取时,不能只依赖CSS选择器,还要提取元素的文本、aria-label、role、以及相对于父节点的路径。这些信息一起构成元素的“指纹”,用于在页面刷新后重新定位。指纹匹配需要容错,允许部分字段变化,只要核心字段匹配就算命中。
4.3 模型输出的解析容错:编号越界和格式错误
即使做了动态索引,模型偶尔还是会输出越界的编号,或者输出一段解释文字而不是纯编号。解析层必须能处理这些情况:如果输出不是有效编号,就回退到规则策略(比如选第一个可点击的搜索按钮),或者重新请求模型。重试次数要有上限,避免死循环。
另一个容错点是动作执行失败。点击了但没反应,或者输入了但值没进去。这时候不能直接判定任务失败,而应该重新感知页面,检查动作是否生效。如果没生效,把同一个动作再执行一次,或者换一个等价动作(比如用回车代替点击搜索按钮)。
4.4 时间预算的分配:7.1秒是怎么省出来的
7.1秒听起来很快,但拆开看并不神秘。页面加载通常占2到3秒,模型推理每一步100到300毫秒,动作执行每一步50到200毫秒。如果整个流程有10步,模型推理总共1到3秒,动作执行总共0.5到2秒,加上页面加载,7秒左右是合理的。
省时间的关键在于减少不必要的步骤。比如不要每步都重新提取整个页面的元素,而是只提取变化区域。不要每步都调用大模型,简单动作(比如关闭弹窗)可以用规则处理。不要串行执行可以并行的操作,比如填写多个输入框可以连续执行,不需要每填一个就重新感知。
5. 动态索引动作空间的边界与适用场景
5.1 它擅长什么:高频、结构相似、容错率高的任务
动态索引动作空间最适合的场景是流程固定但页面实现多变的任务。机票预订、酒店查询、表单填写、后台管理系统操作都属于这一类。这些任务的步骤是明确的,但每个网站的实现方式不同,传统脚本需要为每个网站写一套代码,而Agent可以用同一套逻辑适配多个网站。
另一个适合的场景是需要一定判断力的操作。比如“选择最便宜的航班”需要比较价格,“选择靠窗座位”需要理解座位图,“填写报销单”需要根据发票内容选择费用类型。这些判断用规则很难穷举,但模型可以处理。
5.2 它不擅长什么:强风控、高精度、长流程的任务
动态索引动作空间在遇到强风控时会很吃力。有些网站会检测自动化行为,比如鼠标移动轨迹、点击间隔、浏览器指纹。Agent的点击通常是瞬移式的,间隔也很规律,容易被识别。虽然可以通过模拟人类行为来缓解,但这会增加时间开销,7.1秒的纪录就很难保持了。
高精度任务也不适合。比如金融交易、医疗诊断这类容错率极低的场景,Agent的一次误点可能造成严重后果。这类场景需要人工确认环节,或者用更严格的规则约束动作空间。
长流程任务同样不友好。步骤越多,累积误差越大,每一步95%的准确率,20步之后整体成功率就降到36%了。所以动态索引动作空间更适合短流程、高容错的任务,长流程需要拆分成多个短流程,中间加入检查点。
5.3 和传统RPA的关系:不是替代,是补充
很多人把浏览器Agent和RPA对立起来,其实两者是互补的。RPA擅长稳定、重复、大批量的操作,成本低、速度快、可预测。Agent擅长处理变化、需要判断、低频的操作。实际落地时,通常是RPA负责主干流程,Agent负责处理异常分支和页面改版后的适配。
比如一个机票比价系统,可以用RPA定时抓取固定几个网站的价格,当某个网站改版导致RPA失败时,触发Agent去重新学习页面结构,生成新的选择器配置,再交回给RPA执行。这样既保留了RPA的效率,又利用了Agent的适应性。
6. 如果你想自己复现一套,从哪里下手
6.1 最小可行原型:三个模块加一个循环
自己搭一套动态索引动作空间的Agent,不需要一开始就追求7.1秒。最小可行原型只需要三个模块:页面感知模块负责提取可交互元素和页面状态;动作索引模块负责筛选和编号候选动作;决策模块负责调用模型选择动作。然后套一个循环:感知、索引、决策、执行、再感知。
页面感知可以用Playwright的page.query_selector_all配合自定义的过滤规则,提取按钮、链接、输入框的文本和属性。动作索引先用简单的规则筛选,比如只保留当前视口内的元素、只保留可点击的元素。决策模块可以先用一个小的本地模型或者调用API,prompt里包含任务描述和编号后的动作列表。
6.2 从规则兜底开始,逐步引入模型
不要一上来就全部交给模型。先用规则跑通主流程,比如“找到文本为‘搜索’的按钮并点击”“找到第一个文本包含‘最低价’的航班卡片并点击”。规则跑通后,把规则覆盖不到的分支交给模型。这样既能保证基础流程的稳定性,又能逐步积累模型决策的训练数据。
规则和模型的切换策略也很重要。我的经验是:高置信度走规则,低置信度走模型,模型也低置信度就暂停并请求人工。置信度可以基于规则的匹配程度、模型的输出概率、以及历史成功率来综合计算。
6.3 日志和回放:没有这两样就没法调优
Agent的调试比传统脚本难得多,因为每次执行的路径可能不同。所以日志必须记录每一步的页面快照、动作空间、模型输出、执行结果。有了这些日志,才能回放失败案例,分析是感知错了、索引错了、还是决策错了。
回放功能可以基于Playwright的trace功能实现,它能把每一步的DOM快照、网络请求、截图都保存下来。出问题时,打开trace文件就能看到当时的页面状态和操作序列,定位问题非常快。这个投入在项目初期可能觉得麻烦,但一旦流程复杂起来,没有trace几乎没法维护。
7. 关于速度与准确率的个人体会
7.1秒这个数字,我倾向于把它理解成一个“上限展示”,而不是“日常水平”。实际跑起来,网络波动、页面改版、风控拦截都会让时间上下浮动。追求极致速度的前提是准确率已经稳定在可接受的范围,否则快而错没有意义。
我在类似项目里的体会是:先把准确率做到90%以上,再优化速度。优化速度的手段包括缓存页面元素、并行执行独立动作、用规则替代简单决策、减少不必要的页面重感知。但这些优化都要在准确率不掉的前提下做,每加一个优化就回归测试一遍。
另一个体会是,动作空间的设计比模型选型更重要。同样一个模型,动作空间设计得好,准确率能差出20个百分点。动作空间要足够小,让模型容易选;又要足够全,覆盖所有必要操作;还要有优先级,让关键动作(比如关闭弹窗)在合适的时候排到前面。这个设计没有标准答案,需要根据具体场景反复调。
最后,别忽视失败恢复。Agent不可能每次都成功,关键是失败后能不能优雅地恢复。比如点击没反应就重试,页面跳转错了就回退,验证码出现了就暂停等人工。一个能处理失败的Agent,比一个只会跑成功路径的Agent实用得多。