实战:基于 Computer Use 实现网页元素 AI 自愈,告别固定 XPath 定位

做过网页自动化的都知道,流程刚写完那几天是蜜月期,跑一个月之后才是真正的考验:前端发了个版,按钮 class 名变了,整个流程趴窝。

本文聊一个我最近在项目里落地的方案:基于 Computer Use 的网页元素 AI 自愈机制------元素失效时不中断流程,由视觉模型重新找到元素、自动修复元素定位,流程继续跑。全文给出可直接改用的工程代码。

一、为什么固定 XPath 撑不住长期运行

传统的 RPA 和脚本,元素定位基本都依赖一条写死的 XPath 或 CSS 选择器:

复制代码
# 传统写法:一条写死的选择器
btn = page.locator('//*[@id="app"]/div[2]/div[1]/button[3]')
btn.click()

这条路有三个结构性问题:

第一,选择器天然脆弱。 前端稍微调整一下 DOM 层级,整条 XPath 就失效了。

第二,失效即中断。 旧方案发现元素找不到,只能抛异常、停流程、等人来修。

第三,修复成本高。 每次失效都要人工重新捕获元素、改脚本、重新发布。

核心矛盾在于:元素位置是"猜"出来的,而不是"看"出来的。

Computer Use 的思路正好反过来------让模型像人一样"看"屏幕,用截图加目标描述找到元素。把它嵌进自动化流程里,就构成了元素自愈的底层能力。

二、自愈机制的完整架构

整个机制分四层,串成一个闭环:

  1. 执行层:按步骤执行动作,每步先做一次"元素健康检查";

  2. 检测层:选择器定位失败(超时或不存在)时,判定该元素失效;

  3. 修复层:截全页截图,送视觉模型,返回目标元素的像素坐标框;

  4. 回写层:按坐标在 DOM 上反查元素属性,生成新的稳定选择器,持久化到元素库,流程不中断继续执行。

一句话概括这个闭环:

定位失败 → 视觉重找 → 属性反查 → 生成新选择器 → 更新元素库 → 流程继续。

三、工程代码实现

下面这套代码基于 Playwright + OpenAI 兼容接口的本地视觉模型(Qwen2.5-VL 等均可),可整体迁移到任何 Python 自动化项目里。

1、元素库:不只存一条选择器

元素自愈的前提,是元素库里存的信息足够丰富------主选择器、备用属性、语义描述、参考截图缺一不可:

复制代码
// elements.json ------ 元素库(数组结构,与主流程加载方式对应)
[
  {
    "step_id": "click_submit_order",
    "primary_selector": "//button[contains(@class,'submit-btn')]",
    "attributes": {
      "text": "提交订单",
      "tag": "button"
    },
    "description": "订单确认页右下角的蓝色提交按钮",
    "ref_screenshot": "refs/submit_order.png"
  }
]

2、执行层:带健康检查的动作执行器

复制代码
from playwright.sync_api import sync_playwright, TimeoutError as PWTimeout

class SelfHealingExecutor:
    def __init__(self, page, healer):
        self.page = page
        self.healer = healer   # 修复器,下一节实现

    def click(self, element: dict, timeout_ms: int = 5000):
        loc = self.page.locator(element["primary_selector"])
        try:
            # 健康检查:等待元素出现
            loc.first.wait_for(state="visible", timeout=timeout_ms)
        except PWTimeout:
            # 元素失效,进入自愈流程,拿到修复后的新选择器
            new_selector = self.healer.heal(self.page, element)
            element["primary_selector"] = new_selector   # 回写元素库
            loc = self.page.locator(new_selector)
            # 自愈后二次校验:新选择器必须真实可用
            loc.first.wait_for(state="visible", timeout=timeout_ms)
        loc.first.click()
        return element

3、修复层:视觉模型定位 + 属性反查

这是自愈的核心。视觉模型只负责一件事:给目标描述,返回像素坐标框。拿到坐标后,用 element_from_point 反查真实 DOM 元素,再从它的属性里生成一条更稳的选择器:

复制代码
import base64, json, requests

class VisionHealer:
    def __init__(self, base_url: str, model: str = "Qwen2.5-VL-7B-Instruct"):
        self.url = f"{base_url}/chat/completions"
        self.model = model

    def _ask_vision(self, png_bytes: bytes, description: str) -> dict:
        b64 = base64.b64encode(png_bytes).decode()
        resp = requests.post(self.url, json={
            "model": self.model,
            "messages": [{
                "role": "user",
                "content": [
                    {"type": "text", "text":
                     f"在截图中找到:{description}。"
                     "只返回JSON:{{\"bbox\":[x1,y1,x2,y2]}},坐标为像素值。"},
                    {"type": "image_url",
                     "image_url": {"url": f"data:image/png;base64,{b64}"}}
                ]
            }],
            "temperature": 0
        }, timeout=60)
        text = resp.json()["choices"][0]["message"]["content"]
        return json.loads(text.strip("```json").strip("```"))

    def heal(self, page, element: dict) -> str:
        # 1. 截图
        png = page.screenshot(full_page=True)
        result = self._ask_vision(png, element["description"])
        x1, y1, x2, y2 = result["bbox"]

        # 2. 坐标反查 DOM 元素
        cx, cy = (x1 + x2) // 2, (y1 + y2) // 2
        handle = page.evaluate("""(pt) => {
            const el = document.elementFromPoint(pt.x, pt.y);
            if (!el) return null;
            return {
                tag: el.tagName.toLowerCase(),
                id: el.id || '',
                cls: typeof el.className === 'string' ? el.className : '',
                text: (el.innerText || '').trim().slice(0, 30)
            };
        }""", {"x": cx, "y": cy})
        if not handle:
            raise RuntimeError("视觉定位后反查DOM失败,需要人工介入")

        # 3. 生成更稳定的选择器:优先 id,其次文本,最后 class
        if handle["id"]:
            new_selector = f'//*[@id="{handle["id"]}"]'
        elif handle["text"]:
            new_selector = f'//{handle["tag"]}[contains(normalize-space(.),"{handle["text"][:10]}")]'
        else:
            new_selector = f'//{handle["tag"]}[contains(@class,"{handle["cls"].split()[0]}")]'
        return new_selector

