十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

本地AI Agent浏览器自动化:基于MCP协议的Browser Copilot搭建实战

本地AI Agent浏览器自动化:基于MCP协议的Browser Copilot搭建实战 最近一直在折腾一件事怎么让本地跑的 AI Agent 不只在对话框里讲道理而是真的动手去操作浏览器干活。起因很现实。我有几十个网页需要定期采集结构化信息再跟本地表格做对照。以前两条路要么写 Playwright 脚本但每个站点布局不一样光是等元素、定位节点就够写几天要么人工复制粘贴枯燥不说还容易漏。后来我试着让本地 AI Agent 通过 MCP 协议直接控制浏览器把一个自研的 Browser Copilot 方案跑通了——效果比我预期好不少。这篇文章把这个项目的技术选型、搭建过程、实战效果和踩坑经历完整记录下来给同样想自己搭一套AI 操纵浏览器能力的朋友做参考。1. 一个真实的痛点只会聊天的 AI和真正干活的 AI1.1 从搜资料到干活就差一个浏览器大模型刚火起来那阵大家发现它很能聊但真要让它干点实事——比如帮我把这个页面里的报价下载下来——它就只能给步骤建议然后你自己动手。这里面的差距不在语言能力而是感知-行动闭环。人浏览网页时每天都在做三件事看页面结构、判断下一步点哪里、执行点击输入。这些动作对 AI 来说需要三样东西叠加一个能观察页面状态的接口、一个能执行浏览器操作的接口、一个能把任务拆成动作序列的决策大脑。浏览器就是 AI 从信息处理工具变成行动代理的那道门。让 Agent 打开网页、读内容、填表单、点按钮、翻页、截图绝大部分数字化工作就能自动化了。1.2 为什么不用现成的浏览器自动化脚本有人会问Playwright、Selenium 都这么成熟了直接写脚本不就行了这恰恰是问题所在。传统自动化脚本的痛点是人肉写选择器每个网站结构不同#main div.content a.title这种选择器换个站点就要重写页面改版脚本立刻失效你写的是死流程遇到弹窗、懒加载、验证码这类意外就卡死AI Agent 的方案是把写流程变成定目标。你不用告诉它点哪个按钮只需要说去搜索 AI Agent 的定义把前五条结果整理出来它自己会决定打开哪个页面、怎么找搜索框、怎么提取内容。这个思路的本质变化是从指令编程变成目标驱动。但这里有个绕不开的问题Agent 怎么观察到网页又怎么操作网页直接给它一个浏览器窗口肯定不行需要在模型和浏览器之间做一层结构化接口。这层接口就是 MCP 协议。2. MCP 协议解决了什么把浏览器变成 Agent 的USB 外设2.1 MCP 的核心工作方式MCPModel Context Protocol模型上下文协议是 Anthropic 在 2024 年底开源的一个开放协议目标是统一 AI 应用与外部工具、数据源之间的通信方式。你可以把它理解成 AI 世界的 USB-C 接口鼠标、键盘、显示器各有各的协议但因为都统一到了 USB-C任何设备插上就能用。MCP 要做的就是这个事——让任何 AI 应用都能以同样的方式接入任何工具。MCP 里有几个核心角色角色作用Host运行 AI Agent 的主程序比如 Claude Desktop、自研 Agent 进程Client与 MCP Server 建立连接的客户端负责协议通信Server暴露工具的进程比如浏览器控制服务ToolServer 提供的可调用能力每个工具包含名称、描述、参数结构一次完整调用是这样跑的Agent 启动时通过 MCP Client 向 Server 发initialize握手拿到当前 Server 支持的工具列表。工具描述会被注入到模型的提示上下文中。模型根据用户任务决定调用哪个工具、传什么参数Client 再通过tools/call请求把调用发给 ServerServer 执行完把结果返回给模型。模型看到结果后决定下一步动作直到任务结束。这个过程用的是 JSON-RPC 2.0通信方式支持 stdio本地子进程和 HTTP/SSE远程服务意味着你可以把浏览器控制服务跑在另一台机器上Agent 在本地通过网络远程操作。2.2 MCP 和 Function Calling 不是一回事很多人把 MCP 和 Function Calling函数调用搞混其实它们解决的不是同一个层面的问题。Function Calling 是模型推理时的一种能力模型输出一个结构化的 JSON表示我想调用某个函数参数是这些。它解决的是模型如何表达调用意图。MCP 解决的是外部能力如何标准化接入 Agent 系统。一个 MCP Server 对应一套能力集合不管背后的实现是 Python、Node.js 还是别的什么对 Agent 来说看到的都是一份标准工具清单。两者可以配合使用Agent 的推理引擎支持 Function Calling把 MCP 工具清单转换成函数的 schema 传给模型模型输出调用意图后再由 MCP Client 去执行。我项目里就是这么干的后面会说具体代码。2.3 为什么浏览器操控特别适合 MCP浏览器操作的动作集合非常固定打开页面、点击、输入、下拉选择、滚动、截图、读控制台。这套动作天然适合做成标准工具。在 MCP 出现之前每个 AI 应用想让模型控制浏览器都得自己封装接口。项目 A 封装一套click_by_selector项目 B 又封装一套click_element接口风格各不相同模型接入成本高工具描述写起来也费劲。用 MCP 之后浏览器能力被统一成一套工具集。最典型的就是微软官方维护的 Playwright MCP Server——它把 Playwright 的自动化能力封装成了标准 MCP 工具任何支持 MCP 的 Agent 都能直接用不用关心底层是怎么定位元素的。工具覆盖面很全核心的几个工具作用browser_navigate打开指定 URLbrowser_click点击页面上的元素browser_type / browser_press_key输入文本、按键盘browser_snapshot获取页面无障碍树快照结构化读取页面browser_screenshot页面截图browser_scroll滚动页面browser_select_option处理下拉框browser_tab_list / browser_tab_switch / browser_tab_close标签页管理browser_console_messages读取浏览器控制台日志browser_file_upload文件上传这套工具出现后等于把浏览器变成了 AI Agent 可以即插即用的USB 外设。你要做的只是选一个 Agent 框架接上这个 Server然后告诉模型要干什么。3. 方案选型与架构拆解Browser Copilot 各组件怎么配合3.1 三条技术路线对比在动手前我先梳理了三类AI 控制浏览器的实现路线各有优劣纯视觉方案给模型截屏让视觉模型看图后输出点击坐标或动作。类似 GPT-4o 操作电脑的演示。这套方案上限高但需要很强的视觉模型推理成本高、延迟大本地小模型很难撑起来。代码生成方案让模型生成 Playwright 脚本再由解释器执行。灵活但脚本一旦报错模型得反复调试稳定性一般而且每轮对话都要塞大量上下文。MCP 工具方案浏览器操作被预封装成细粒度工具模型只需要做选择工具传参数的决策。工具结果的返回格式是结构化的模型容易理解。对比下来MCP 方案最平衡。它不要求模型从零写代码也不依赖多强的视觉能力只要模型能根据工具描述做出选择就行。这对本地部署尤为重要因为本地模型的参数规模决定了它做不了太复杂的推理。3.2 我的选型本地 LLM Ollama Playwright MCP我的最终技术栈是这样本地 LLMOllama 运行qwen2.5:7b-instruct。选它的原因是支持 tool calling且中文理解和指令跟随在 7B 级别里比较稳MCP Serverplaywright/mcp微软官方维护负责所有浏览器操作MCP ClientPython 的mcpSDK自研一个轻量 Agent 循环浏览器Chromium由 Playwright 驱动所有组件全部跑在本地。选择本地化部署的原因有三个第一是隐私。这些任务涉及内部业务数据不想经过任何第三方 API。第二是成本。几十个网页的采集任务如果全走云端视觉模型一次完整跑下来可能几百次调用费用不小本地 7B 模型的速度虽然不算快但完全免费。第三是离线和内网场景。很多企业内部网络本来就访问不了外部 API本地模型加 MCP 的方案可以完全离线运作。3.3 一次完整调用的数据流我画不出来流程图但可以把调用链路用文字描述清楚。假设给 Agent 的任务是打开百度搜索 AI Agent 是什么返回前五条结果推理引擎本地 Qwen收到任务后在上下文中看到 MCP Client 注入的工具清单其中包含browser_navigate描述是打开指定 URL。模型决策先调browser_navigate参数是{url: https://www.baidu.com}。MCP Client 把这个调用请求按 JSON-RPC 格式发给 Playwright MCP ServerServer 驱动 Chromium 执行导航。执行完成后Server 返回结果结果里包含页面状态和可见文本摘要。Client 把结果回填给模型。模型继续决策需要读取页面结构才能找到搜索框于是调browser_snapshot。返回的是页面的无障碍树文本比如按钮、输入框、链接的文字和位置信息。模型看到输入框的标识信息后调browser_type输入AI Agent调browser_press_key按回车。页面跳转后再调browser_snapshot提取搜索结果。可以看到整个循环就是观察 → 决策 → 动作 → 观察的反复MCP 充当了模型和浏览器之间的翻译层。这个结构不依赖特定模型只要模型支持 tool calling换成 Claude、GPT 或者别的模型工具接入层不需要改。4. 环境准备与最小 Demo先把浏览器MCP 化4.1 安装 Ollama 并拉取工具调用模型第一步是本地的模型推理服务。我用的是 Ollama安装方式很简单去官网下载对应平台的安装包装完启动服务即可。之后拉取模型ollama pull qwen2.5:7b这里有个容易踩的坑并不是所有模型都支持工具调用。早期拉过纯基座模型对话没问题但工具调用格式完全不对。后来用ollama show qwen2.5:7b检查模板发现它在生成工具调用上是没问题的。如果你用别的模型建议先确认它是否支持 OpenAI 风格的 function calling否则后面 Agent 会一直假装调用工具实际却只输出文字。拉完模型先做一次裸测确认工具调用能力正常import ollama client ollama.Client() response client.chat( modelqwen2.5:7b, messages[{role: user, content: 调用工具打开 https://example.com}], tools[{ type: function, function: { name: browser_navigate, description: 打开指定网址, parameters: { type: object, properties: { url: {type: string} }, required: [url] } } }] ) print(response.message.tool_calls)正常的话会看到模型输出工具调用意图包含函数名和参数。这一步如果不过关后面全白搭建议提前排掉。4.2 启动 Playwright MCP Server安装依赖npm install -g playwright/mcp playwright install chromiumplaywright install会下载 Chromium 内核这一步在国内网络环境下可能比较慢可以设置PLAYWRIGHT_DOWNLOAD_HOST环境变量指向镜像源。装完后直接启动npx playwright/mcplatest --headless --user-data-dir /tmp/browser-profile参数说明--headless表示无头模式适合纯后台采集任务--user-data-dir指定独立的浏览器用户目录避免污染本机默认浏览器配置。如果调试阶段想看浏览器动作去掉--headless略等一会儿就能看到窗口里的操作过程。启动后用 MCP Inspector 可以快速验证npx playwright/mcplatest --inspector它会启动一个可视化调试面板可以手动调用工具点开browser_navigate填个 URL 测试一下。这个工具对排查 Server 是否正常工作非常方便。4.3 用 Python 写一个最小 MCP ClientServer 跑起来之后写一个最小的 Python MCP Client先确认工具列表能拿到import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client server_params StdioServerParameters( commandnpx, args[playwright/mcplatest, --headless], ) async def main(): async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() result await session.list_tools() for tool in result.tools: print(tool.name, -, tool.description) res await session.call_tool( browser_navigate, {url: https://example.com} ) print(res) asyncio.run(main())这个代码里有两个关键动作initialize()完成 MCP 握手之后才能获取工具call_tool()是实际调用浏览器工具的核心语句。整个 Session 结束时会自动关闭浏览器进程。4.4 让 Agent 走出第一步有了工具列表还不够要让本地模型自己决策调用哪个工具核心是把 MCP 工具清单翻译成模型的 tools 参数。我这里做了一个简单的工具适配器def mcp_tool_to_ollama_tool(mcp_tool): return { type: function, function: { name: mcp_tool.name, description: mcp_tool.description, parameters: mcp_tool.inputSchema, } }把 MCP 返回的每个工具转成 Ollama 能识别的格式然后塞进client.chat(tools...)。模型输出tool_calls后循环执行并回填结果这就是一个最朴素的 Agent 循环。这一步跑通后一个纯本地的、能通过 MCP 操作浏览器的 AI Agent 框架就搭起来了。从聊天机器人到能干活的 Agent差别就在这几行适配代码。5. 三个实战任务从搜索到表单看 Agent 如何操作浏览器5.1 任务一搜索并提取结果标题和链接完整任务指令是打开百度搜索AI Agent 是什么把搜索结果页前五条结果的标题和链接整理出来。Agent 的行动轨迹如下我通过操作日志整理出来的真实调用序列[agent] 调用 browser_navigate({url: https://www.baidu.com}) [agent] 调用 browser_snapshot() 查看页面结构 [agent] 找到搜索输入框(idkw)调用 browser_type({id: kw, text: AI Agent 是什么}) [agent] 调用 browser_press_key({key: Enter}) [agent] 调用 browser_snapshot() 读取搜索结果 [agent] 提取前五条结果的标题和链接返回最终答案全过程大概 6 到 8 次工具调用耗时约两分钟大头在本地模型推理。有个细节值得说百度搜索结果里的链接是经过跳转包装的地址直接读href拿到的是重定向中间的 URL。我在 Agent 的 system prompt 里加了一句链接需展示真实可访问的地址它会自己决定先打开目标页面拿最终 URL还是直接展示当前结果中的链接。这就是目标驱动和规则脚本的区别——模型会自己找解决路径。运行结果基本满意五条结果都能准确提取。唯一的问题是偶尔会把相关搜索里的词条当成搜索结果我在任务描述里加了只提取标题标注为结果的条目后好转。5.2 任务二滚动页面并截图存档采集类任务里截屏用于存档非常关键。Agent 可能不需要看懂截图但人需要事后复核。我造了一个本地长页面用来测试约 4000 像素高中间有一段目标文字。任务指令打开 file:///tmp/demo-page.html滚动到出现目标区域的位置把页面截图保存到本地。Agent 的路径是[agent] 调用 browser_navigate({url: file:///tmp/demo-page.html}) [agent] 调用 browser_snapshot() 查看页面 [agent] 调用 browser_scroll({direction: down}) [agent] 调用 browser_snapshot() 确认目标区域是否出现 [agent] 调用 browser_screenshot({type: full_page})browser_scroll支持方向参数但滚动到具体位置需要模型自己通过多次滚动来逼近。这过程看着有点笨拙但足够可靠。截图可以配置保存到指定目录Playwright MCP 也支持把截图图片返回给模型——本地 7B 模型看不懂图但如果你把模型换成 Qwen-VL 或云端视觉模型就多了一条看图认识页面的通道定位元素会更精准。5.3 任务三自动填写并提交表单这个任务最直观也是很多自动化需求的缩影填写表单、点击提交、校验结果。我本地做了一个测试表单包含姓名和邮箱两个输入框一个提交按钮提交后页面会显示提交结果。任务指令打开 file:///tmp/demo-form.html在姓名输入框填张三邮箱填zhangsanexample.com点击提交读取返回结果。Agent 执行时有一个有意思的细节它先browser_snapshot看到表单结构注意到输入框没有id属性只有placeholder。但 Playwright MCP 的 snapshot 会把可访问名称也返回出来模型就根据 placeholder 文本定位到了输入框。这比传统脚本里死用 CSS 选择器要鲁棒得多——因为模型读的是页面语义结构而不是硬编码的 DOM 路径。整个任务五步完成导航 → 快照 → 填名字 → 填邮箱 → 点提交。全程没有人工干预。最终页面显示的提交成功张三 zhangsanexample.com被 Agent 读出来作为最终回答任务闭环。三个任务跑下来我的直观感受是在任务链条清晰、目标明确的场景下本地 7B 模型加 MCP 工具方案已经具备可用性。它真的能像一个小实习生一样一步步按照指令把活干完。但要是任务描述模糊或者页面结构特别复杂模型还是会迷路出错。6. 踩坑实录本地模型和浏览器自动化结合时的九类问题6.1 模型不调用工具而是假装调用这是我遇到的第一个问题现象很典型。模型没有输出结构化的 tool_calls而是在回答文本里写我将打开浏览器访问 example.com然后就没有然后了。排查过程分了三步检查模型是否支持工具调用。用ollama show qwen2.5:7b看模型模板确认支持直接裸调测试用最简单的 tools 定义验证工具调用输出能正常返回检查我自己的 Agent 循环代码发现问题出在工具 schema 转换时inputSchema里的title字段导致 Ollama 解析失败。修复方式是在适配器里把inputSchema的title字段剔除只保留type、properties、required。改完之后工具调用恢复正常。另一个影响因素是温度参数。本地模型对工具调用的格式敏感temperature 调太高容易在 JSON 输出上跑偏。我最后固定在temperature0.3左右既保留了少量随机性又不至于输出格式混乱。6.2 页面快照太大把本地模型上下文撑爆这是接入真实网站后遇到的最大瓶颈。有些门户网站的页面无障碍树快照展开后有几万字符一轮过去 7B 模型 8K 的上下文直接爆掉。我从两个方向处理用 Playwright MCP 的--max-snapshot-length参数限制快照长度默认值是 2000 字符对复杂页面可以再调低在 system prompt 里要求 Agent优先使用最小化动作避免重复获取全页面快照需要查看局部时先用滚动定位再快照。调低快照长度后模型看到的信息少了定位元素偶尔会失败但配合滚动重试整体成功率反而高于上下文被撑爆的情况。6.3 浏览器状态和多标签带来的迷路Agent 操作浏览器跟人在浏览器里干活一样是有状态的。最常见的故障是页面中某个链接设置了target_blankAgent 点击后浏览器自动切到了新标签页但模型还想着之前那个页面的上下文继续操作老页面的元素——结果自然是找不到元素。我用的对策是在任务 prompt 里明确约束点击链接后始终在当前标签页内工作如需切换标签页先调用 browser_tab_list 确认所有标签页信息再决定切换到哪个。加上这个约束后标签页迷路的问题大幅减少。6.4 非视觉模型的截图盲区本地 7B 大语言模型基本不具备视觉理解能力给它截图等于白给。我一开始让 Agent 频繁截图用于定位后来发现它不仅看不懂还白白消耗了工具调用时间。调整后定位元素一律依赖browser_snapshot的无障碍树文本截图只用于最终存档和人工审计。如果你的机器配置允许跑 Qwen-VL 这类多模态模型可以考虑双模型方案大模型做视觉判断小模型做文本决策。但部署复杂度和显存消耗会明显上升优先级不高。6.5 安全边界Agent 不该碰的东西本地 Agent 拿到浏览器控制权后风险是实打实的。最危险的一点是如果你直接用默认的用户目录启动浏览器Agent 操作的是你日常登录过的那个浏览器实例它可能访问你的邮箱、后台管理系统甚至点下删除按钮。我在项目里做了三个安全基线永远用独立的--user-data-dir创建全新浏览器配置与个人浏览器完全隔离高危操作确认机制在 Agent 循环里加了一层拦截逻辑当检测到工具调用目标是删除、发布、支付之类的动作时暂停执行并向操作者发送确认请求推荐生产环境用 Docker 跑浏览器服务把整个浏览器隔离在容器里。这层确认机制写起来不复杂就是在call_tool前后包一层代理按工具名和参数做白名单匹配。6.6 其他零散但同样烦人的坑npx首次启动要下载依赖网络慢会超时。解决改用全局安装直接用playwright/mcp --headless启动页面总是先加载首屏数据是异步懒加载的。Agent 快照太早会看不到目标内容。处理在 prompt 里要求如果目标未出现先调用 browser_wait 等待 1 到 2 秒再重新快照浏览器进程不释放跑完任务后残留 Chromium 进程。解决任务结束时显式调用browser_close并在主进程 catch 里兜底杀进程本地模型推理速度慢长任务容易半路超时。解决把大任务拆成多个短会话每个会话只负责一个子目标下一个会话继承上一个会话的文字摘要Ollama 服务偶发不响应。解决办法是把ollama serve用进程守护工具托管崩溃后自动重启。7. 生产化之前我还会做这几件事7.1 操作日志与审计AI 代理的不可预测性决定了它必须有完整的审计链路。我把每次工具调用都记录成结构化 JSON包含时间戳、工具名、参数、返回值的摘要截图按任务编号归档。出问题时按日志回放一遍就能定位是哪一步决策导致的错误。实际排查问题的时候这些日志比模型本身的思考过程有用得多。7.2 会话记忆与任务复用Agent 默认没有记忆同一个站点的采集任务每次都要重新理解页面结构。我在本地加了一个轻量 SQLite 存储把历史任务的关键信息记录下来站点 URL、页面结构摘要、使用的工具序列。下次遇到相似任务先查记忆匹配到就将历史决策路径作为参考注入 prompt。实测下来本来要七八步摸索的任务现在三四步就能完成。7.3 模型路由与人工确认最后一件重要的事是把执行权和决策权分层。简单线性任务打开页面、读文本、提取字段用本地 7B 模型足够复杂任务多步推理、跨站点数据对比、需要综合判断的场景我选择把 Agent 的推理底座切到云端更强模型MCP 工具层完全不用动。切换模型对现有代码的侵入极小因为 MCP 已经把所有浏览器能力标准化了模型只是决策器被抽象在 Agent 循环内部。接 Ollama 是本地底座接 Azure OpenAI 或 Anthropic Claude 就换一个推理引擎实现其他逻辑不变。再往后还想补一套人工确认网关对发布文章发送邮件删除数据这类不可逆动作在 Agent 执行前必须经过人工审批。技术上可以在 MCP Client 层包一层代理按规则拦截也可以直接对接工单系统。这是让 AI 代理真正进入业务流之前必须补上的最后一块拼图。我刚跑通这套系统的时候挺兴奋但冷静下来看Browser Copilot 的价值不在于AI 能控制浏览器这个噱头而在于它把浏览器的接口标准化成了一件普通工具。以前写爬虫脚本、做自动化测试、填表单每个场景都要从零开始现在只要描述清楚目标Agent 就能自己想办法完成——这带来的效率提升不是一点半点。按照这套方案你也可以自己动手搭一个 Browser Copilot 出来。建议从小任务开始逐步增加复杂度先把踩坑实录里那些问题提前做好防护会顺利很多。
返回列表