做过网页自动化的都知道,流程刚写完那几天是蜜月期,跑一个月之后才是真正的考验:前端发了个版,按钮 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 的思路正好反过来------让模型像人一样"看"屏幕,用截图加目标描述找到元素。把它嵌进自动化流程里,就构成了元素自愈的底层能力。
二、自愈机制的完整架构
整个机制分四层,串成一个闭环:
-
执行层:按步骤执行动作,每步先做一次"元素健康检查";
-
检测层:选择器定位失败(超时或不存在)时,判定该元素失效;
-
修复层:截全页截图,送视觉模型,返回目标元素的像素坐标框;
-
回写层:按坐标在 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 把"看"的能力引入了自动化流程,让元素失效从"流程中断"变成"一次自动修复"。
回顾这个方案的四个要点:
元素库要存语义描述,不只存选择器;
修复闭环要回写元素库,修复结果必须持久化;
视觉模型要本地部署,数据不出域是底线;
自愈解决小变化,页面重构还是要靠人工。
对于维护着几十条网页自动化流程的团队来说,元素自愈不是锦上添花,而是把自动化从"演示能跑"推到"生产敢用"的关键一步。