AgentBrowser获取最上层元素
问题:Agent-browser如何动态获取页面元素,如最上面一层的元素?
agent-browser 获取页面元素的核心机制,我可以用一句话概括:它不解析整个DOM树,而是扫描页面的“无障碍树”(Accessibility Tree),生成一份仅包含可交互元素的精简清单,并为每个元素打上 @e1、@e2 这样的唯一标签。
这就解决了你说的“获取最上面一层元素”的需求——snapshot 命令返回的正是页面上所有“可交互”的表层元素清单。
一,核心机制:Snapshot(快照)
agent-browser 的设计哲学是为 AI Agent 提供“所见即所得”的确定性操作,它通过以下三步完成元素的发现和定位:
步骤
命令/操作
作用与原理
输出示例
1. 生成快照
agent-browser snapshot -i
扫描页面的无障碍树,过滤掉纯文本或装饰性元素,只提取按钮、输入框、链接等可交互元素。
- textbox "用户名" [ref=e1]
- button "登录" [ref=e2]
2. 分配引用
工具自动完成
为快照中的每个元素分配一个以 @e
开头的唯一ID(ref)。这个ID在当前页面状态下是稳定的。
ref=e1
,
ref=e2
3. 操作元素
agent-browser click @e1
AI Agent 直接使用 ref进行操作,完全不需要关心元素的id、class或 XPath。
执行点击、填表等操作
特别注意(v0.22.0版本更新):从v0.22.0开始,snapshot -i 命令默认会包含所有鼠标可交互的元素(如可点击的div),不再需要额外的 -C 标志,进一步降低了AI的认知负担。
🔄 动态获取元素的关键:Snapshot-Action 循环
你提到的“动态获取”,关键在于理解 snapshot 是一个状态性的、会过期的快照。
工作流遵循一个严格的循环:生成快照 → 基于引用操作 → 状态失效 → 重新生成快照。
二,为什么必须重新快照?
一旦你点击了一个链接或提交了一个表单,页面跳转或DOM结构发生重大变化,之前所有的 @e1、@e2 引用都会指向旧页面的元素,在新页面上完全失效。因此,每次页面状态改变后,AI Agent必须重新执行 snapshot 命令,获取一份新的元素清单。
💡 实战示例:获取并操作顶层元素
# 1. 打开目标网页 agent-browser open https://example.com/login # 2. 获取当前页面的可交互元素快照 agent-browser snapshot -i # 👇 命令输出(AI 看到的“最上面一层元素”) # - textbox "Email address" [ref=e1] # - textbox "Password" [ref=e2] # - button "Sign in" [ref=e3] # - link "Forgot password?" [ref=e4] # 3. AI 决定操作这些元素 agent-browser fill @e1 "user@example.com" agent-browser fill @e2 "mySecurePassword" agent-browser click @e3 # 4. 点击登录后,页面跳转,旧的 refs 失效,需要重新获取快照 agent-browser snapshot -i # 现在会返回新页面(如用户仪表盘)的可交互元素清单
三,总结与建议
agent-browser 的动态元素获取机制,核心就是 snapshot + ref。与传统方式相比,它有以下几个显著优势:
低Token消耗:无障碍树比完整DOM树小得多,对AI Agent非常友好。
高稳定性:不依赖易变的CSS/XPath,只要元素的可访问性属性(如role, name)不变,ref就能稳定定位。
- 自愈能力:如果页面发生变化导致元素失效,Agent只需重新执行 snapshot,就能获得新的有效引用,完美契合你之前提到的“低维护成本”和“自愈能力”需求。
建议你后续在进行自动化编排时,务必在每次可能引起页面变化的操作(如点击、表单提交)之后,立即加入一个 snapshot 调用来刷新AI对页面的“认知”。
四,如何获取最上层元素?
由于Agent-browser获取的是整体页面元素,如果页面上有弹层,抽屉,你就会发现内容会非常多。而我们要操作的只是抽屉或是弹层上的元素,从snapshot输出来看,最上面的元素内容在最后面输出,所我们需要想办法动态截取最上面的元素内容。
我们可以通过下面的函数来实现:
def splitSnapShot(self,snapshot): """ 按指定的规则拆分snapshot :param snapshot: :param specnum: :return: """ lines = snapshot.splitlines() spline=list() tmpline=list() ckline=1 for line in lines: stripped_line = line.lstrip() leading_whitespace = line[:len(line) - len(stripped_line)] if len(leading_whitespace)==2 : # tmpline.append(line) tsline="".join(tmpline) if len(tsline)>0: tsline="\n".join(tmpline) tmpck=tsline.replace("- generic","").strip() if len(tmpck)>0: spline.append(tsline) tmpline[:] = [] #把等于2的信息拼到下个数据字段中 2026-04-17 tmpline.append(line) # if len(leading_whitespace)>=specnum: else: tmpline.append(line) ckline=ckline+1 if len(tmpline)>0: tsline="\n".join(tmpline) tmpck=tsline.replace("- generic","").strip() if len(tmpck)>0: spline.append(tsline) return spline def getTopLaySnap(self,sessionid): """ 获取一个页面最上层的页面布局 :param testurl: :return: """ snapshot = self.run_command("snapshot", [],sessionid) specsnap=self.splitSnapShot(snapshot) if len(specsnap)<=2: return specsnap[-1] else: tmplist=list() index=1 for parline in specsnap: if index>2: tmplist.append(parline) index=index+1 finalsnapshot="\n".join(tmplist) return finalsnapshot在合适的地方,动态调用这个函数即可。而函数参数sessionid是为了支持多线程执行程序而传入的信息。
