1. 从 RPA 和 Selenium 迁移时,我到底在痛什么
如果你做过两年以上自动化,大概率经历过这样的循环:RPA 里拖拽出来的流程,业务页面一改版就全红;Selenium 脚本里写满time.sleep(3),跑一次要十分钟,失败率还高得离谱。RPA 的问题在于它把「界面坐标」当契约,Selenium 的问题在于它把「DOM 结构」当契约,而这两样东西恰恰是前端迭代中最不稳定的部分。
MCP(Model Context Protocol)加 Playwright 的组合,换了一个思路:不再让脚本去死记元素路径,而是让模型理解「我要做什么」,再由 Playwright 去执行浏览器动作。Playwright 本身自带自动等待、多浏览器内核、网络拦截能力,比 Selenium 的显式等待稳得多;MCP 则把「模型决策」和「浏览器执行」拆成两个可独立调试的进程,出问题时你能清楚知道是模型理解错了,还是页面真的没加载出来。
这套方案适合谁?适合已经写过 Selenium 或 RPA、想把手动维护脚本的精力转移到「描述任务」上的开发者;也适合需要做数据采集、表单填报、页面巡检,但不想再被 XPath 和 iframe 折磨的人。下面我从环境准备开始,把 MCP 服务端配置、Playwright 启动参数、统一 Key 接入,以及三步验证动作完整走一遍。
2. TaoToken 前置:统一 Key 与 MCP 服务端骨架
MCP 服务端要调用模型能力,就得有一个稳定的 API 入口。我试过把 Key 散落在各个脚本里,后来统一收敛到 TaoToken 的 API 地址https://taotoken.net/api,配合一个 Key 管理多个模型调用,省去了到处改配置的麻烦。你可以在控制台创建 Key,然后把它写进 MCP 的配置文件里。
先看 MCP 服务端的config.toml骨架。这个文件决定了 MCP Server 启动时加载哪些工具、用哪个模型端点、超时和重试怎么设:
# config.toml - MCP Server 配置骨架 [server] name = "playwright-mcp" version = "0.1.0" transport = "stdio" # 本地开发用 stdio,部署可换 sse log_level = "info" [model] provider = "openai-compatible" base_url = "https://taotoken.net/api" api_key = "${TAOTOKEN_API_KEY}" # 从环境变量读取,不要硬编码 model = "claude-sonnet-4-20250514" max_tokens = 4096 timeout_seconds = 60 retry = 2 [playwright] headless = true browser = "chromium" viewport_width = 1440 viewport_height = 900 navigation_timeout = 30000 action_timeout = 15000 [tools] enabled = ["navigate", "click", "fill", "screenshot", "extract_text"]这里有几个点值得展开。transport = "stdio"表示 MCP Server 通过标准输入输出和客户端通信,适合本地调试;如果你要远程调用,可以改成sse并配端口。api_key用${TAOTOKEN_API_KEY}占位,实际运行时从环境变量注入,避免 Key 进版本库。model字段填你实际要用的模型名,TaoToken 的 API 兼容 OpenAI 格式,所以provider写openai-compatible即可。
环境变量这样设置:
# Linux / macOS export TAOTOKEN_API_KEY="sk-你的Key" # Windows PowerShell $env:TAOTOKEN_API_KEY="sk-你的Key"如果你还没创建 Key,去控制台的 API Keys 页面生成一个,权限选「模型调用」就够了,不需要开管理权限。这一步做完,MCP 服务端就有了调用模型的能力,接下来配 Playwright 的浏览器启动参数。
3. 可复制配置:Playwright 启动参数与 MCP 工具注册
Playwright 的启动参数直接决定自动化稳不稳。默认配置下,Chromium 以 headless 模式启动,但很多页面会检测 headless 特征并返回不同内容。我的做法是保留 headless 但加上一组反检测参数,同时把slow_mo设小一点方便观察:
# playwright_launcher.py from playwright.sync_api import sync_playwright def launch_browser(): pw = sync_playwright().start() browser = pw.chromium.launch( headless=True, args=[ "--disable-blink-features=AutomationControlled", "--no-sandbox", "--disable-dev-shm-usage", "--disable-gpu", "--window-size=1440,900", ], slow_mo=50, # 每步操作间隔 50ms,便于调试 ) context = browser.new_context( viewport={"width": 1440, "height": 900}, user_agent=( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/124.0.0.0 Safari/537.36" ), locale="zh-CN", timezone_id="Asia/Shanghai", ) context.set_default_timeout(15000) context.set_default_navigation_timeout(30000) return pw, browser, context--disable-blink-features=AutomationControlled是减少自动化特征的关键参数,配合自定义 user_agent,能显著降低被识别概率。--disable-dev-shm-usage在容器环境里防止共享内存不足导致崩溃。slow_mo只在调试时开,生产环境设 0。
接下来把 Playwright 动作注册成 MCP 工具。MCP 的工具定义是一个 JSON Schema,描述工具名、参数和返回值。下面是一个navigate工具的注册示例:
# mcp_tools.py from mcp.server import Server from mcp.types import Tool, TextContent server = Server("playwright-mcp") @server.list_tools() async def list_tools(): return [ Tool( name="navigate", description="打开指定 URL 并等待页面加载完成", inputSchema={ "type": "object", "properties": { "url": {"type": "string", "description": "目标网址"}, "wait_until": { "type": "string", "enum": ["load", "domcontentloaded", "networkidle"], "default": "networkidle", }, }, "required": ["url"], }, ), Tool( name="extract_text", description="提取页面中指定选择器的文本内容", inputSchema={ "type": "object", "properties": { "selector": {"type": "string"}, "all": {"type": "boolean", "default": False}, }, "required": ["selector"], }, ), ] @server.call_tool() async def call_tool(name: str, arguments: dict): if name == "navigate": page = get_current_page() page.goto(arguments["url"], wait_until=arguments.get("wait_until", "networkidle")) return [TextContent(type="text", text=f"已打开 {arguments['url']}")] if name == "extract_text": page = get_current_page() if arguments.get("all"): texts = page.locator(arguments["selector"]).all_inner_texts() else: texts = [page.locator(arguments["selector"]).inner_text()] return [TextContent(type="text", text="\n".join(texts))] raise ValueError(f"未知工具: {name}")wait_until="networkidle"比 Selenium 的implicitly_wait聪明得多,它会等网络请求静默后才继续,动态加载的页面也能抓到。工具注册完,MCP 客户端就能用自然语言触发这些动作了。
4. 验证请求:三步确认调用链路正常
配置写完不代表能跑,我习惯用三步验证法确认整条链路。第一步启动 MCP 服务,第二步跑一个最小抓取脚本,第三步看日志确认模型调用和浏览器动作都发生了。
4.1 第一步:启动 MCP 服务
# 确保环境变量已设置 echo $TAOTOKEN_API_KEY # 启动 MCP Server python -m mcp_server --config config.toml正常启动后终端会输出类似:
[INFO] MCP Server playwright-mcp v0.1.0 starting... [INFO] Transport: stdio [INFO] Model endpoint: https://taotoken.net/api [INFO] Tools loaded: navigate, click, fill, screenshot, extract_text [INFO] Server ready, waiting for requests...如果卡在Model endpoint那行不动,多半是 Key 没读到或网络不通。先确认echo $TAOTOKEN_API_KEY有输出,再检查base_url有没有拼错。
4.2 第二步:跑通首个页面抓取脚本
写一个最小客户端,通过 MCP 协议让模型决定抓什么,Playwright 执行:
# first_scrape.py import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def main(): params = StdioServerParameters( command="python", args=["-m", "mcp_server", "--config", "config.toml"], ) async with stdio_client(params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() # 让模型理解任务:打开页面并提取标题 result = await session.call_tool( "navigate", {"url": "https://example.com", "wait_until": "networkidle"}, ) print("导航结果:", result.content[0].text) result = await session.call_tool( "extract_text", {"selector": "h1", "all": False}, ) print("页面标题:", result.content[0].text) asyncio.run(main())运行python first_scrape.py,预期输出:
导航结果: 已打开 https://example.com 页面标题: Example Domain这一步跑通,说明 MCP 工具注册、Playwright 启动、页面加载三个环节都正常。
4.3 第三步:确认调用链路日志
把config.toml里的log_level临时改成debug,重启服务后再跑一次脚本,你会看到类似日志:
[DEBUG] Received tool call: navigate [DEBUG] Model request -> https://taotoken.net/api/v1/chat/completions [DEBUG] Model response: 200 OK, tokens used: 312 [DEBUG] Playwright: page.goto(https://example.com) [DEBUG] Playwright: navigation completed in 842ms [DEBUG] Tool result returned to client重点看三行:Model request确认请求打到了 TaoToken 的 API;Model response: 200确认鉴权和模型调用成功;Playwright: navigation completed确认浏览器动作执行完毕。三行都在,链路就是通的。如果Model response返回 401,检查 Key;返回 429,说明触发限流,降低并发或换时间段。
5. 本篇常见错排查
迁移过程中我踩过的坑集中在几个地方,列出来供你对照。
报错ModuleNotFoundError: No module named 'mcp':MCP 的 Python SDK 还在快速迭代,用pip install mcp装最新版,如果和 Playwright 版本冲突,先建虚拟环境再装。Playwright 需要额外执行playwright install chromium下载浏览器内核,只装 pip 包不装内核会报Executable doesn't exist。
页面抓到的内容是空的:八成是wait_until设成了load,而目标页面是前端渲染的。改成networkidle,或者显式等某个选择器出现:page.wait_for_selector(".content", timeout=10000)。Selenium 迁移过来的同学容易习惯性加time.sleep,在 Playwright 里应该用wait_for_selector或expect断言。
MCP 服务启动后客户端连不上:transport设成stdio时,客户端必须用同样的 stdio 方式启动子进程,不能一个用 stdio 一个用 sse。如果你在容器里跑,注意 stdio 需要保持进程存活,别让主进程提前退出。
模型返回的指令 Playwright 执行不了:这是工具 schema 定义太宽泛导致的。比如click工具只接受selector字符串,模型可能返回一段自然语言描述。解决办法是在工具description里写清楚参数格式,并给几个示例,模型会照着格式输出。
Key 泄露风险:永远不要把 Key 写进config.toml提交到 Git。用环境变量或.env文件,并把.env加进.gitignore。TaoToken 控制台可以随时吊销旧 Key,怀疑泄露就立即轮换。
6. 接入文档与后续动作
三步验证跑通后,你手里就有了一套可用的 MCP + Playwright 自动化骨架。接下来要做的,是把具体业务动作注册成更多 MCP 工具,比如表单填报、文件下载、截图对比。每加一个工具,都按「定义 schema → 实现 Playwright 动作 → 用 debug 日志验证」的流程走一遍,链路清晰,出问题也好定位。
如果你在配置 Key 或接入 MCP 时遇到鉴权、超时、限流这类问题,可以直接查接入文档,里面有各语言 SDK 的示例和错误码说明。需要生成或轮换 Key 就去 API Keys 页面。想先验证模型对话是否正常,可以用模型对话页面发一条测试消息,确认 Key 和端点都通。长期做编码和 Agent 任务的话,Coding Plan 页面有更完整的额度方案,适合把自动化任务跑在稳定配额上。
从 RPA 和 Selenium 迁过来,最大的心态转变是:不再追求「一次写对脚本」,而是把任务描述清楚,让模型和 Playwright 去处理页面变化。脚本维护量降下来之后,你才有精力去做真正有价值的自动化设计。