基于LLM与Playwright的智能网页自动化:Web-Use项目实战解析
1. 项目概述:一个能“看懂”网页的智能体
如果你也厌倦了那些重复、繁琐的网页操作——比如在不同电商平台比价、手动填写表单、或者从一堆搜索结果里筛选信息——那么今天聊的这个项目,你可能会非常感兴趣。它叫Web-Use,本质上是一个智能化的自主网页浏览代理。简单来说,你可以把它理解为一个拥有“眼睛”和“手”的AI助手,它能像真人一样打开浏览器,阅读网页内容,理解你的指令,然后自动执行点击、输入、滚动、下载等一系列操作。
这个项目的核心价值在于,它将大语言模型的理解能力与浏览器的自动化操控能力结合了起来。传统的网页自动化工具(比如Selenium)需要你编写非常精确的脚本,告诉它“点击ID为‘submit’的按钮”。但现实中的网页千变万化,按钮的ID可能每天都不一样。Web-Use的思路则更接近人类:它让AI“看”到网页的截图和DOM结构,然后“思考”:“用户想让我找RTX 4060的价格,这个页面上有好几个商品卡片,我应该点开哪一个看看详情?” 这种基于视觉和语义理解的自动化,适应性要强得多。
我花了一些时间深入研究了这个项目的代码和设计,它非常适合开发者、数据分析师、或者任何需要从网上批量获取和处理信息的从业者。无论是做市场调研、竞品分析、内容聚合,还是自动化一些日常的办公流程,它都能显著提升效率。接下来,我会拆解它的核心设计、手把手带你完成部署和定制,并分享我在测试过程中踩过的坑和总结的经验。
2. 核心架构与设计思路拆解
要理解Web-Use为什么能工作,我们需要先抛开代码,看看它的大脑和四肢是如何协同的。整个系统的设计可以概括为“感知-思考-行动”的循环,这个循环在LangGraph的框架下被组织成一个可控的工作流。
2.1 大脑:LLM的决策与规划能力
项目默认支持Gemini、Groq和Ollama三种大模型后端。选择哪个模型,是第一个需要权衡的点。Gemini(尤其是gemini-2.0-flash)在响应速度和多模态理解(结合视觉)上表现均衡,API调用也相对方便,适合快速上手和大多数任务。Groq凭借其极快的推理速度著称,如果你需要代理进行大量、快速的链式思考(比如分析一个结构复杂的页面),它会是不错的选择。Ollama则提供了完全的本地化部署能力,使用像llama3.2、qwen2.5这样的开源模型,保证了数据的私密性,但需要你本地有足够的GPU资源。
模型在这里扮演“指挥官”的角色。它接收来自用户的自然语言指令(如“去亚马逊找RTX 4060笔记本显卡的价格”),并结合当前浏览器的“观察结果”(网页截图、文本摘要、可交互元素列表)进行思考。它的输出不是最终答案,而是一个或多个具体的“动作”指令,比如CLICK("//button[contains(text(), 'Next')]")或TYPE("search_box", "RTX 4060 laptop")。
注意:模型的选择直接影响智能体的“智商”和成本。对于简单的导航任务,轻量模型足够;但对于需要复杂逻辑判断的任务(例如从一篇长文中提取特定观点的摘要),更强大的模型能显著提升成功率。在项目初期,建议先用Gemini Flash这类低成本模型验证流程。
2.2 眼睛与手:Playwright与CDP
如果说LLM是大脑,那么Playwright就是智能体的眼睛和手。Playwright是一个强大的浏览器自动化库,它支持Chromium、Firefox和WebKit。Web-Use主要利用它做两件事:
- 导航与交互:根据LLM发出的动作指令,精准地打开网页、点击元素、输入文本、滚动页面。
- 环境感知:这是关键。Playwright不仅能获取页面的DOM树,还能通过Chrome DevTools Protocol捕获页面的完整截图。这张截图会被传递给LLM(如果开启了视觉功能),让模型能“看到”按钮的实际位置、图片内容、甚至是验证码,从而做出更接近人类的判断。
为什么不用更老的Selenium?Playwright在速度、稳定性以及对现代Web应用(大量使用JavaScript动态渲染)的支持上更胜一筹。它的API设计也更现代化,与异步编程(Async/Await)模式结合得更好,这对于需要等待页面加载、元素出现的自动化任务至关重要。
2.3 协调中枢:LangGraph的工作流管理
LLM和Playwright各司其职,但它们需要一个“神经系统”来有序地组织“观察-思考-行动”这个循环。这就是LangGraph的用武之地。LangGraph允许你以图(Graph)的形式定义智能体的工作流。
在Web-Use中,这个工作流大致是这样的:
- 开始节点:接收用户查询。
- 观察节点:使用Playwright获取当前页面的状态(文本、截图、可操作元素)。
- 思考节点:将用户查询和观察结果一起喂给LLM,请求LLM决定下一步做什么动作,或者判断任务是否已完成。
- 行动节点:执行LLM决定的动作(如点击、输入)。
- 条件判断:检查LLM的输出是“继续行动”还是“任务完成”。如果是继续,则跳回第2步(观察节点),形成循环;如果完成,则结束。
这种图结构使得智能体的行为不再是线性的脚本,而是一个可以根据环境反馈动态调整的决策过程,非常灵活。
3. 从零开始:环境部署与核心配置详解
理论讲完了,我们动手把它跑起来。这里我会详细说明每一步的意图和可能遇到的问题。
3.1 基础环境搭建
项目推荐使用uv作为Python包管理器和python 3.12+。uv是新兴的工具,速度极快,能完美处理依赖冲突。如果你还没安装,可以先用pip安装它:pip install uv。
首先,克隆仓库并进入目录:
git clone https://github.com/CursorTouch/Web-Use.git cd Web-Use接着,使用uv sync安装所有依赖。这个命令会根据项目根目录的pyproject.toml文件,创建一个独立的虚拟环境并安装所有包。这比传统的pip install -r requirements.txt更干净、更快速。
然后,安装Playwright所需的浏览器内核:
playwright install chromium这里我明确指定安装chromium,因为它是CDP支持最完善的,也是项目默认使用的浏览器。安装过程会自动下载浏览器二进制文件。
3.2 模型API密钥配置
项目的智能核心是LLM,所以你需要准备一个模型的API密钥。这里以Google Gemini为例。
- 前往 Google AI Studio 创建一个API密钥。
- 在项目根目录下,复制
.env.example文件并重命名为.env。 - 打开
.env文件,将你的密钥填入对应位置:GOOGLE_API_KEY="你的_actual_api_key_here"重要安全提示:永远不要将
.env文件提交到Git仓库!.gitignore文件通常已经包含了它。你的API密钥就是钱,泄露可能导致未经授权的使用和费用损失。
如果你想使用Groq或Ollama,配置方式类似。对于Ollama,你通常需要设置OLLAMA_BASE_URL(例如http://localhost:11434)和OLLAMA_MODEL(例如llama3.2),并在本地运行Ollama服务。
3.3 首次运行与验证
项目提供了一个简单的入口脚本app.py。但在运行前,我们先创建一个简单的测试脚本test_agent.py来理解其工作原理:
# test_agent.py from src.inference.gemini import ChatGemini from src.agent.web import WebAgent from dotenv import load_dotenv import os # 1. 加载环境变量 load_dotenv() google_api_key = os.getenv('GOOGLE_API_KEY') # 2. 初始化LLM # 参数说明: # model: 指定Gemini模型版本,'gemini-2.0-flash' 是性价比之选。 # api_key: 传入密钥。 # temperature: 创造性,设为0使输出更确定、可重复,适合自动化任务。 llm = ChatGemini(model='gemini-2.0-flash', api_key=google_api_key, temperature=0) # 3. 初始化Web智能体 # 参数说明: # llm: 上面创建的语言模型实例。 # verbose: 设为True,会在控制台打印详细的决策日志,调试时非常有用。 # use_vision: 是否将网页截图传给模型。开启后能力更强,但消耗更多token。 agent = WebAgent(llm=llm, verbose=True, use_vision=False) # 4. 发起一个简单的任务 user_query = "打开百度首页,在搜索框里输入'天气预报',然后点击搜索按钮。" print(f"[用户指令] {user_query}") try: agent_response = agent.invoke(user_query) print(f"\n[智能体最终回复] {agent_response.get('output')}") except Exception as e: print(f"\n[执行出错] {e}")运行这个脚本:
python test_agent.py如果一切顺利,你会看到控制台开始滚动日志。verbose=True会让你看到智能体的思考过程,例如:
[THOUGHT] 用户想搜索天气预报。我需要先导航到百度首页。 [ACTION] NAVIGATE(url='https://www.baidu.com') [OBSERVATION] 已成功加载百度首页。页面包含一个搜索框和一个“百度一下”按钮。 [THOUGHT] 现在需要在搜索框中输入“天气预报”。 [ACTION] TYPE(selector='#kw', text='天气预报') ...最终,浏览器会自动完成打开百度、输入、搜索这一系列操作。第一次看到浏览器被自动操控,感觉还是挺奇妙的。
4. 核心功能模块深度解析与定制
让一个Demo跑起来只是第一步。要让Web-Use真正为你所用,必须理解它的核心模块,并知道如何调整。
4.1 观察模块:智能体如何“看”网页
智能体在行动前,必须先观察环境。在src/agent/state.py和相关的工具函数中,定义了观察的内容。主要包括:
- 页面文本摘要:通过Playwright获取页面的主要文本内容,并经过清洗和截断(避免token超限)。这是LLM理解页面内容的基础。
- 可交互元素列表:提取页面上所有可点击、可输入的HTML元素(如按钮、链接、输入框),并生成它们的XPath或CSS选择器,以及描述性文字(如按钮上的文字)。这个列表是LLM决定“点击哪里”的关键依据。
- 屏幕截图(可选):当
use_vision=True时,Playwright会截取当前页面的完整PNG图片,并将其编码为Base64字符串,随提示词一起发送给支持多模态的LLM(如Gemini)。这对于识别验证码、理解图表或处理纯图片按钮至关重要。
自定义观察粒度:默认的文本摘要可能过于简略。如果你处理的页面信息密集,可以修改提取逻辑。例如,你可以选择只提取<article>或.main-content标签内的文本,或者通过Playwright的evaluate方法执行自定义JavaScript来提取更结构化的数据。
4.2 动作模块:智能体如何“操作”网页
LLM思考后输出的动作,需要被解析并执行。动作定义在src/agent/tools.py中,通常包括:
navigate(url): 导航到指定URL。click(selector): 点击某个元素。type(selector, text): 在输入框输入文本。scroll(direction): 滚动页面。wait(time): 等待一段时间。extract_text(selector): 提取特定元素的文本。
关键点:选择器的稳定性。LLM通常基于元素文本(如“搜索”)来生成选择器,但网页上的文本可能不唯一,或者动态变化。为了提高可靠性,项目代码中通常会结合多种策略来定位元素:优先使用唯一的ID,其次是稳定的CSS类组合,最后才是XPath。在实际使用中,你可能会发现智能体偶尔点击错误。这时,你可以通过增强提示词(Prompt)来指导LLM生成更稳健的选择器,例如要求它“优先使用包含># price_monitor.py import json from datetime import datetime from src.inference.gemini import ChatGemini from src.agent.web import WebAgent from dotenv import load_dotenv import os def monitor_jd_price(product_name, output_file='prices.json'): """ 监控京东商品价格 """ load_dotenv() llm = ChatGemini(model='gemini-2.0-flash', api_key=os.getenv('GOOGLE_API_KEY'), temperature=0) # 开启vision,帮助识别商品图片和价格标签 agent = WebAgent(llm=llm, verbose=True, use_vision=True) # 构建更精确的指令 detailed_query = f""" 请执行以下步骤: 1. 打开 https://www.jd.com 2. 在首页找到搜索框,输入“{product_name}”,然后点击搜索按钮。 3. 在搜索结果页面,找到并点击第一个看起来是“联想拯救者Y7000P”笔记本电脑的商品链接。注意避开广告。 4. 进入商品详情页后,找到商品标题和当前价格。 5. 将标题和价格信息整理后告诉我。 注意:如果页面需要滚动才能看到更多内容,请先向下滚动。每一步操作后请等待页面加载稳定。 """ print(f"开始监控任务: {product_name}") try: response = agent.invoke(detailed_query) result_text = response.get('output', '') print(f"智能体返回结果:\n{result_text}") # 简单解析结果(这里可以做得更复杂,比如用正则表达式提取价格数字) # 假设结果格式为:“标题:xxx,价格:yyy元” import re price_match = re.search(r'价格[::]?\s*([\d,]+\.?\d*)元?', result_text) title_match = re.search(r'标题[::]?\s*(.+)', result_text) data = { 'product': product_name, 'title': title_match.group(1) if title_match else '未提取到', 'price': price_match.group(1) if price_match else '未提取到', 'timestamp': datetime.now().isoformat(), 'source': 'JD' } # 保存到JSON文件 try: with open(output_file, 'r', encoding='utf-8') as f: history = json.load(f) except FileNotFoundError: history = [] history.append(data) with open(output_file, 'w', encoding='utf-8') as f: json.dump(history, f, ensure_ascii=False, indent=2) print(f"价格数据已保存: {data}") except Exception as e: print(f"任务执行失败: {e}") if __name__ == '__main__': monitor_jd_price('联想拯救者Y7000P')
5.3 处理复杂交互与稳定性优化
上面的基础脚本可能会在真实网站中遇到挑战:
- 登录与验证码:京东未登录状态可能无法获取准确价格。解决方案是使用Playwright的
context.storage_state()功能,先手动登录一次并保存登录状态(Cookie、LocalStorage),后续任务加载该状态。对于验证码,可以结合use_vision=True让LLM识别,或者集成第三方打码平台API。 - 页面动态加载:商品列表可能是滚动加载。需要在提示词中明确指示智能体“向下滚动直到看到更多商品”,或者在动作循环中加入判断逻辑。
- 元素定位失败:商品的选择器可能每天变化。更健壮的方法是让LLM基于商品标题、价格等文本特征来识别,而不是依赖固定的CSS路径。可以修改观察模块,提取每个商品块的文本信息,让LLM从中选择。
- 超时与重试:网络不稳定可能导致操作失败。应在
agent.invoke外层添加重试机制,并对特定错误(如TimeoutError)进行捕获和重试。
6. 常见问题排查与性能调优指南
在实际使用中,你肯定会遇到各种问题。下面是我总结的一些典型场景和解决方案。
6.1 智能体陷入循环或行为异常
现象:智能体在页面上重复点击同一个按钮,或者在“输入”和“搜索”之间来回切换,无法推进任务。原因:
- 观察信息不足:LLM没有从提供的页面文本/元素列表中识别出关键信息。
- 提示词不清晰:任务指令存在歧义,LLM无法理解最终目标。
- 动作执行反馈有误:点击动作执行了,但页面状态没有按预期变化(例如触发了JavaScript错误),而观察模块没有捕获到这个变化。
排查步骤:
- 开启
verbose日志:这是最重要的调试工具。查看每一步LLM接收到的观察信息是否准确反映了页面状态。 - 检查观察输出:如果页面文本摘要太短或遗漏了关键信息,需要调整观察模块的提取逻辑。
- 优化提示词:在系统提示中加入更严格的约束,例如:“如果你在连续3个步骤中执行了相似的动作但页面状态没有发生实质性变化,请停止并报告‘可能遇到障碍’。”
- 人工介入检查:让智能体在关键步骤暂停,手动检查浏览器页面是否如预期加载。可能是网站有反爬机制触发了验证码。
6.2 执行速度慢或Token消耗高
现象:完成一个简单任务花费很长时间,或者Gemini API账单费用增长较快。原因:
use_vision=True:传输高清截图会消耗大量Token,且模型处理图片需要时间。- 页面内容过于复杂:观察模块提取的页面文本过长,导致每次请求的提示词都非常庞大。
- LLM响应慢:使用的模型本身推理速度较慢(如某些大型模型)。
- 网络延迟:与Playwright浏览器实例或LLM API的通信存在延迟。
优化策略:
- 按需使用视觉:对于文本信息丰富的页面(如新闻、文档),关闭
use_vision。仅在需要识别图像内容时开启。 - 精简观察内容:修改代码,只提取与当前任务可能相关的页面区域文本。例如,在搜索场景下,只提取搜索框和结果列表区域的DOM。
- 选择高效模型:对于导航类任务,
gemini-2.0-flash或groq-llama3在速度和成本上远优于更大的模型。 - 设置超时与重试:为Playwright操作(如
click、wait_for_selector)设置合理的超时时间,避免因元素未加载而长时间等待。 - 并行化考虑:对于需要监控大量独立页面的任务,可以运行多个智能体实例,但要注意API的速率限制。
6.3 特定网站兼容性问题
现象:在A网站工作正常,在B网站完全无法操作。原因:
- 反机器人检测:网站检测到Playwright的自动化特征(如
navigator.webdriver属性为true)。 - 复杂前端框架:页面由React/Vue等框架动态渲染,DOM结构在初始加载后剧烈变化,导致元素选择器失效。
- 非标准交互:网站使用了自定义的富交互组件(如拖拽、画布),Playwright的标准API难以模拟。
应对方案:
- 启用Stealth模式:Playwright可以通过加载特定插件或设置参数来隐藏自动化特征。需要更深入的Playwright配置。
- 等待网络空闲:在关键操作后,使用
page.wait_for_load_state('networkidle')等待页面动态内容加载完成,再进行观察。 - 使用更稳健的定位器:优先使用
page.get_by_role()、page.get_by_text()或page.get_by_test_id()等基于语义的定位器,这些比脆弱的XPath或CSS选择器更可靠。 - 定制化动作:对于特殊交互,可以在
tools.py中编写新的动作函数,利用Playwright的evaluate直接执行JavaScript来操作页面。
6.4 错误处理与日志记录
一个健壮的自动化系统必须有完善的错误处理和日志记录。
增强错误处理: 在调用agent.invoke时,用try-except包裹,捕获特定异常并执行备用方案(如重试、切换策略、发送警报)。
import time from playwright._impl._errors import TimeoutError as PlaywrightTimeoutError def robust_invoke(agent, query, max_retries=3): for attempt in range(max_retries): try: return agent.invoke(query) except PlaywrightTimeoutError as e: print(f"尝试 {attempt+1} 超时: {e}") if attempt < max_retries - 1: time.sleep(2 ** attempt) # 指数退避 else: raise # 重试次数用尽,抛出异常 except Exception as e: print(f"尝试 {attempt+1} 发生未知错误: {e}") # 可以根据错误类型决定是否重试 break return None结构化日志: 不要只依赖verbose的打印输出。将运行日志(时间戳、用户查询、LLM思考、执行动作、观察结果、最终输出)结构化地记录到文件或数据库中,便于后续分析和复盘。
7. 进阶应用与扩展思路
当你熟悉了基础操作后,可以尝试将这些智能体组合起来,解决更复杂的问题。
7.1 多智能体协作工作流
想象一个场景:你需要收集某个行业趋势报告,需要从新闻网站、学术数据库和社交媒体多源头获取信息。你可以设计三个 specialized agents:
- 新闻采集Agent:专门导航新闻网站,搜索关键词,提取文章摘要和链接。
- 论文检索Agent:专门操作知网、Google Scholar等,下载相关论文摘要。
- 舆情分析Agent:专门在社交媒体平台搜索话题讨论。
然后,用一个协调员Agent来接收用户指令“收集关于‘AI for Science’的近期资料”,并将子任务分发给上述三个智能体,最后汇总结果。这可以通过更复杂的LangGraph来实现,图中包含并行执行和结果聚合的节点。
7.2 与本地工具和数据库集成
Web-Use获取的数据不应该只停留在打印输出。可以轻松地将其与你的数据栈集成:
- 保存到数据库:在动作循环的最后,添加一个
save_to_db工具,将提取的结构化数据(如商品价格、新闻标题)存入SQLite、PostgreSQL或MongoDB。 - 触发本地脚本:当监控到商品价格低于阈值时,让智能体调用一个本地Python脚本,向你发送邮件或钉钉通知。
- 连接本地API:让智能体将提取的文本发送给你本地部署的总结模型(如通过Ollama运行的Qwen),生成一份简洁的报告。
7.3 构建图形化界面与控制面板
对于非技术同事,让他们直接操作命令行和Python脚本是不现实的。你可以利用FastAPI或Gradio,为你的Web-Use智能体快速搭建一个简单的Web界面。界面可以包含:
- 任务输入框。
- 模型和参数选择下拉菜单。
- 任务执行状态显示(实时日志流)。
- 历史任务记录和结果查看。 这样,任何人都可以通过浏览器提交一个网址和指令,启动一个网页自动化任务。
Web-Use项目提供了一个强大的起点,但它不是一个开箱即用、解决所有问题的万能工具。它的真正威力在于其可扩展的架构。理解其“观察-思考-行动”的核心循环,掌握如何调试和优化提示词,学会处理真实网站的复杂性,你就能将它定制成专属于你的、无比顺手的自动化利器。从简单的数据抓取到复杂的多步骤业务流程,边界只取决于你的想象力。