4、主流程:串联起来

复制代码
def run_flow():
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False)
        page = browser.new_page()
        page.goto("http://127.0.0.1:8080")   # 换成你的目标系统地址

        elements = json.load(open("elements.json", encoding="utf-8"))
        healer = VisionHealer(base_url="http://127.0.0.1:8000/v1")
        executor = SelfHealingExecutor(page, healer)

        for step in elements:
            executor.click(step)
            # 每步后落盘,防止流程中断导致修复结果丢失
            json.dump(elements, open("elements.json", "w", encoding="utf-8"),
                      ensure_ascii=False, indent=2)

        browser.close()

四、三个容易踩的坑

这套机制跑通不难,跑稳要处理几个细节:

坑一:整页截图在页面很长时不准。 长页面建议分段截图,或让模型基于"当前视口"定位,坐标换算要减掉滚动偏移。上面的示例为了突出主线没有展开,落地时务必处理。

坑二:视觉模型会认错元素。 页面上出现多个相似按钮时,只给一句描述容易点错。缓解办法是把元素库里的 description 写具体(位置、颜色、邻近文字都写上),并把 temperature 设为 0。即便如此,自愈结果也应该记录日志,人工抽查,不要把视觉模型当成百分百正确。

坑三:自愈不能代替流程设计。 如果页面大改版,布局整个变了,自愈再强也兜不住。元素自愈解决的是"小变化",解决不了"重构"。 这两个场景要分开管理。

五、部署形态:内网离线

这套方案对部署环境有一个现实要求:截图要送出内网,数据安全就无从谈起。

所以完整方案里,视觉模型建议本地部署(vLLM 或 Ollama 起服务,通过 OpenAI 兼容接口调用),整套流程可以在内网离线环境运行,截图和页面数据全程不出本地,这也满足了很多企业内部"数据不出域"的要求。

如果你不想自己从零搭这套自愈框架,也可以直接考察带原生自愈能力的工具。比如蓝印RPA内置了 AI 修复元素定位的能力,Web 元素失效时能自动重新定位、实现元素自愈,并且支持内网离线使用、流程数据全部保存在本地设备上,同时支持把流程打包成 EXE 分发给同事直接运行。对中小企业来说,这类开箱即用的方案比自己维护一套视觉模型栈要省不少事。

六、小结

固定 XPath 的问题,本质是"用静态信息描述动态界面"。Computer Use 把"看"的能力引入了自动化流程,让元素失效从"流程中断"变成"一次自动修复"。

回顾这个方案的四个要点:

元素库要存语义描述,不只存选择器;

修复闭环要回写元素库,修复结果必须持久化;

视觉模型要本地部署,数据不出域是底线;

自愈解决小变化,页面重构还是要靠人工。

对于维护着几十条网页自动化流程的团队来说,元素自愈不是锦上添花,而是把自动化从"演示能跑"推到"生产敢用"的关键一步。

相关推荐
2601_958957591 小时前
2026云手机哪个品牌好?五款主流品牌横向对比
人工智能·智能手机·手机
唠点键盘之外的1 小时前
29 切片的权限隔离(实战篇):Milvus + Spring AI,从 0 搭一条带权限过滤的检索链路
人工智能·spring·milvus
.Bryce.1 小时前
MCP+A2A+ANP,Agent通信协议的完整知识地图
java·人工智能·后端·agent·通信协议
JuiceFS1 小时前
面向 EB 级 AI 存储:JuiceFS 企业版架构演进
人工智能·后端
聚城云-GeecityCloud1 小时前
一套物业管理系统多少钱?2026物业软件收费模式与成本明细
大数据·人工智能
LMT流氓兔1 小时前
第五单元——第4课:给工具参数加校验
java·前端·算法·ai·langchain
成都旺小宝科技1 小时前
首访决策人没来,AI一客一策两天成交!
大数据·人工智能·机器学习·ffmpeg·语音识别
码农小白AI1 小时前
批次检验报告和入库单对不上?IACheck AI 报告文档审核 Agent 做原始记录匹配
人工智能
昇腾CANN1 小时前
10月21日16点直播丨CANN社区Agent能力介绍——模型、TileLang、AMCT开发
人工智能·开源·昇腾·cann·cann开源