1. 项目背景与设计目标
- 背景:需要对大批量目标网址进行自动化操作(如提取邮箱/填写表单)。由于各网站 DOM 结构与交互逻辑差异极大,传统基于固定 CSS/XPath 的脚本无法统一兼容,需引入 AI Agent 动态决策。
- 核心目标:
- 零 API 成本与高隐私:利用本地 RTX 3060 (12GB) 运行轻量模型,实现完全本地化的推理与执行。
- 极致的 Token 控制:全量 HTML 会导致 Context 爆炸和 AI 幻觉,必须在输入给 AI 前对 DOM 进行极致降噪与切片。
- 高并发与高可用:支持多任务并行抓取,具备完善的异常降级与防死循环机制。
2. 核心架构与选型
🚀 主选方案:Playwright-MCP 官方服务 + 指纹浏览器 CDP
- 实现方式:
打开指纹浏览器 $\rightarrow$ 获取 CDP 端口 $\rightarrow$ 拉起npx @playwright/mcp@latest服务 $\rightarrow$ 代码(C# / SK)创建 MCP Client $\rightarrow$ 将 MCP Tools 与浏览器指挥权彻底托管给本地小模型。 - 优点:开箱即用,开发成本极低。网页打标、DOM 降噪、物理点击、Shadow DOM 与 Iframe 均由微软官方底层闭环处理,无需手写页面清洗逻辑。
- 注意事项:多线程并发时,需在代码中为每个指纹环境动态分配独立 MCP 端口(如
8931,8932),并控制好进程生命周期。
🥈 备选方案 A:原生 Accessibility Tree + 双向 ID 锚定 (Fallback 1)
- 实现方式:
通过 Playwright 的page.Accessibility.SnapshotAsync()抓取极简 A11y 树,作为上下文传给 AI。 - 解决 A11y 树“太简化导致无法定位 DOM”的技巧:
- 操作前注入轻量 JS,为所有可交互 DOM 打上
data-ai-id="X"。 - 顺手写入标准 ARIA 属性:
el.setAttribute('aria-description', 'ai-id-X')(防止被 A11y 树过滤)。 - A11y 树会天然携带此 Description,AI 决策后返回
ai-id-X,代码通过page.Locator("[aria-description='ai-id-X']")执行物理点击。
- 操作前注入轻量 JS,为所有可交互 DOM 打上
- 适用场景:当 Playwright-MCP 服务因特殊原因挂起,或需要更轻量级、无外部进程依赖的单机执行时。
🥉 备选方案 B:自定义 DOM 极简剪切脚本 (Fallback 2)
- 实现方式:
在页面中注入自研 JS 脚本:给<a>,<button>,[onclick]绑定唯一data-ai-id,强行剥离script,style,svg,path以及无用空标签,拼接成专供 AI 阅读的极简 JSON 树。AI 决策时直接带入data-ai-id。 - 优点:高度定制。可以把业务逻辑(如“自动保留包含
@符号的文本”)写死在前端清洗脚本里,极致省 Token。 - 缺点:调试成本极高,真实页面的边缘 Case(如隐藏元素、多层嵌套)需要大量时间适配。
🚷 弃用方案:OmniParser V2 纯视觉 Agent
- 实现方式:Playwright 截图 $\rightarrow$ 视觉模型画框标注坐标 $\rightarrow$ 大模型根据坐标指示点击。
- 弃用原因:
- 显存爆掉:视觉模型 + 检测模型会吃满 3060 显存,无法做多线程并发。
- 长图盲区:无法一次性截取 Footer(底部)区域的联系方式,AI 容易在“向下滚动”指令中陷入死循环。
- 延迟极高:单步截图与图像推理需耗时数秒,效率远低于文本流。
3. 关键工程细节与避坑指南
3.1 MCP 并发与状态隔离 (防串线与崩溃)
- 进程级隔离:由于
@playwright/mcp默认是单实例状态,高并发时必须在 C# 端为每个抓取任务启动一个独立的 MCP Server 进程(通过--port分配不同的 SSE 端口,或使用 stdio 管道)。 - 浏览器上下文隔离:通过 CDP 连接指纹浏览器时,必须在 C# 端通过 CDP 协议(
Target.createBrowserContext)为每个任务创建独立的 Browser Context(相当于无痕模式的新窗口),并将该 Context 的 ID 传给 MCP,严防多个 AI Agent 操作同一个 Tab 导致“互相踩踏”。
3.2 Snapshot 后处理降噪 (防 Context 爆炸)
- 痛点:Playwright-MCP 的
browser_snapshot默认返回完整的 AX Tree。对于包含巨大导航菜单或 Footer 的官网,单次 Snapshot 可能高达 10k+ Tokens,直接撑爆小模型的 Context。 - 对策:在 C# 端的 MCP Client 拦截层,对返回的 Snapshot 文本进行正则截断或裁剪。例如:移除所有
role="navigation"且子节点超过 20 个的庞大菜单树,只保留核心内容区域的节点,强制将单次 Snapshot 控制在 3000 Tokens 以内。
3.3 Agent 防死循环硬性约束 (防算力空转)
- 痛点:小模型在复杂 SPA 页面极易陷入“点击 -> 弹窗 -> 关闭 -> 再次点击”的死循环。
- 对策:
- 硬限制:在 System Prompt 和 C# 循环代码中双重限制
MaxSteps = 6。 - URL 去重:在 C# 端维护一个
HashSet<string> visitedUrls。如果 AI 调用的 Tool 导致页面跳转到了已访问过的 URL,C# 端直接拦截并返回 Tool 错误信息:“该页面已访问过,请寻找其他链接”,强制 AI 改变策略。
- 硬限制:在 System Prompt 和 C# 循环代码中双重限制
4. 落地实施路线 (漏斗式执行流)
- 环境隔离与基础直达:
- 主程序调用指纹浏览器 API 唤醒环境(自带代理 IP 与伪装指纹)。
- 主程序通过 CDP 驱动浏览器直达目标首页(不浪费 AI 算力做基础导航)。
- AI 攻坚 (主路线):
- 启动
playwright-mcp,由本地小模型自动执行多轮工具调用,多跳深入 Contact Us 或社交媒体页面寻找邮箱。
- 启动
- 降级兜底 (备选方案):
- 若 MCP 在特殊异常页面挂起或返回无效 Snapshot,自动降级为“自定义 DOM 剪切脚本 (备选方案 B)”,吐出 JSON 树让 AI 做最后攻坚。
- 结果提取与清理:
- 提取到邮箱后,通过 CDP 关闭当前 Browser Context,释放显存与内存,进入下一个任务。
5. 硬件与部署建议
- 硬件配置:
- GPU:NVIDIA RTX 3060 12GB (核心推理节点)。
- CPU/内存:建议 8核16线程以上,32GB+ 系统内存 (Playwright 多开浏览器实例极其吃内存)。
- 软件栈:
- 推理引擎:Ollama (
本地部署 Phi-4-mini 与 Qwen2.5-Coder-7Bmac air 上使用 qwen3.5:9b-mlx 在 3060 win 机器上使用 qwen3.5:9b)。- 浏览器控制:Node.js 环境 +
@playwright/mcp。 - 业务代码:.NET 8 (使用
Microsoft.Extensions.AI抽象层接入 MCP 与 LLM)。 - 指纹浏览器:支持 CDP 协议导出的商业/开源指纹浏览器 (如 AdsPower, VMLogin 等)。
- 浏览器控制:Node.js 环境 +
- 实操结果与方案演进复盘
在实际落地验证中,各方案的表现与预期存在一定差异,最终的实操结果与踩坑总结如下:
主选方案(Playwright-MCP)—— 理想很丰满,落地受限于模型指令遵循能力:
表现:这是最初最看好的方案。但在实际测试中,不同大模型对 MCP(Model Context Protocol)工具调用的稳定度表现参差不齐。例如云端大模型(如阿里云百炼平台 Qwen-Plus)及部分本地模型(如 Windows 端运行的 Qwen、Phi 系列)会出现“口头承诺已操作但实际上并未真正调用 MCP 工具”的幻觉现象;而在 Mac 端运行的 Qwen 则表现不稳定,时而正常工作、时而呆滞卡死。
结论:现阶段受限于小模型/部分云端模型对复杂的多轮工具定义(Tool Calling)理解不够稳定,纯 MCP 方案的自动化成功率达不到全自动批量生产的要求。
备选方案 A(Accessibility Tree)—— 定位链路过长,多步还原困难:
表现:通过 A11y 树虽然能够极大地精简输入上下文,但由于其过滤掉了大量的 DOM 细节,AI 依据极简树反推并重建准确的 CSS 或 XPath 定位路径极其困难,容错率极低。
备选方案 B(自定义 DOM 极简剪切 + 确定性路由)—— 最终胜出的最优解:
表现:实践证明,“简化数据结构 + 减少 AI 自由度” 是当前最稳妥的架构。
去噪与直达:通过自定义脚本剥离无用标签,仅向 AI 暴露高价值的交互节点。
职责剥离:不再让 AI 直接输出复杂的 XPath/CSS 路径(极易返回错误语法导致崩溃),而是让 AI 仅返回下一步的目标跳转链接(URL)或动作指令,由底层 C# 代码负责精确执行。
状态与历史绑定:在提示词中强制注入已走过的历史路径(Visited History),结合步数硬上限与 URL 去重机制,彻底根治了小模型在复杂 SPA 页面中的死循环顽疾。