免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

AI Agent浏览器交互实战:从CDP到MCP的选型与实现

AI Agent浏览器交互实战:从CDP到MCP的选型与实现 如果你正在做 AI Agent不管你是用 LangGraph 自己搭、在 n8n 里拖流程还是拿现成框架做二次开发大概率会在某个环节撞上同一个问题浏览器交互能力。比如你让 Agent 去查资料它可以很快生成一堆检索词但真要它打开某个网页、把筛选条件选好、一页一页翻完再把结论整理成人话很多人就卡住了。有人觉得这不就是爬虫吗其实不是。爬虫只需要把页面内容抓下来Agent 需要的是在浏览器里“替用户办事”判断、点击、输入、等待、再判断中间每一步都可能出错出错之后还得能自我恢复。这篇文章我会从项目实战的角度把 AI Agent 需要的浏览器交互能力拆开来讲。包括它到底解决什么问题、底层靠哪些技术支撑、选型时怎么挑、最容易翻车的细节在哪、以及怎么从零搭一个最小可用模块。如果你正在学 Agent 开发或者准备把网页自动化能力接进自己的 Agent 产品这篇文章应该能帮你少走一段弯路。1. 为什么浏览器是AI Agent绕不开的“手脚和眼睛”1.1 五个高频场景从“帮我看个网页”到“替我把事办了”我最早意识到浏览器交互的重要性是在做一个“竞品价格监控”Agent 的时候。理论上这种需求可以靠官方 API 解决但实际做下来发现大部分业务系统只提供网页端压根没有开放 API。就算有 API接口权限申请流程也长到让人失去耐心。最后只能让 Agent 模拟人的操作去网页里拿数据。类似的情况并不少见我梳理了下自己接触过的项目大概有这么五类高频场景信息检索与汇总Agent 根据问题打开搜索结果页进入具体文章提取关键段落多页面信息合并成报告。表单填写与业务办理订会议室、提交报销、报名活动、修改配置这些操作在网页上就是“填几个输入框 点几个按钮”。登录后数据采集很多数据必须登录才能看Agent 需要带着登录态去访问页面再按条件筛选、翻页、导出。跨系统流程编排一个业务流程可能要经过 OA、CRM、财务三个系统系统之间没有 API 打通只能靠浏览器操作搬数据。屏幕级理解任务有些页面元素不是传统 DOM 结构比如在线设计工具、Canvas 报表Agent 需要像人一样“看屏幕”再决定点哪里。这五类场景有一个共同点网页是用户业务的主要入口但并没有为程序化访问准备友好接口。AI Agent 要想真正“替用户做事”浏览器交互能力就成了刚需。它不是锦上添花而是 Agent 能不能落地的分水岭。1.2 浏览器交互的三个层次读、想、做在拆方案之前我习惯把浏览器交互能力分成三个层次也方便和团队沟通时对齐概念第一层是“读”。Agent 需要把网页内容转成自己能理解的信息可能是一段清洗后的文本也可能是一棵简化过的 DOM 树还可能是一张截图。读的方式会直接影响后续所有决策质量。第二层是“想”。模型根据目标和当前读到的页面状态决定下一步做什么。这一步牵扯到大模型推理能力是点击某个按钮还是先滚动到底部或者是发现登录过期需要中止。想得对不对取决于喂给模型的信息够不够、噪不噪音。第三层是“做”。把决策变成真实浏览器操作点击、输入、滚动、跳转以及操作后的等待与校验。做得好不好取决于操作层能不能处理各种页面异常。很多刚接触 Agent 的开发者会有一个误区觉得浏览器交互能力就是“用 Playwright 写脚本”。脚本是确定性的写死了就按固定路径走Agent 是动态的它需要根据页面反馈实时调整下一步。所以你要的不是一个自动化脚本而是一个能感知页面状态、能执行操作、能反馈结果给模型的能力环。这个能力环才是真正常说的“AI Agent 浏览器交互能力”。2. 给Agent配浏览器从CDP到MCP的选型地图2.1 CDP协议浏览器被远程操控的入口Chrome DevTools Protocol也就是 CDP是浏览器暴露出来的一套远程控制协议。你可以通过它查看 DOM 节点、监听网络请求、模拟点击、截图、设置 Cookie几乎所有浏览器自动化工具的底层都是它在干活。我在最初研究这套东西时想过直接拿 CDP 给 Agent 写交互层。后来发现不是不行而是太痛。CDP 是面向调试场景设计的消息格式非常底层一个简单的“点击按钮”操作需要先通过 Runtime.evaluate 执行一段 JS 代码自己定位元素处理 iframe 边界处理元素不可见还得自己管理 WebSocket 连接。这套逻辑写下来没个两三千行根本稳不住。所以我的结论是CDP 更适合作为底层机制去理解不适合在业务代码里直接操控。你可以把它当成发动机但别自己手搓整车。在选型时真正应该对比的是“如何把 CDP 能力包装成 Agent 可用的工具”。2.2 WebDriver与Playwright把浏览器封装成可调用APIWebDriver 是一种较老也更通用的浏览器自动化标准Selenium 就是典型实现。它适合传统回归测试但你要让 Agent 高密度、长链路地操作浏览器Selenium 的稳定性和启动速度都不够理想。Playwright 是我目前在项目里的主力方案。它最大的价值是做了很多“人觉得很自然、程序却很麻烦”的兜底自动等待元素出现、自动处理网络空闲、自带多种选择器机制还能跑 Chromium、Firefox、WebKit 多内核。对 Agent 来说这意味着操作成功率更高心智负担更小。但 Playwright 本质上还是一个自动化库它不负责“决策”。你让它点就能点让它填就能填可它不知道什么时候该点、点完之后要检查什么。所以 Playwright 在技术栈里属于“执行层”Agent 的决策层还需要另外设计。2.3 MCP浏览器服务让Agent生态与浏览器解耦MCP 最近讨论很多它在 Agent 开发里的核心价值是把能力变成“工具”暴露给模型。浏览器也不例外。你可以把浏览器操作封装成一组 MCP 工具browser_navigate、browser_click、browser_input、browser_snapshot模型只要在这组工具里选择合适的调用即可。这套方案的好处是解耦。Agent 的决策代码不关心浏览器到底装在哪里也没必要理解 Playwright 的 API它只需要按 MCP 的标准格式发起调用、接收结果。浏览器跑在本地也好跑在远程容器里也好对 Agent 来说都一样。如果你的 Agent 框架支持 MCP 工具列表我会优先建议把浏览器能力包装成 MCP Server。这比自己在 Agent 线程里硬编码 Playwright 调用要干净得多也方便以后把浏览器交互能力复用到别的 Agent 项目里。2.4 现有方案对比不是说哪个最好而是看哪个最合适我在不同项目里试过不同组合下面这张表可以先给你一个整体视角方案能力视角适合阶段主要痛点原生 CDP底层协议研究浏览器机制、定制特殊能力开发量大维护成本高Playwright / Puppeteer自动化执行需要稳定执行页面操作的业务脚本不负责决策需要自己写循环MCP 浏览器 Server工具封装接入 LangGraph 等 Agent 框架需要额外维护服务进程多模态模型直接操作屏幕视觉理解页面结构复杂、无 DOM 可依赖成本高坐标点按容错低做选型时不要只盯着“哪个更火”而是先回答一个问题你的 Agent 是偏“决策密集”还是偏“操作密集”如果主要是让模型反复判断下一步干嘛MCP 工具化更合适如果流程本身相对固定只是中间加上模型抽取那 Playwright 脚本套一层轻量封装就够了。3. 容易被忽略的五个交互细节等到什么时候才算加载完3.1 DOM与视觉什么时候用结构化文本什么时候用截图很多页面问题本质是“Agent 看到的东西”和“真实页面状态”不一致。DOM 文档树非常精准能拿到元素的属性、层级、文本但现代前端框架渲染出来的页面DOM 树可能非常庞大一个后台系统页面动辄几千个节点。把这些全塞给大模型token 不够用关键信息也被淹没。截图则更接近人眼看到的画面多模态模型可以理解布局但截图没有结构化语义模型给出的点击坐标经常有偏移。我的做法是双通道默认场景下生成“简化 DOM 视图”只保留可交互元素、可见文本和表单控件只有当 Agent 明确判断“按元素找不到目标”时再截一张图交给多模态模型。这样既控制了 token 消耗也保留了兜底能力。3.2 登录态、会话与 Cookie 的共享边界Agent 一旦需要操作登录后的页面登录态管理就会成为最大的隐性坑。你用 Playwright 启动一个浏览器上下文第一次登录成功Cookie 存在内存里进程一重启登录态没了Agent 又得重新登录。我通常在项目里用一个独立的 browser context 目录把浏览器数据持久化到磁盘比如--user-data-dir参数这样重启后可以复用登录态。但这又带来一个新问题多个任务共用一个登录态容易越权。比如 Agent A 在某个业务系统里有普通员工权限Agent B 在同一台机器上如果也复用了相同浏览器上下文就可能访问到不该看的数据。更稳妥的做法是为每一个“任务主体”创建相互隔离的浏览器上下文。登录态隔离本质上就是权限隔离。不要图省事全项目共用一个浏览器实例。3.3 等待与完成判断Agent最容易自欺欺人的地方我见过很多 Agent 脚本点击“提交”按钮后直接断言成功结果只是按钮置灰后端接口返回异常页面上弹了个红色错误提示。Agent 却以为任务完成了因为“点击”这个动作本身执行成功了。浏览器交互里最难的就是判断“什么时候算真正完成”。加载完成不等于页面可用按钮出现不等于点击生效点击生效不等于业务成功。我总结出一个判断公式实际项目中我会按这个逻辑去写校验任务完成 页面目标状态出现 网络请求静默 关键断言通过。目标状态可能是“成功提示文本出现”网络静默是确认没有未完成的异步请求关键断言则是根据业务场景写死的验收条件。三者同时满足才算完成。3.4 页面里的“暗礁”iframe、shadow DOM、Canvas普通页面上能直接用选择器定位的元素一旦被 iframe 包裹主文档里的选择器就找不到。shadow DOM 则会把内部节点隐藏起来默认的 DOM 查询也拿不到。Canvas 更彻底整张表是一个画布根本不存在“按钮节点”。遇到 iframe要先切换到对应 frame 再定位遇到 shadow DOM需要用shadowRoot穿透遇到 Canvas就只能走坐标点击 图像识别。这些不是冷门知识而是 Agent 操作企业系统时的高频问题。很多老系统喜欢用 iframe 嵌页面数据报表系统又普遍用 Canvas 渲染图表你不提前处理Agent 就只能在原地转圈。3.5 反自动化机制能识别“这步不该自动做”才叫成熟现在很多站点都有风控体系会识别自动化操作特征。Agent 操作频率、鼠标轨迹、请求头指纹都可能触发验证码或设备风控。这里要特别把边界说清楚正规项目里我们不应该去写绕过验证码、对抗风控的逻辑。更好的思路是让 Agent 具备“自知之明”。当它发现页面出现验证码、风控提示、或不明异常时能主动停下来把问题抛给人工处理或者调用合规的官方认证接口。能识别“这步不该我自己做”比单方面追求全自动化重要得多。我在实际需求评审时也会跟产品经理说清楚不是所有页面都适合 Agent 操作风控边界必须提前约定。4. 给Agent设计浏览器交互模块三层结构状态机4.1 感知层先想清楚给模型看什么感知层的目标是把浏览器里的原始页面压缩成一份“对决策有用的预案”。我见过有团队直接把整棵 DOM 用 Base64 塞进 Prompt结果模型上下文爆炸决策质量还很差。我现在比较推荐的做法是感知层负责输出“页面摘要”主要包括四类信息当前页面标题和 URL主要可见文本去掉脚本、样式、无语义标签可交互元素清单包括按钮文字、输入框占位符、链接文本页面内出现的提示性文本如错误提示、空状态、成功状态。这些信息经过过滤后模型不仅能知道页面在讲什么还能马上判断接下来可以操作什么。感知层做得越干净模型决策的准确率就越高。4.2 决策层操作指令要受控不是让模型自由发挥决策层负责输出下一步操作。如果不做限制模型可能会说太多废话或者输出一个操作层根本不支持的指令。我会把操作类型收敛成一个白名单模型只能从这个白名单里选常见的有navigate(url)跳转页面click(selector或文本)点击input(selector, text)输入scroll(direction)滚动select(selector, label)下拉选择wait(condition)等待某个条件screenshot()截图。决策输出格式我用 JSON因为 JSON 容易解析也容易校验。一个动作指令大致长这样{ action: click, target: 提交, reason: 表单已填写完成可在页面底部找到提交按钮 }决策层不直接执行浏览器 API它只产出结构化指令。这样操作层可以统一做参数校验、重试、日志记录也方便未来接入不同的浏览器实现。4.3 操作层怎么保证“点下去”真的有效操作层要把决策层的指令映射到 Playwright 或类似工具的真实调用。这一步的关键是对指令做“防御式执行”。举个例子模型说“点击提交”操作层不能傻乎乎地只按文本定位。一般我会这么处理先用文本匹配找可见按钮如果找不到改用包含文本的父级可点击元素找到后用wait_for等待元素可交互而不是立即点击点击后等待页面状态变化把点击结果、页面快照、异常信息原样返回给决策层。操作层还有一个重要职责是“幂等”。同一个点击指令如果模型重复发出操作层不能让用户收到两次报销单。所以执行前要判断目标状态是否已经满足已经满足就直接返回“该步骤已完成”。4.4 Agent主流程状态机把决策和执行串起来把感知、决策、操作串起来我会用一个状态机来管理 Agent 主流程避免模型在一个死循环里转不出来。状态一般包括初始化、决策中、等待页面、执行动作、校验结果、任务完成、任务终止。状态转移的核心原则只有一条每个动作执行完都必须回到“校验结果”状态。校验通过进入下一步决策校验失败把错误信息拼进上下文让模型重新决策。只有当重试次数达到上限或安全策略要求停止时才进入终止状态。这个状态机看起来简单但真的能避免很多问题。没有状态机约束的 Agent经常会出现“连续点击同一个按钮五次”“页面跳转后还在按旧页面的指令操作”这类低级错误。5. 从零搭一个最小可用的Agent浏览器交互模块5.1 选型与设计为什么我这样搭我不会一上来就引入重量级框架。最小可用版本我推荐这样组合Playwright 负责浏览器操作MCP Server 负责把操作暴露成工具LangGraph 或 n8n 负责 Agent 编排。如果你不想用 MCP也可以直接把 Playwright 操作封装成 Python 函数给 Agent 调用但那样后续扩工具会比较乱。为什么底层选 Playwright 而不是 Selenium因为它的自动等待机制能大幅减少 Agent 的无效重试。为什么加一层 MCP因为我想让决策层直接按工具名和参数槽调用不关心浏览器怎么实现。这个设计不是最炫的但胜在层与层之间边界清晰出问题方便排查。5.2 核心代码骨架先把循环转起来下面是一个简化版的核心骨架只保留了浏览器交互的最小闭环。实际项目里你还需要加超时控制、错误分类、操作审计等逻辑但骨架能帮你把整体结构立住。# browser_agent/tools.py from playwright.async_api import async_playwright class BrowserController: def __init__(self): self.browser None self.page None async def start(self): p await async_playwright().start() self.browser await p.chromium.launch(headlessTrue) self.page await self.browser.new_page() async def navigate(self, url: str) - str: await self.page.goto(url, wait_untildomcontentloaded) return await self.page.title() async def click(self, text: str) - str: locator self.page.get_by_role(button, nametext) await locator.wait_for(statevisible, timeout5000) await locator.click() return clicked接着把这些操作封装成 MCP 工具。MCP 的作用是让 Agent 模型知道这批工具的存在以及参数格式我通常会这样暴露# mcp_server.py from mcp import Server, tool tool() async def browser_navigate(url: str) - str: 打开指定URL并返回页面标题 return await controller.navigate(url) tool() async def browser_click(button_text: str) - str: 点击页面中指定文本的按钮 return await controller.click(button_text)这里要注意工具的描述字段不要写一堆空话而要写清楚“什么时候该用、入参是什么、返回什么”。模型是靠描述来决定调用哪个工具的描述写得越准确模型越不容易乱选。5.3 接入LangGraph和n8n的方式在 LangGraph 里Agent 节点可以通过绑定工具列表实现决策。业务流程大致是模型收到用户目标调用浏览器工具观察页面拿到观察结果后继续决策直到任务完成。如果你用 n8n 这类低代码平台思路也一样浏览器交互模块对外提供 HTTP 接口n8n 通过 HTTP Request 节点在流程里调用。这种方式适合不想写太多代码的团队但调试起来不如代码直观。我在实际项目中如果流程超过十个节点还是会回到 LangGraph 或纯 Python 来实现因为流程一旦复杂低代码画布会变得很难维护。无论用哪种编排方式有一个心得是通用的不要把整棵 DOM 塞给模型只传当前页面摘要和可操作元素清单。这样省 token决策也更稳定。6. 实际项目里的翻车现场和排查链路6.1 先别急着改代码按五步定位问题浏览器交互类问题有个特点表面上是“代码报错”本质上往往是“页面状态和预期不一致”。我排查问题时从不直接改选择器而是按固定链路走一遍。第一步隔离环境。同一段代码可能在自己的测试机正常、在服务器上报错先确认两边的浏览器版本、网络环境、用户数据目录是否一致。第二步降级定位。把复杂选择器换成人眼可读的文本选择器重新执行一次。如果文本能定位到说明问题出在元素属性动态变化如果文本也定位不到大概率是页面根本没加载到那个状态。第三步看网络和控制台日志。浏览器控制台的错误信息往往能直接指出是资源加载失败、接口异常还是 JS 执行报错。第四步截图回放。让 Agent 每次操作前强制截一张图把操作前、操作后两个画面放在一起对比很快能看出哪个步骤出了问题。第五步还原当时的模型上下文。因为 Agent 是决策驱动的固化代码不够还要看模型当时接收到了什么页面摘要。有时候页面摘要漏掉了关键元素模型自然就会走错误分支。6.2 三类高频错误的真实日志与修复思路第一类Timeout waiting for selector。这个问题最常见的原因是页面有延迟加载内容比如滚动到指定位置才加载按钮。我一开始用的是固定等待sleep(3)后来发现网络慢的机器上根本不够。正确做法是定位到目标元素后用wait_for(statevisible)并且把等待条件绑定到“业务目标是否出现”上。第二类Element is not attached to the DOM。在单页应用里特别常见。你第一次定位到元素时它还在但点击前页面重新渲染旧的元素引用已经失效。修复思路是不要缓存元素引用每次操作前都基于最新页面状态重新解析目标。这也是为什么我会让 Agent 每次决策前都重新获取页面摘要而不是复用上一次的。第三类比较隐蔽不报错但业务结果是错的。比如 Agent 提交了表单页面没有任何异常提示但提交的内容并不完整。我的排查方法是给 Agent 增加“完成后断言”表单提交过后再读取列表页第一行数据确认关键字段和提交内容一致。这个断言往往能拦住一大半“看起来成功实际失败”的情况。7. 边界感Agent操作浏览器的权限与安全底线7.1 授权与最小权限原则Agent 替用户操作浏览器时携带的是用户的凭证和身份。权限天然比普通爬虫大得多所以更要注意最小权限。我的原则是Agent 只能访问当前任务需要的系统页面不能拿到所有登录态后四处访问每个自动化任务都要有独立的浏览器上下文任务结束立刻销毁涉及支付、删除、批量导出等高风险操作必须设置人工确认点不允许 Agent 自动完成。7.2 审计、终止与可撤销浏览器里所有操作都应该有审计记录包括动作名称、目标页面、操作用户、操作时间、执行结果。这不是为了事后追责而是当 Agent 操作出问题时你能快速定位是哪一步导致的结果异常。此外要给 Agent 加“紧急停止”机制一个开关能立刻终止所有浏览器会话终止后任何人都不能通过 Agent 界面继续恢复执行。这个开关在开发阶段就能帮你省下不少麻烦因为模型有时候会连续执行出一连串你没想到的操作。7.3 能力边界要提前写在设计里成熟的 Agent 不是什么都做而是知道哪些事不该做。项目启动时就要把禁止项列清楚不做验证码对抗、不做撞库式访问、不批量抓取非授权隐私数据、不执行任何可能破坏线上数据的操作。当 Agent 遇到这些场景时正确响应是提示“需要人工介入”并提供当前页面的链接和截图。我在实际项目里的体会是浏览器交互能力不是自动化能力越强越好而是可控制、可审计、可中止的能力才算成熟。让 Agent 在明确边界内稳定地做完一件事比让它像人一样“什么都能点”要重要得多。把这一点想清楚你的 Agent 距离真正上线其实就不远了。
返回列表