AI智能体实战:opencode与browser-use结合实现网页自动化操作
1. 项目概述:当AI学会“上网冲浪”
最近在AI应用开发圈里,一个趋势越来越明显:让大模型不再只是“纸上谈兵”,而是能真正操作电脑、使用浏览器,去完成一些需要与真实世界交互的任务。这听起来有点像科幻电影,但得益于像 opencode 和 browser-use 这类框架的出现,它已经变成了我们开发者可以上手实操的现实。我花了几天时间,把这两个工具组合起来,实实在在地跑通了一个自动化流程,整个过程既有踩坑的酸爽,也有跑通后的豁然开朗。这篇文章,我就来和你复盘一下这次“和AI一起搞事情”的实战经历。
简单来说, opencode 是一个开源的AI智能体(Agent)开发框架,它提供了一套标准化的方式来定义AI的能力(Skills)和工作流。而 browser-use 则是一个专门让AI智能体控制浏览器进行自动化操作的库。把它们俩结合,就等于给AI装上了眼睛和手,让它能看懂网页、点击按钮、填写表单、抓取信息。我这次实战的目标很明确:验证这套组合拳的可行性,并梳理出一条从环境搭建到任务执行的清晰路径,看看AI到底能帮我们自动完成哪些繁琐的网页操作。
无论你是对AI应用开发感兴趣的工程师,还是想寻找自动化解决方案的产品经理,或者单纯是个技术爱好者,相信这个结合了前沿框架与具体实操的复盘,都能给你带来一些直接的参考和启发。我们不止聊概念,更会深入到代码、配置和那些只有亲手做过才会遇到的细节里。
2. 核心工具选型与思路解析
为什么是 opencode 加 browser-use ?这个选择背后有我对当前AI智能体开发生态的几点考量。市面上相关的框架和库不少,每个都有其侧重点。
2.1 为什么选择 opencode?
opencode 吸引我的地方在于它的“规范性”和“轻量级”。它不像一些庞大的企业级框架那样沉重,而是专注于为单智能体(Single Agent)提供清晰、模块化的开发模式。它的核心是围绕 Agent 、 Skill 和 Workflow 这几个概念构建的。
- Skill(技能) :这是AI能力的原子单元。比如,“读取文件”、“调用某个API”、“进行数学计算”都可以被定义为一个Skill。在
opencode中,你可以用类似装饰器的方式非常优雅地定义一个Skill,这极大地提高了代码的可读性和可维护性。 - Agent(智能体) :一个Agent就是这些Skills的集合体。你为它配置一个大模型(比如GPT-4、Claude或者本地部署的模型),然后赋予它一系列Skills,它就能根据你的指令,自主判断该调用哪个Skill来完成任务。
- Workflow(工作流) :对于更复杂的任务,你可以通过YAML文件来定义一系列步骤,形成一个工作流。这适合那些有固定流程的自动化任务。
选择 opencode ,意味着我可以用一种结构化的方式去管理AI的能力,而不是写一堆零散、难以复用的脚本。这对于后续扩展和维护至关重要。
2.2 为什么选择 browser-use?
让AI操作浏览器,听起来简单,实则挑战巨大。难点在于如何让AI“理解”网页的复杂结构,并做出准确的操作。 browser-use 在这个问题上提供了一个巧妙的思路。
它没有尝试让AI去直接解析原始的HTML DOM(那太复杂且不稳定),而是为AI提供了一个“简化版”的网页视图。这个视图提取了页面上所有可交互的元素(如按钮、输入框、链接)的文本描述、类型和关键属性,并将其以结构化的方式(比如JSON)呈现给AI。AI基于这个“摘要”来判断该做什么,然后 browser-use 再将AI的指令(如“点击那个写着‘登录’的按钮”)翻译成真实的浏览器操作(如执行一个JavaScript点击事件)。
这种“抽象层”的设计非常高明,它降低了大模型理解网页的门槛,提高了操作的准确性和鲁棒性。相比于直接让AI去啃HTML或依赖坐标点击, browser-use 的方案显得更加可靠和智能。
2.3 组合策略:让 opencode 驱动 browser-use
我的核心思路是: 将 browser-use 封装成一个或多个 opencode 的 Skill 。这样, opencode Agent 就获得了“使用浏览器”这个强大的能力。当用户提出一个涉及网页操作的任务时, opencode Agent 会进行规划,判断是否需要调用“浏览器技能”,然后以正确的参数去调用它。
例如,我可以定义一个名为 navigate_to_website 的Skill,其内部使用 browser-use 打开指定网址;再定义一个 extract_info_from_page 的Skill,用来抓取页面特定信息。这样一来,一个复杂的任务如“去某某电商网站搜索‘无线鼠标’并列出前三个商品的价格”,就可以被分解为:导航 -> 搜索 -> 提取信息,每个步骤对应一个清晰的Skill调用。
这个架构的优点是解耦和灵活。浏览器操作的细节被隐藏在Skill内部,上层的Agent只需要关心任务逻辑。未来如果需要更换浏览器自动化工具,或者增加新的网页操作能力,都只需要修改或新增对应的Skill即可,不会影响整体的Agent逻辑。
3. 环境搭建与核心配置详解
理论说得再多,不如动手搭起来。这部分我会详细记录从零开始搭建整个开发环境的过程,并把几个关键的配置坑点讲清楚。
3.1 基础环境准备
首先,你需要一个Python环境(建议3.9以上)。我习惯使用 conda 或 venv 创建独立的虚拟环境,避免包冲突。
# 创建并激活虚拟环境
python -m venv opencode-browser-env
source opencode-browser-env/bin/activate # Linux/Mac
# 或 opencode-browser-env\Scripts\activate # Windows
接下来安装核心库。这里要注意版本兼容性,我使用的是写作时相对稳定的版本。
pip install opencode # 安装opencode核心库
pip install browser-use # 安装浏览器自动化库
pip install playwright # browser-use依赖Playwright来驱动浏览器
playwright install chromium # 安装Chromium浏览器驱动,这是必须的一步
注意 :
playwright install这一步可能会因为网络问题失败。如果遇到,可以尝试设置国内镜像源,或者耐心多试几次。这是第一个小坑,但绕不过去。
3.2 opencode 的初始化与模型配置
安装好后,我们需要初始化一个 opencode 项目,并配置最关键的部分——大模型。
# 初始化一个opencode项目(如果框架提供了cli工具)
# 或者,更常见的是,我们直接创建一个项目结构。
mkdir my_ai_agent && cd my_ai_agent
创建一个 config.yaml 或直接在代码中配置。核心是为Agent指定一个LLM(大语言模型)。 opencode 通常支持通过环境变量或配置文件来设置API Key和模型类型。
# config.yaml 示例
agent:
name: "WebAssistant"
llm:
provider: "openai" # 也可以是 anthropic, azure_openai, 或 local (如 ollama)
model: "gpt-4-turbo-preview"
api_key: ${OPENAI_API_KEY} # 建议从环境变量读取
在代码中,你可以这样加载配置并创建Agent:
import opencode
from opencode.agent import Agent
import os
from dotenv import load_dotenv
load_dotenv() # 加载.env文件中的环境变量
# 从配置文件或字典创建Agent配置
agent_config = {
"name": "WebAssistant",
"llm": {
"provider": "openai",
"model": "gpt-4-turbo",
"api_key": os.getenv("OPENAI_API_KEY"),
"base_url": os.getenv("OPENAI_BASE_URL", None) # 如果需要自定义端点
}
}
my_agent = Agent.from_config(agent_config)
实操心得 :模型的选择直接影响智能体的“智商”和成本。对于复杂的网页理解和任务规划,GPT-4系列的效果远好于GPT-3.5。如果考虑成本或数据隐私,可以探索本地模型(如通过Ollama部署的Llama 3、Qwen等),但需要评估其指令跟随和规划能力是否足够。一开始验证想法时,用GPT-4是效率最高的。
3.3 browser-use 的启动与初始化
browser-use 需要一个浏览器实例来工作。我们需要在代码中启动它,并将其与我们的Agent关联起来。
from browser_use import Browser, BrowserConfig, Controller
import asyncio
async def create_browser():
# 配置浏览器,例如设置无头模式(不显示界面)、超时时间等
config = BrowserConfig(
headless=False, # 调试时设为False可以看到浏览器操作,生产环境设为True
disable_security=True, # 有时需要禁用安全策略以访问某些页面
)
browser = Browser(config=config)
controller = Controller(browser)
return controller
# 由于browser-use是异步的,我们需要在异步上下文中运行
async def main():
controller = await create_browser()
# ... 后续将controller与opencode skill结合
这里的关键是 BrowserConfig 。 headless=False 在开发时非常有用,你能亲眼看到AI在操作什么,方便调试。但当你写好一个自动化服务部署到服务器时,一定要记得改为 headless=True 。
3.4 关键集成:将 browser-use 封装为 opencode Skill
这是整个集成的核心步骤。我们要创建一个自定义的Skill,在其内部使用 browser-use 的Controller。
from opencode.skills import skill
from pydantic import Field
from browser_use import Controller
class BrowserSkill:
def __init__(self, controller: Controller):
self.controller = controller
@skill(
name="navigate_to_url",
description="导航到指定的网址",
input_schema={
"url": Field(..., description="要访问的完整网址,例如 https://www.example.com")
}
)
async def navigate(self, url: str) -> str:
"""打开一个网页。"""
try:
# browser-use 的 goto 方法
await self.controller.goto(url)
# 可以获取当前页面的摘要信息返回给Agent
page_state = await self.controller.get_page_state()
return f"成功导航到 {url}。页面标题是:{page_state.title}"
except Exception as e:
return f"导航到 {url} 时出错:{str(e)}"
@skill(
name="extract_text_by_selector",
description="根据CSS选择器提取页面上的文本内容",
input_schema={
"selector": Field(..., description="CSS选择器,例如 'h1.product-title'"),
"all_elements": Field(False, description="是否提取所有匹配元素,默认只取第一个")
}
)
async def extract_text(self, selector: str, all_elements: bool = False) -> str:
"""从页面提取文本。"""
# 这里简化处理,实际browser-use可能提供更智能的元素定位和内容提取
# 一种方法是使用controller执行JavaScript
if all_elements:
script = f"Array.from(document.querySelectorAll('{selector}')).map(el => el.innerText).join('|||')"
else:
script = f"document.querySelector('{selector}')?.innerText || '未找到元素'"
result = await self.controller.evaluate(script)
return result
定义好Skill类后,我们需要在创建Agent时,将这个Skill实例注册进去。
async def main():
# 1. 创建浏览器控制器
controller = await create_browser()
# 2. 创建浏览器Skill实例
browser_skill = BrowserSkill(controller)
# 3. 创建Agent,并注册技能
my_agent = Agent.from_config(agent_config)
my_agent.register_skill(browser_skill.navigate)
my_agent.register_skill(browser_skill.extract_text)
# 4. 现在可以给Agent下达任务了
task = "请打开百度首页,然后搜索一下今天的天气。"
response = await my_agent.run(task)
print(response)
这个流程清晰地展示了如何将底层工具的能力,通过Skill的形式“嫁接”到AI智能体上,形成它可理解和调用的能力。
4. 实战任务分解与自动化流程实现
环境搭好了,技能也定义了,现在我们来设计一个具体的实战任务,看看AI如何一步步完成。我设计了一个相对经典且实用的场景: 商品价格监控 。具体任务是:“去京东网站,搜索‘联想拯救者笔记本电脑’,获取排名前五的商品名称和价格,并计算平均价格。”
4.1 任务规划与技能链设计
对于这个任务,一个合格的Agent应该能自己规划出以下步骤:
- 导航 :打开京东首页 (
navigate_to_url)。 - 交互 :找到搜索框,输入关键词“联想拯救者笔记本电脑”,并点击搜索按钮。这可能需要一个新的Skill,比如
input_text_and_submit。 - 等待与筛选 :等待搜索结果页面加载完成,并定位到商品列表区域。
- 信息提取 :从商品列表中,提取前五个商品的名称和价格 (
extract_text_by_selector或更专用的extract_product_list)。 - 数据处理 :解析提取到的文本,将价格转换为数字,并计算平均值。这可以是一个纯数据处理的Skill,如
calculate_average_price。
我们需要为步骤2和5创建新的Skill,并完善步骤4的提取逻辑。
4.2 实现交互与数据提取技能
首先,实现一个更通用的交互技能,用于输入文本和点击。
@skill(
name="input_and_click",
description="在页面上找到匹配描述的元素,输入文本或点击它",
input_schema={
"element_description": Field(..., description="要交互的元素的描述,如‘搜索框’、‘登录按钮’"),
"action": Field(..., description="要执行的动作,'type' 或 'click'"),
"text": Field(None, description="如果是'type'动作,需要输入的文本")
}
)
async def interact_with_element(self, element_description: str, action: str, text: str = None) -> str:
"""
依赖browser-use的AI元素定位能力。
browser-use的controller可以让LLM根据描述去找到元素并操作。
这里假设我们有一个更高级的browser-use方法。
"""
# 注意:这是理想化的接口,实际browser-use的API可能需要调整
# 一种可能的实现是使用controller的`perform_action`方法,传入自然语言指令
if action == 'click':
result = await self.controller.ai_click(element_description)
return f"已点击‘{element_description}’。"
elif action == 'type' and text:
result = await self.controller.ai_type(element_description, text)
return f"已在‘{element_description}’中输入文本:{text}。"
else:
return "无效的动作指令。"
其次,实现一个专门针对京东搜索结果页的商品提取技能。这需要事先分析京东页面的HTML结构。
@skill(
name="extract_jd_products",
description="从京东搜索结果页提取商品列表信息",
input_schema={
"max_items": Field(5, description="需要提取的最大商品数量")
}
)
async def extract_jd_products(self, max_items: int = 5) -> str:
"""
这是一个针对特定网站(京东)的专用技能。
通过分析京东搜索结果的DOM结构,使用CSS选择器精准抓取。
"""
# 京东商品列表项的选择器(请注意,网站结构可能随时变化,这是最大的风险点)
product_selector = ".gl-item"
name_selector = ".p-name em" # 商品名称
price_selector = ".p-price i" # 商品价格
product_info_list = []
# 使用JavaScript批量获取元素信息,比多次evaluate效率高
script = f"""
(() => {{
const items = document.querySelectorAll('{product_selector}');
const results = [];
const limit = Math.min(items.length, {max_items});
for (let i = 0; i < limit; i++) {{
const item = items[i];
const nameEl = item.querySelector('{name_selector}');
const priceEl = item.querySelector('{price_selector}');
results.push({{
name: nameEl ? nameEl.innerText.trim() : '未知商品',
price: priceEl ? priceEl.innerText.trim() : '未知价格'
}});
}}
return JSON.stringify(results);
}})()
"""
try:
result_json = await self.controller.evaluate(script)
import json
products = json.loads(result_json)
formatted_output = "提取到的商品信息:\n"
for idx, prod in enumerate(products, 1):
formatted_output += f"{idx}. {prod['name']} - 价格:{prod['price']}\n"
return formatted_output
except Exception as e:
return f"提取商品信息时发生错误:{str(e)}。可能是页面结构已变化,需要更新选择器。"
4.3 组装任务与运行Agent
现在,我们有了导航、交互(输入点击)、专用提取三个核心技能。我们可以通过一个工作流(Workflow)YAML文件来定义整个任务,或者直接给Agent一个复杂的自然语言指令,让它自己规划。
# workflow.yaml (opencode工作流示例)
name: jd_price_monitor
tasks:
- name: open_jd
skill: navigate_to_url
inputs:
url: "https://www.jd.com"
- name: search_product
skill: input_and_click
inputs:
element_description: "搜索框"
action: "type"
text: "联想拯救者笔记本电脑"
- name: click_search_button
skill: input_and_click
inputs:
element_description: "搜索按钮"
action: "click"
- name: wait_for_results
# 这里可能需要一个‘等待’技能,或者browser-use内部已处理
skill: delay
inputs:
seconds: 3
- name: extract_products
skill: extract_jd_products
inputs:
max_items: 5
在代码中加载并运行这个工作流:
async def run_workflow():
controller = await create_browser()
browser_skill = BrowserSkill(controller)
my_agent = Agent.from_config(agent_config)
my_agent.register_skill(browser_skill.navigate)
my_agent.register_skill(browser_skill.interact_with_element)
my_agent.register_skill(browser_skill.extract_jd_products)
# 假设opencode支持从YAML加载工作流
workflow = Workflow.from_yaml('workflow.yaml')
final_result = await my_agent.execute_workflow(workflow)
print("任务执行结果:")
print(final_result)
# 最后别忘了关闭浏览器,释放资源
await controller.close()
如果直接使用自然语言指令,代码会更简洁,但对Agent的规划能力要求更高:
task = """
请执行以下操作:
1. 打开京东网站 (www.jd.com)。
2. 在搜索框中输入“联想拯救者笔记本电脑”并进行搜索。
3. 等待搜索结果加载完成。
4. 从结果中提取前5个商品的名称和价格。
5. 将结果整理成清晰的列表给我。
"""
response = await my_agent.run(task)
print(response)
无论采用哪种方式,最终你都能看到AI自动操作浏览器,并返回结构化的商品信息。这个过程充满了成就感,也让你直观感受到AI智能体的潜力。
5. 避坑指南与效能优化实战
在实际操作中,理想很丰满,现实却会遇到各种骨感的问题。下面是我在实战中踩过的坑以及总结出的优化技巧。
5.1 稳定性挑战:网页动态加载与元素定位
- 问题 :现代网页大量使用JavaScript动态加载内容。你可能刚打开页面时,想要的商品列表还没加载出来,AI就已经开始执行“提取”技能了,结果自然是失败。
- 解决方案 :
- 显式等待 :在关键步骤(如点击搜索后)后,增加一个“等待”技能。可以是一个简单的
asyncio.sleep,也可以是更智能的等待某个特定元素出现。 - 重试机制 :在Skill内部实现重试逻辑。例如,提取信息失败时,自动等待1秒再试一次,最多重试3次。
- 使用 browser-use 的内置等待 :
browser-use的Controller在进行操作(如ai_click)时,通常内部会等待页面稳定。确保你使用的是这些高级方法,而不是直接执行原始的、不稳定的JavaScript。
- 显式等待 :在关键步骤(如点击搜索后)后,增加一个“等待”技能。可以是一个简单的
5.2 泛化能力:网站结构变化
- 问题 :
extract_jd_products这个技能严重依赖京东的CSS选择器。只要京东前端一改版,这个技能立刻失效。这是专用技能的最大弱点。 - 解决方案 :
- 抽象通用提取技能 :尝试开发不依赖固定选择器的技能。例如,利用
browser-use提供的页面摘要,让AI根据语义(如“找到所有看起来像商品价格的数字”)来提取。这更接近AI的原生能力,但准确率可能稍低。 - 配置化选择器 :将CSS选择器作为外部配置文件,当网站改版时,只需更新配置文件,而无需修改代码。
- 混合策略 :对于核心的、稳定的业务(如监控自家公司官网),可以使用专用技能保证精度;对于探索性的、多网站的任务,则依赖AI的语义理解能力。
- 抽象通用提取技能 :尝试开发不依赖固定选择器的技能。例如,利用
5.3 效率与成本优化
- 问题 :每次操作都让大模型(如GPT-4)来规划每一步,Token消耗大,速度慢,成本高。
- 解决方案 :
- 技能粒度设计 :不要设计过于细碎的技能。比如,把“登录”作为一个技能,而不是“输入用户名”、“输入密码”、“点击登录”三个技能。让一次模型调用完成一个逻辑上完整的子任务。
- 工作流(Workflow)优先 :对于固定流程的任务,尽量使用YAML定义的工作流。工作流的执行引擎可以直接调用技能,无需大模型反复规划,极大节省成本和时间。
- 模型分级 :在规划阶段使用能力强但贵的模型(如GPT-4),在执行具体的、定义清晰的技能时,可以尝试使用更小、更快的模型(如GPT-3.5-Turbo甚至本地小模型),如果该技能对逻辑要求不高的话。
5.4 错误处理与日志记录
- 问题 :自动化流程一旦出错,如果日志不清晰,很难定位问题出在哪一步。
- 解决方案 :
- 结构化日志 :在每个Skill的开始和结束,以及关键操作点,记录详细的日志,包括时间戳、技能名、输入参数、输出结果、以及任何错误信息。
- 截图功能 :在关键步骤失败时,自动调用浏览器截图功能,保存当时的页面状态。这对于调试元素定位问题 invaluable(无比珍贵)。
- 异常捕获与友好反馈 :在Skill内部做好异常捕获,不要让底层库的复杂异常直接抛给用户或Agent。应该转化为人类或AI能理解的友好错误信息,并提示可能的解决方向。
5.5 安全与伦理考量
- 问题 :自动化工具可能被滥用,用于爬虫、刷单等违反网站规则或法律法规的行为。
- 注意事项 :
- 遵守 robots.txt :在开发技能时,可以加入对目标网站
robots.txt的检查。 - 设置合理的速率限制 :在Skill或工作流中,加入操作之间的延迟,模拟人类行为,避免对目标服务器造成压力。
- 明确使用边界 :在项目文档和内部分享中,强调该技术应用于提高个人工作效率、进行合规的数据分析等正当场景,切勿用于恶意用途。
- 遵守 robots.txt :在开发技能时,可以加入对目标网站
6. 扩展想象:还能用这个组合做什么?
跑通一个价格监控的Demo只是开始。 opencode + browser-use 这个组合的想象力空间非常大,它本质上是赋予了AI“在图形界面中完成任务”的能力。以下是一些我想到的、值得尝试的方向:
6.1 自动化办公与数据填报 许多内部管理系统或Web版办公软件(如OA、CRM、ERP)的操作是重复且规律的。你可以训练一个Agent,让它每天自动登录系统,下载最新的销售报表,提取关键数据,并填写到另一个汇总系统中。这能将人力从枯燥的“人肉搬运”工作中解放出来。
6.2 智能信息聚合与简报生成 让Agent每天早上自动打开你常看的几个新闻网站、技术博客、行业报告页面,提取标题和摘要,然后让另一个文本处理Skill进行总结归纳,最终生成一份个性化的每日晨报,通过邮件或通讯工具发送给你。
6.3 自动化测试与监控 对于前端开发者或测试人员,可以编写一个Agent,让它按照测试用例(自然语言描述)去操作你的Web应用,比如“注册一个新用户”、“将商品加入购物车并结算”。Agent可以记录下操作过程中是否出现错误、页面响应是否正常,甚至能对UI元素进行简单的“视觉”校验(结合截图和OCR)。这比编写传统的UI自动化测试脚本更灵活、更接近自然语言。
6.4 交互式学习与助手 结合知识库和浏览器能力,可以打造一个更强大的研究助手。你可以问它:“帮我查查最近三个月关于‘AI智能体’融资的新闻,并总结出投资方和金额。” Agent会规划任务:打开特定创投媒体网站 -> 搜索关键词 -> 翻页 -> 提取信息 -> 整理分析。这比单纯用大模型生成虚构内容要可靠得多。
6.5 跨平台自动化桥梁 browser-use 目前主要针对浏览器,但思路可以扩展。理论上,任何能提供结构化界面描述的工具,都可以被集成。未来,或许会出现 desktop-use 来控制桌面应用, mobile-use 来控制手机模拟器。 opencode 作为智能体大脑,可以统一调度这些“手”和“眼”,实现真正的跨平台、跨应用的复杂自动化。
这次实战让我深刻体会到,AI智能体开发正在从一个炫技的概念,快速落地为可用的工程实践。 opencode 提供了清晰的架构, browser-use 解决了关键的“最后一公里”交互问题。虽然目前它们在处理极端复杂、充满变数的网页时仍会“翻车”,但对于定义相对清晰、流程化的任务,已经展现出惊人的潜力。最大的挑战可能不再是技术本身,而是我们如何更好地设计任务、分解步骤,并教会AI应对那些不可避免的异常情况。这本身,就是一个非常有意思的、人与AI协同进化的过程。
更多推荐



所有评论(0)