让Agent会敲命令行不难,让Agent会点网页才是门槛。
2026年还在用 requests.get() 让Agent "操作网页"的团队,基本上都会撞到同一堵墙:目标站是SPA,HTML里什么都没有;登录要人扫一次码;发布按钮点不动;反爬检测一来所有请求都返回同一页。
浏览器自动化(Browser Automation)是把Agent从"读网页"升级到"操作网页"的关键一环。本文按「选型 → 部署 → 登录态 → 交互 → 反检测真实边界」的顺序,把所有生产环境踩过的坑一次讲清。
| 方式 | 代表 | 适合场景 | 主要问题 |
|---|---|---|---|
| MCP浏览器服务 | Playwright MCP | Agent原生调用,工具即点即用 | 进程守护、端口冲突 |
| 脚本化驱动 | Playwright / Puppeteer | 流程固定、批量执行 | Agent不会自己写脚本 |
| 反检测驱动 | patchright / camoufox | 有风控的目标站 | 覆盖面有限,不保证绕过 |
| 复用真实浏览器 | CDP连接已开Chrome | 已有的登录态、复杂指纹 | 需要用户先开浏览器 |
| 视觉操作 | 截屏+坐标点击 | 无DOM可用的极端场景 | 慢、脆、贵 |
结论先说:常用组合是「Playwright MCP 负责日常 + patchright 脚本负责批量」。 前者让Agent自己决定点哪里,后者把重复流程固化下来,别让模型每一步都重新决策(慢且贵,还不稳定)。
把浏览器封装成 MCP 工具(browser_navigate / browser_click / browser_snapshot / browser_evaluate),Agent就获得了浏览器能力,而不用每做一个任务就写一套脚本。工具边界天然就是权限边界:你只给它注册浏览能力,它就干不了别的事。
npm 全局安装的 MCP 服务,bin 目录里是 *.sh 脚本(bash 包装器),不是 Win32 exe。用 stdio 模式拉起时,父进程拿不到可执行的入口,表现为"连上了但没有工具"或直接超时退出。
修法:改用 HTTP/SSE 模式,用端口而非管道通信。
# 以独立进程启动浏览器MCP,监听端口
npm i -g @playwright/mcp
playwright-mcp --port 3102
# 再把它挂到Agent上(URL连接,不走stdio)
hermes mcp add browserplaywright --url http://localhost:3102/mcp
用 nohup ... & 在终端里起的后台进程,终端会话一结束就死。表现是"昨天能用今天报连接拒绝"。
修法:用系统级独立进程启动(Windows 上用 PowerShell 的 Start-Process 脱离 shell 会话),并且只按端口杀进程,绝不 blanket kill node —— 那会顺手杀掉你机器上所有 Node 服务(n8n、面板、自己的服务)。
# 只清理占用该端口的进程,再重启
netstat -ano | grep ":3102" | awk '{print $5}' | sort -u
hermes mcp add 之类的命令会弹交互式提问,在 MSYS 环境下 stdin 常常不通,表现为命令卡死。修法:用 flag 一次性传参,或用 PowerShell 包一层喂输入。 能非交互就别交互。
浏览器自动化真正的瓶颈不是点击,是登录。一个站点如果没有可复用的登录态,每天都要人扫一次码,那就不是自动化。
| 层级 | 手段 | 成功率 | 人工介入 |
|---|---|---|---|
| L1 | 复用浏览器 profile / storage_state | 高(长尾最省) | 无 |
| L2 | 凭据重登(账号密码 + 验证码) | 中 | 偶发要验证码 |
| L3 | 人工扫码 / 手工导出Cookie | 兜底 | 必须 |
L1 是主路径,L3 是兜底,中间不要卡住。 生产环境一定要给登录态配三个东西:常驻(别每次任务都新建上下文)、超时(Cookie 有效期到期前主动刷新)、哨兵(隔一段时间用一个只读页面验证登录态还活着)。
2024年底之后,Chrome 引入了 App-Bound Encryption(v20 Cookie 格式),任何纯本地脚本都解不开:browser-cookie3、rookiepy 这类库会直接失败或返回空。
这不是版本问题,是设计上的墙。可行的路线只有两条:
经验:能 CDP 就别导 Cookie,能导 Cookie 就别去解数据库。
只验证"能不能打开首页"是自欺欺人——登录过期往往首页还能看,一到写操作就跳登录页。 要做两个探针:
现代前端框架(自定义元素、Web Component)经常让常规选择器无从下手。
SPA 页面元素是异步渲染的,page.click() 立刻执行会报 selector not found。用 waitForSelector 或等待网络安静,而不是 sleep(3) 撞运气。固定 sleep 写小了失败、写大了拖时长,两头不讨好。
实测过一种情况:发布按钮是自定义标签(<xxx-publish-btn>),查 innerHTML 是空的、shadowRoot 也是 null,常规选择器全都拿不到它。
可行的方法是按坐标点击:先定位到它的边界框,再点框中心。
# 伪代码:找到承载按钮的容器,取其边界框,点中心
handle = page.locator('publish-widget, [class*="btn" i]').filter(has_text='发布')
box = handle.first.bounding_box()
if box:
page.mouse.click(box['x'] + box['width'] / 2,
box['y'] + box['height'] / 2)
比坐标点击更稳的是"元素+文本"双重过滤:locator('[class*="btn" i]').filter(has_text='发布') —— 类名可能带随机 hash,但按钮上的文字是中性的、稳定的锚点。文案是最抗改版的选择器。
正文编辑器通常是 contenteditable,fill() 不生效。要用 execCommand('insertText') 或在元素上派发 input 事件;纯输入框则要用原生的 value setter 再手动触发 input 事件,否则前端框架的状态根本不更新(页面看着有字,提交却是空的)。
别去做拖拽模拟,直接给 <input type=file> 喂本地路径,一行搞定,也最稳。
点完发布不等于发布成功。判据是"目标页面上出现了这条内容":URL 变成已发布状态、列表页出现新条目、拿到内容 ID。只看"按钮点到了"就等于把Agent的成功判断交给了UI。
以下都是实测结论,别被工具宣传骗了。
1. 平台级风控下,headless 会返回"假成功"。
有些工具在 headless 模式下会返回 {"success": true} 而内容根本没发出去。所有涉及发布的自动化,第一次上线时都要用肉眼核对一次真实产物,之后把"产物时间戳"做成断言。
2. Cloudflare JS Challenge 会让所有工具同时失效。
表现很典型:任何路径(包括不存在的路径)都返回同一段 SPA 骨架 HTML,HTTP 200,内容一样。这时候你不是"被拦了",而是根本无法区分真实端点和假路径——扫描器、爬虫、浏览器驱动全部失去意义。遇到这种情况,第一件事是判断这是不是全站挑战:随便请求一个明显不存在的路径,如果返回和首页一样的 200 页面,立刻放弃当前目标架构,换情报来源。
3. 社区类站点(Reddit/HN/Dev.to)普遍识别自动化注册。
自动填表注册的成功率极低,登录态复用反而稳定得多。不要在设计里假设Agent能自己注册账号。
4. 代理软件会劫持DNS污染下载。
开着 TUN 模式的代理时,某些图片/文件 CDN 会被解析到假地址(比如 198.18.0.0/15 段),表现为下载必失败或 0 字节文件。先关 TUN 再下素材,或者用系统的原生路径 curl 而不是 MSYS 路径(后者在 Windows 上会因路径转换悄无声息地失败)。
浏览器自动化最贵的成本不是点击,是维护——页面改版、登录过期、选择器失效。让它能长期跑,靠的是三条:
浏览器自动化的价值不在"能点",而在点完之后你能不能证明它真的点对了。这一条想明白,剩下的都是工程细节。
相关阅读
📚 更多创作资源: https://afdian.com/a/felix007