ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于视觉模型的网页自动化Agent:超越API的通用解决方案

基于视觉模型的网页自动化Agent:超越API的通用解决方案 在实际 AI Agent 开发中一个常见的误区是认为 Agent 必须通过调用目标网站的 API 接口才能实现自动化操作。这种思路将 Agent 的能力限制在了那些提供了友好、稳定且功能完备的 API 的网站上。然而现实中的大量网页尤其是那些没有开放 API 或 API 功能受限的网站才是自动化需求最旺盛的场景。此时依赖 API 的 Agent 将寸步难行。本文旨在探讨一种更通用、更接近人类操作模式的解决方案基于视觉模型的网页理解与交互 Agent。我们将从概念入手逐步构建一个能够“看懂”网页并执行点击、输入等基础操作的原型系统并深入分析其背后的技术原理、实现细节以及在实际工程化中面临的挑战与最佳实践。1. 理解视觉模型驱动的 AI Agent 核心优势在深入代码之前必须厘清为什么视觉模型是解决网页自动化问题的“正解”以及它与传统 API 驱动方式的根本区别。1.1 API 驱动方式的局限性基于 API 的 Agent 工作流通常如下开发者预先编写好针对特定网站 API 的调用逻辑Agent 根据用户指令组合调用这些 API。这种方式存在几个固有缺陷覆盖范围有限仅适用于提供了开放 API 的网站。对于绝大多数网站如企业内部系统、老旧网站、或出于安全考虑未开放 API 的网站此方法无效。耦合度高维护成本大Agent 的逻辑与特定 API 的接口设计、参数格式、认证方式强耦合。一旦目标网站更新 API 版本或修改接口Agent 就需要同步更新代码维护负担沉重。功能受限于 API 能力Agent 只能执行 API 提供的操作。如果某个操作例如在某个复杂表单的特定位置勾选一个复选框没有对应的 API则无法实现。1.2 视觉模型的通用性原理视觉模型驱动的 Agent 则模拟了人类用户与图形用户界面GUI交互的方式感知通过截取或接收浏览器页面的屏幕截图即“视觉”输入。理解使用视觉语言模型VLM分析截图识别页面上的UI元素如按钮、输入框、链接、文本、它们的布局、状态如是否可点击、是否已勾选以及当前显示的内容。规划与决策基于用户指令如“登录”、“搜索商品XXX”和当前页面状态决定下一步要执行的操作如“点击登录按钮”、“在搜索框输入文本”。执行将决策转化为对浏览器的自动化操作指令如模拟鼠标点击、键盘输入并执行。这种方式的优势显而易见通用性强理论上任何能通过浏览器访问的网页都可以通过这种方式进行自动化操作不依赖于网站是否提供 API。更贴近真实用户行为操作逻辑基于视觉和语义而非脆弱的代码级接口更能适应网页的UI变化。功能灵活只要模型能识别出UI元素就能执行相应的交互不受预先定义的API列表限制。1.3 关键组件Navigator 模型与浏览器 Agent要实现上述流程核心在于两个组件Navigator 模型这是一个专为网页理解和交互任务设计的视觉语言模型。它接收网页截图和用户指令输出一个结构化的“动作计划”。这个计划通常包括下一个要执行的原子操作如CLICKTYPESCROLL以及操作的目标位置如以坐标或元素描述符表示。浏览器 Agent这是一个控制浏览器如通过 Selenium、Playwright 或 Puppeteer并执行 Navigator 模型输出指令的驱动程序。它负责导航到指定URL、截取屏幕、将动作指令转化为浏览器自动化命令并观察执行后的页面变化。2. 环境准备与核心依赖配置我们将使用 Python 作为开发语言并选择一些成熟的开源库来搭建我们的原型。这个环境配置是后续所有实验的基础。2.1 基础 Python 环境建议使用 Python 3.9 或更高版本。使用虚拟环境是一个好习惯。# 创建并激活虚拟环境 (以 conda 为例) conda create -n web_vision_agent python3.10 conda activate web_vision_agent # 或者使用 venv python -m venv venv # Windows: venv\Scripts\activate # Linux/Mac: source venv/bin/activate2.2 核心 Python 库安装我们将主要依赖playwright进行浏览器控制transformers和PIL用于图像和模型处理。首先安装 Playwright 及其浏览器。# 安装 Playwright 库 pip install playwright # 安装 Playwright 所需的浏览器Chromium, Firefox, WebKit playwright install chromium注意playwright install会下载浏览器二进制文件可能需要一些时间并且请确保网络通畅。接下来安装其他必要的库。pip install transformers pillow torch openaitransformers: Hugging Face 的模型库用于加载和运行视觉语言模型。pillow(PIL): Python 图像处理库。torch: PyTorch 深度学习框架许多 VLM 的后端。openai: 此处作为备选如果我们后续想使用云端的视觉 API如 GPT-4V作为 Navigator 模型。2.3 模型选择与准备Navigator 模型是整个系统的“大脑”。我们可以选择本地部署的轻量级开源模型也可以调用云 API。这里给出两种方案的准备方案一使用本地开源模型以 Qwen2-VL 为例Qwen2-VL 是一个性能不错的开源多模态模型支持图像理解和推理。# 安装 Qwen 相关依赖 pip install qwen-vl-utils在代码中我们可以通过transformers库加载模型。首次运行时会从 Hugging Face 下载模型权重请确保有足够的磁盘空间和稳定的网络。方案二使用云端 API以 OpenAI GPT-4V 为例这种方式无需本地 GPU开发更快捷但会产生 API 调用费用且依赖网络。# 确保已安装 openai 库 # 设置环境变量或在代码中配置 API Key export OPENAI_API_KEYyour-api-key-here关键决策点选择本地模型还是云端 API取决于你的需求。本地模型无网络延迟、无使用费用、数据隐私性好但对硬件GPU 内存有要求且推理速度可能较慢。云端 API 开发便捷、模型能力强、无需关心硬件但存在成本、网络延迟和数据出境风险。对于学习和原型开发可以先用云端 API 验证流程。3. 构建一个最小可运行的视觉网页 Agent现在我们开始编写代码将各个组件串联起来实现一个能完成简单任务例如打开百度首页并搜索关键词的 Agent。3.1 项目结构设计创建一个清晰的项目结构有助于管理代码。web_vision_agent/ ├── agent_core.py # Agent 核心逻辑类 ├── navigator.py # Navigator 模型封装 ├── browser_controller.py # 浏览器控制封装 ├── config.py # 配置文件 ├── tasks/ # 预定义任务示例 │ └── baidu_search.py └── main.py # 主程序入口3.2 实现浏览器控制器首先我们封装一个基于 Playwright 的浏览器控制器。它负责启动浏览器、导航、截图和执行动作。# browser_controller.py import asyncio from playwright.async_api import async_playwright, Page from PIL import Image import io class BrowserController: def __init__(self, headless: bool False): self.headless headless self.browser None self.page: Page None self.playwright None async def start(self): 启动浏览器并打开新页面 self.playwright await async_playwright().start() # 使用 Chromium更稳定 self.browser await self.playwright.chromium.launch(headlessself.headless) self.page await self.browser.new_page() # 设置视口大小影响截图内容 await self.page.set_viewport_size({width: 1280, height: 720}) print(浏览器启动成功。) async def goto(self, url: str): 导航到指定URL if not self.page: raise RuntimeError(浏览器未启动请先调用 start() 方法。) await self.page.goto(url) print(f已导航至: {url}) async def take_screenshot(self) - Image.Image: 截取当前页面可视区域的截图返回 PIL Image 对象 if not self.page: raise RuntimeError(页面未就绪。) # 等待页面基本稳定可根据需要调整 await self.page.wait_for_load_state(networkidle) screenshot_bytes await self.page.screenshot(typepng) image Image.open(io.BytesIO(screenshot_bytes)) return image async def perform_action(self, action: dict): 根据动作字典执行操作 action_type action.get(type) # 坐标是相对于视口左上角 x action.get(x) y action.get(y) text action.get(text) if action_type CLICK: if x is not None and y is not None: await self.page.mouse.click(x, y) print(f在坐标 ({x}, {y}) 执行点击。) else: print(错误CLICK 动作缺少坐标。) elif action_type TYPE: if text: # 先点击输入区域确保焦点 if x and y: await self.page.mouse.click(x, y) await self.page.keyboard.type(text) print(f输入文本: {text}) elif action_type PRESS_ENTER: await self.page.keyboard.press(Enter) print(按下 Enter 键。) elif action_type SCROLL: delta_y action.get(delta_y, 300) # 默认向下滚动300像素 await self.page.mouse.wheel(0, delta_y) print(f滚动 {delta_y} 像素。) else: print(f未知动作类型: {action_type}) async def close(self): 关闭浏览器 if self.browser: await self.browser.close() if self.playwright: await self.playwright.stop() print(浏览器已关闭。)3.3 实现 Navigator 模型封装接下来我们封装 Navigator 模型。这里我们先实现一个基于 OpenAI GPT-4V 的版本因为它能快速验证流程。我们会设计一个提示词Prompt让模型根据截图和指令输出结构化的动作。# navigator.py import base64 from io import BytesIO from PIL import Image import openai from typing import Dict, Any import json class OpenAINavigator: def __init__(self, api_key: str, model: str gpt-4-vision-preview): self.client openai.OpenAI(api_keyapi_key) self.model model def _encode_image(self, image: Image.Image) - str: 将 PIL Image 转换为 base64 字符串 buffered BytesIO() # 为了节省 token可以适当压缩图像 image.save(buffered, formatJPEG, quality85) img_str base64.b64encode(buffered.getvalue()).decode() return img_str def analyze_and_plan(self, image: Image.Image, instruction: str) - Dict[str, Any]: 分析网页截图根据指令生成下一步动作。 返回一个动作字典。 base64_image self._encode_image(image) # 精心设计的系统提示词是关键 system_prompt 你是一个网页交互助手。你的任务是观察用户提供的网页截图理解用户的指令然后决定下一步最合适的原子操作。 你只能输出一个 JSON 对象格式必须严格如下 { reasoning: 简要解释你为什么选择这个动作, action: { type: CLICK | TYPE | PRESS_ENTER | SCROLL | DONE, x: integer, // 仅当 type 为 CLICK 或 TYPE 时需要表示目标点的横坐标 y: integer, // 仅当 type 为 CLICK 或 TYPE 时需要表示目标点的纵坐标 text: string, // 仅当 type 为 TYPE 时需要表示要输入的文本 delta_y: integer // 仅当 type 为 SCROLL 时需要正数向下负数向上 } } 规则 1. 坐标 (x, y) 是相对于截图左上角的像素坐标。请尽可能精确地估计目标元素中心点的位置。 2. 如果任务已完成例如搜索结果已显示将 type 设为 DONE。 3. 如果当前页面没有完成指令所需的元素可以建议 SCROLL。 4. 保持动作简单一次只做一个操作。 user_prompt f用户指令{instruction}。请分析当前网页截图并给出下一步动作。 try: response self.client.chat.completions.create( modelself.model, messages[ {role: system, content: system_prompt}, { role: user, content: [ {type: text, text: user_prompt}, { type: image_url, image_url: { url: fdata:image/jpeg;base64,{base64_image} }, }, ], }, ], max_tokens500, ) result_text response.choices[0].message.content # 尝试从返回文本中解析 JSON # 模型有时会在 JSON 外加 json 标记 if json in result_text: result_text result_text.split(json)[1].split()[0].strip() elif in result_text: result_text result_text.split()[1].strip() action_plan json.loads(result_text) return action_plan except json.JSONDecodeError as e: print(f解析模型输出 JSON 失败: {e}) print(f原始输出: {result_text}) # 返回一个安全动作例如滚动 return {reasoning: 解析失败默认滚动, action: {type: SCROLL, delta_y: 300}} except Exception as e: print(f调用 OpenAI API 出错: {e}) raise3.4 组装核心 Agent现在我们将浏览器控制器和 Navigator 模型组合成完整的 Agent。# agent_core.py import asyncio from browser_controller import BrowserController from navigator import OpenAINavigator from PIL import Image class VisionWebAgent: def __init__(self, navigator_model, headless: bool False): self.browser BrowserController(headlessheadless) self.navigator navigator_model self.max_steps 20 # 防止无限循环 async def run_task(self, start_url: str, instruction: str): 执行一个任务 await self.browser.start() try: await self.browser.goto(start_url) for step in range(self.max_steps): print(f\n--- 步骤 {step 1} ---) # 1. 观察截取当前页面 screenshot: Image.Image await self.browser.take_screenshot() # 可选保存截图用于调试 # screenshot.save(fdebug_step_{step}.jpg) # 2. 思考与规划Navigator 分析 print(f指令: {instruction}) plan self.navigator.analyze_and_plan(screenshot, instruction) print(f模型推理: {plan.get(reasoning)}) next_action plan.get(action, {}) # 3. 执行 action_type next_action.get(type) if action_type DONE: print(任务完成) break await self.browser.perform_action(next_action) # 等待一小段时间让页面反应 await asyncio.sleep(2) else: print(f达到最大步骤数 ({self.max_steps})任务可能未完成。) finally: await self.browser.close()3.5 创建并运行一个示例任务最后我们编写主程序来运行一个具体的任务让 Agent 在百度首页搜索“AI Agent”。# main.py import asyncio import os from agent_core import VisionWebAgent from navigator import OpenAINavigator async def main(): # 配置 OpenAI API Key api_key os.getenv(OPENAI_API_KEY) if not api_key: print(错误请设置 OPENAI_API_KEY 环境变量。) return # 初始化 Navigator (使用 GPT-4V) navigator OpenAINavigator(api_keyapi_key) # 初始化 AgentheadlessFalse 以便观察浏览器行为 agent VisionWebAgent(navigator_modelnavigator, headlessFalse) # 定义任务 start_url https://www.baidu.com user_instruction 在搜索框里输入‘AI Agent’然后点击‘百度一下’按钮进行搜索。 print(f开始任务: {user_instruction}) await agent.run_task(start_url, user_instruction) if __name__ __main__: asyncio.run(main())运行这个程序 (python main.py)你将看到一个浏览器窗口自动打开导航到百度然后模型会尝试识别搜索框和按钮的位置并模拟点击和输入。这个过程可能不会一次成功但它展示了完整的工作流。4. 关键参数、配置与模型调优详解一个可用的原型只是第一步。要让这个 Agent 稳定可靠需要深入理解并调整各个环节。4.1 浏览器控制器配置要点浏览器的状态直接影响截图内容和操作成功率。配置项说明与常见值影响headlessFalse(有界面) /True(无界面)调试时设为False便于观察生产环境设为True节省资源。viewport_size{width: 1280, height: 720}固定视口大小确保截图和坐标计算的一致性。不同分辨率下元素位置会变。wait_for_load_stateload/domcontentloaded/networkidle控制何时认为页面“加载完成”。networkidle更严格能减少截图时页面还在加载的问题。动作执行后等待asyncio.sleep(2)执行点击、输入后等待页面更新或网络请求完成。这个时间需要根据目标网站响应速度调整太短可能导致下一步截图时页面未更新。4.2 Navigator 模型提示词工程提示词Prompt是引导模型正确输出的关键。上述示例中的system_prompt已经包含了一些要点但还可以优化坐标系统说明必须明确告知模型坐标是相对于截图的且原点在左上角。可以要求模型“估计目标元素中心点的像素坐标”。动作原子化强制要求一次只输出一个动作避免模型输出“先点击A再输入B”的复杂指令这不利于程序解析和执行。定义终止条件明确告知模型什么情况下输出DONE例如“当搜索结果页面成功加载并显示相关条目时”。处理不确定性可以指示模型如果无法确定唯一目标可以选择最可能的一个并在reasoning中说明。输出格式强化使用 JSON Schema 描述或非常严格的格式示例减少模型输出格式错误的概率。4.3 使用本地开源模型的调整如果使用本地模型如 Qwen2-VL代码需要调整。本地模型通常不直接输出结构化 JSON需要更复杂的后处理。# 示例使用 Qwen2-VL 的简化调用 from transformers import Qwen2VLForConditionalGeneration, AutoProcessor import torch class QwenVLNavigator: def __init__(self, model_nameQwen/Qwen2-VL-7B-Instruct): self.device cuda if torch.cuda.is_available() else cpu self.model Qwen2VLForConditionalGeneration.from_pretrained( model_name, torch_dtypetorch.float16, device_mapauto ) self.processor AutoProcessor.from_pretrained(model_name) def analyze_and_plan(self, image: Image.Image, instruction: str) - Dict[str, Any]: # 构建消息类似 OpenAI 的格式 messages [ { role: user, content: [ {type: image}, {type: text, text: f截图。用户指令{instruction}。请输出下一步动作的JSON格式为...同前} ] } ] # 预处理 text self.processor.apply_chat_template(messages, add_generation_promptTrue) inputs self.processor(text[text], images[image], return_tensorspt).to(self.device) # 生成 generated_ids self.model.generate(**inputs, max_new_tokens512) generated_text self.processor.batch_decode(generated_ids, skip_special_tokensTrue)[0] # 从 generated_text 中解析出 JSON 部分这里需要编写稳健的解析逻辑 # ... 解析逻辑 ... return parsed_action关键挑战输出格式控制开源模型遵循指令生成结构化 JSON 的能力可能不如 GPT-4V 强需要更精细的提示和可能的输出后处理如正则表达式提取。坐标回归精度让模型直接输出精确的像素坐标非常困难。一种改进方案是让模型输出对目标元素的文本描述如“带有‘百度一下’文字的蓝色按钮”然后结合传统的计算机视觉方法如模板匹配、OCR定位来获取精确坐标。这就是“语义描述 CV 定位”的混合策略。推理速度与资源本地模型推理速度慢且需要大量 GPU 内存。可以考虑模型量化、使用更小的模型或采用 API 方式。5. 运行验证、调试与结果分析运行上述百度搜索示例后你需要观察并分析 Agent 的行为以判断其是否成功。5.1 成功运行的标志视觉观察浏览器自动在搜索框内输入了“AI Agent”并点击了“百度一下”按钮。URL 变化地址栏从www.baidu.com变为包含wdAI%20Agent等搜索参数的 URL。页面内容页面跳转后显示出了与“AI Agent”相关的搜索结果列表。控制台输出程序最终打印出“任务完成”。5.2 常见失败模式与调试方法如果任务失败请按以下步骤排查失败现象可能原因调试与解决思路浏览器启动失败Playwright 浏览器未正确安装或路径问题。运行playwright install chromium重新安装。检查网络代理设置。导航后页面空白或错误网络问题、网站反爬机制。增加导航超时时间。检查page.goto的返回状态。尝试添加wait_until参数。模型无法输出有效 JSON提示词不够清晰或模型输出被截断。保存模型的原始输出 (result_text) 到日志文件分析其内容。简化提示词强调“只输出JSON”。增加max_tokens参数。动作坐标错误点击了错误位置1. 模型识别不准。2. 视口大小变化导致坐标偏移。3. 页面有动态元素如弹窗、浮动广告。调试核心保存每一步的截图 (screenshot.save(...))。将模型预测的坐标 (x, y) 在截图上画一个红圈与真实目标对比。固定视口大小。在执行动作前等待更长时间 (sleep)确保页面稳定。输入框未获得焦点输入失败TYPE动作前没有成功CLICK输入框。在perform_action中对于TYPE动作即使提供了坐标也强制先执行一次点击。或者让模型明确输出“先点击输入框再输入文本”的两个独立动作。陷入无限循环如反复滚动模型无法识别任务完成状态或DONE条件未触发。改进提示词明确“DONE”的触发条件。在 Agent 循环中增加超时或最大步数限制。可以尝试让模型在输出动作的同时输出一个“完成度置信度”。调试技巧在agent_core.py的循环中添加保存截图和记录动作的代码这是最有效的调试手段。# 在 agent_core.py 的 run_task 循环内添加 debug_dir debug_logs os.makedirs(debug_dir, exist_okTrue) screenshot.save(os.path.join(debug_dir, fstep_{step}_before_action.jpg)) # 执行动作... # 执行后可以再截一张图对比 # after_screenshot await self.browser.take_screenshot() # after_screenshot.save(os.path.join(debug_dir, fstep_{step}_after_action.jpg)) with open(os.path.join(debug_dir, actions.log), a) as f: f.write(fStep {step}: {json.dumps(plan)}\n)6. 工程化挑战与进阶最佳实践将原型转化为稳定、可用的生产系统需要解决一系列工程挑战。6.1 提升动作执行的鲁棒性单纯依赖模型的像素坐标点击非常脆弱。混合定位策略结合多种定位方式。视觉坐标模型输出的坐标作为初选。语义检索用模型识别出的元素文本描述通过 Playwright 的page.get_by_text()或page.get_by_role()等语义定位器进行二次定位。这比像素坐标稳定得多。CSS 选择器备用对于已知的、结构稳定的网站可以预定义关键元素的 CSS 选择器作为后备方案。动作重试与超时为每个动作如点击添加重试机制。如果点击后页面没有发生预期变化可通过 URL 变化、特定元素出现来判断则重试或尝试备用定位方式。6.2 处理复杂页面状态与等待智能等待不要只用固定的sleep。使用 Playwright 的page.wait_for_selector(),page.wait_for_url(),page.wait_for_function()等方法等待特定条件满足后再进行下一步。这需要 Navigator 模型不仅能输出动作还能输出“等待什么条件”的期望。状态管理让 Agent 维护一个简单的页面状态历史避免在相同状态间循环。例如记录最近几次的截图哈希值如果检测到循环则触发回退或异常处理策略。6.3 降低模型调用成本与延迟缓存与记忆对于重复访问的页面或相似操作可以缓存模型的输出截图指令 - 动作。短期内相同的状态无需重复调用昂贵的模型。动作抽象与复用将常见的操作序列如“登录”、“填写表单并提交”抽象成可复用的“技能”Skill。Navigator 模型只需识别出需要调用哪个“技能”而无需规划每一个原子步骤。这大大减少了模型调用次数和复杂度。使用更经济的模型对于简单的元素识别可以尝试使用专门的、更小的视觉模型或 OCR 模型而不是通用的多模态大模型。6.4 安全与伦理考量遵守robots.txt在自动化访问任何网站前应检查其robots.txt文件尊重网站所有者设置的爬虫规则。控制访问频率添加随机延迟模拟人类操作速度避免对目标网站造成负载压力。明确用途此类技术应用于自动化测试、辅助工具等合法合规场景不得用于恶意爬虫、欺诈、攻击等行为。7. 扩展方向与学习路径基于视觉模型的网页 Agent 是一个快速发展的领域你可以从以下几个方向深入探索更强大的 Navigator 模型关注如CogAgent、WebVoyager等专门为网页交互训练的开源模型。它们通常在网页元素定位和指令遵循上表现更好。引入强化学习让 Agent 通过试错来学习根据任务完成的正负奖励来优化其决策策略减少对昂贵模型每次规划的依赖。构建技能库如前所述将常见任务登录、搜索、下单封装成技能让高层模型进行技能调度底层用确定性脚本执行提高效率和可靠性。多模态信息融合不仅使用截图也结合页面 DOM 树、可访问性树Accessibility Tree的信息为模型提供更丰富、结构化的上下文。端到端训练如果有足够的数据可以尝试端到端训练一个模型直接输入截图和指令输出浏览器操作序列绕过中间的结构化输出解析。构建一个真正鲁棒的视觉网页 Agent 是一项复杂的系统工程它涉及多模态理解、规划、强化学习、软件测试等多个领域的知识。本文提供的原型是一个坚实的起点通过不断迭代模型提示、增强动作执行模块、引入状态管理和技能抽象你可以逐步将其应用到更实际、更复杂的自动化场景中。
返回列表