资讯动态

Playwright MCP Server:让大模型直接操作浏览器的实战指南

发布时间:2026/10/14 5:23:39 来源:尧图企业网站定制
简介Playwright MCP Server 是一套基于 FastMCP 框架构建的专业级浏览器自动化服务器面向需要网页交互与数据提取的开发者及 LLM 应用集成场景。它依托微软 Playwright 跨 chromium、firefox、webkit 的自动化能力可模拟表单填写、链接点击、内容抓取等操作并针对自然语言指令处理做了优化适合复杂网页操作与大规模数据采集任务。资源包共 26 个文件约 90KB以 11 个 py 脚本为核心辅以 5 个 md 文档、2 个 yml 配置、1 个 json 示例及 html、toml、license 等涵盖服务端逻辑、工具模块、测试用例与部署说明。目录中 server.py、tools、core 等模块划分清晰examples 提供 claude_desktop_config.json 与 usage_example.py 便于快速接入docs 内含技术架构与发布配置文档方便读者理解模块化设计与自定义脚本扩展思路。目前已有 147 人学习适合希望将浏览器自动化接入 LLM 工作流的开发者参考。1. 浏览器自动化接进大模型Playwright MCP Server 到底解决了谁的痛点你可能已经用 Playwright 写过一堆脚本登录、填表、截图、抓数据跑起来挺顺。但当你试图让大模型自己“动手”操作浏览器时问题就来了——模型只能输出文本它没法直接点按钮、填输入框、等页面加载。你需要在模型和浏览器之间搭一层协议把“点击”“输入”“截图”这些动作变成模型能调用的工具。Playwright MCP Server 就是干这个的它基于 FastMCP 框架把 Playwright 的浏览器自动化能力封装成 MCP 协议下的工具集让任何支持 MCP 的客户端比如某些桌面 AI 工具、IDE 插件、自研 Agent都能直接调用浏览器操作。适合谁一是想给自家 Agent 加浏览器能力的后端工程师二是做自动化测试但想用自然语言驱动的人三是需要让模型“看见”网页并交互的产品开发者。这一章先把概念立住后面手把手跑通。2. 从零搭起 Playwright MCP Server环境、依赖与最小可运行实例2.1 为什么选 FastMCP 而不是手写 HTTP 接口FastMCP 是一个 Python 库专门用来快速构建 MCP 服务器。它帮你处理了协议握手、工具注册、参数校验、错误封装这些脏活。如果你手写 HTTP 接口得自己定义路由、解析 JSON-RPC、管理会话状态还要兼容 MCP 客户端的发现机制。FastMCP 把这些抽象成装饰器mcp.tool()一贴函数就变成模型可调用的工具。Playwright 负责浏览器操作FastMCP 负责协议转换两者结合就是 Playwright MCP Server 的核心。选型理由很直接省去协议层开发专注浏览器逻辑社区活跃遇到问题能搜到答案Python 生态和 Playwright 无缝衔接。2.2 安装依赖与初始化项目结构先建一个干净的虚拟环境避免和系统里的包打架。我一般用 venv命令如下python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install fastmcp playwright playwright install chromium # 只装 Chromium 减小体积需要 Firefox/WebKit 再加项目结构建议这样组织后面扩展工具时不会乱playwright-mcp-server/ ├── server.py # 入口注册工具 ├── browser.py # 浏览器生命周期管理 ├── tools/ │ ├── navigation.py # 导航类工具 │ ├── interaction.py # 点击、输入等 │ └── extraction.py # 截图、取文本 └── requirements.txtbrowser.py里封装一个单例的浏览器上下文避免每次调用都重启浏览器。关键参数headless设 True 适合服务器环境设 False 方便调试slow_mo加 50~100 毫秒能让模型看清操作节奏但会拖慢批量任务。2.3 注册第一个工具打开网页并返回标题在server.py里写最小可运行实例from fastmcp import FastMCP from playwright.sync_api import sync_playwright mcp FastMCP(Playwright MCP Server) # 全局浏览器实例懒加载 _playwright None _browser None def get_browser(): global _playwright, _browser if _browser is None: _playwright sync_playwright().start() _browser _playwright.chromium.launch(headlessTrue) return _browser mcp.tool() def open_page(url: str) - str: 打开指定 URL 并返回页面标题 browser get_browser() page browser.new_page() try: page.goto(url, timeout15000, wait_untildomcontentloaded) title page.title() return f页面标题: {title} except Exception as e: return f打开失败: {str(e)} finally: page.close() if __name__ __main__: mcp.run()逻辑说明mcp.tool()把open_page注册为 MCP 工具模型调用时传url参数。wait_untildomcontentloaded比默认的load快适合大多数场景如果页面有大量异步渲染改成networkidle但超时要加长。page.close()放在 finally 里防止内存泄漏。参数timeout单位毫秒15 秒是保守值内网页面可以降到 5000。2.4 用 MCP 客户端验证工具是否可用启动服务器python server.py。然后用任意 MCP 客户端连接或者用 FastMCP 自带的测试工具from fastmcp import Client async def test(): async with Client(server.py) as client: result await client.call_tool(open_page, {url: https://example.com}) print(result) import asyncio asyncio.run(test())如果返回页面标题说明链路通了。常见失败端口被占、Playwright 浏览器没装、Python 版本低于 3.10。看到Tool not found检查装饰器是否写对看到Browser closed检查是否在工具外提前关了浏览器。3. 把点击、输入、截图做成模型能调用的工具参数设计与状态管理3.1 点击与输入选择器策略和等待机制模型不会写 CSS 选择器所以工具参数要设计得“傻瓜化”。我一般提供三种定位方式文本内容、角色名称、CSS 选择器。优先级文本 角色 CSS因为文本最贴近自然语言。mcp.tool() def click_element(text: str None, role: str None, name: str None, selector: str None) - str: 点击页面元素支持文本、角色名称、CSS 选择器三种方式 browser get_browser() page browser.new_page() try: page.goto(current_url, timeout10000) # current_url 需从会话状态取 if text: page.get_by_text(text, exactFalse).first.click(timeout5000) elif role and name: page.get_by_role(role, namename).click(timeout5000) elif selector: page.locator(selector).first.click(timeout5000) else: return 请提供定位参数 page.wait_for_timeout(500) # 给页面反应时间 return 点击成功 except Exception as e: return f点击失败: {str(e)} finally: page.close()参数说明exactFalse允许模糊匹配模型说“登录按钮”也能点到“登录”。wait_for_timeout(500)是玄学保险有些 SPA 点击后 DOM 更新慢不加这行下一步取文本会拿到旧内容。timeout5000对大多数页面够用慢页面调到 10000。3.2 会话状态保持别让每次调用都开新页面上面的代码每次new_page()会丢失登录态和当前 URL。正确做法是用一个字典按session_id存 page 对象_sessions {} mcp.tool() def navigate(session_id: str, url: str) - str: 在指定会话中导航到 URL browser get_browser() if session_id not in _sessions: _sessions[session_id] browser.new_page() page _sessions[session_id] page.goto(url, timeout15000, wait_untildomcontentloaded) return f当前页面: {page.title()} mcp.tool() def close_session(session_id: str) - str: 关闭会话释放资源 if session_id in _sessions: _sessions[session_id].close() del _sessions[session_id] return 会话已关闭 return 会话不存在模型调用时带上session_id就能连续操作同一个页面。注意会话多了会吃内存建议加一个定时清理超过 10 分钟没活动的会话自动关掉。3.3 截图与文本提取让模型“看见”页面截图返回 base64 还是文件路径取决于客户端。支持多模态的客户端直接传 base64纯文本客户端传路径。我一般两个都返回import base64 mcp.tool() def screenshot(session_id: str, full_page: bool False) - dict: 截取当前页面返回 base64 和保存路径 page _sessions.get(session_id) if not page: return {error: 会话不存在} path f/tmp/screenshot_{session_id}.png page.screenshot(pathpath, full_pagefull_page) with open(path, rb) as f: b64 base64.b64encode(f.read()).decode() return {base64: b64, path: path} mcp.tool() def get_text(session_id: str, selector: str body) - str: 提取指定元素的文本内容 page _sessions.get(session_id) if not page: return 会话不存在 return page.locator(selector).inner_text(timeout5000)full_pageTrue会滚动截全页适合内容长的页面但图片体积大base64 可能超客户端限制。get_text默认取 body模型可以指定selector缩小范围。注意inner_text会等元素出现超时设 5000 毫秒页面没加载完就报错。4. 避坑与排查Playwright MCP Server 落地时最容易翻车的五个地方4.1 浏览器启动报错 “Executable doesnt exist”现象运行工具时抛异常提示找不到 Chromium 可执行文件。原因playwright install没执行或者虚拟环境切换后没重新装。解决在激活的虚拟环境里跑playwright install chromium如果还不行加--with-deps装系统依赖。Docker 里部署时基础镜像要带libnss3、libatk-bridge2.0-0这些库否则 headless 也起不来。4.2 工具调用超时但手动跑脚本正常现象MCP 客户端调open_page等 30 秒没返回。原因MCP 客户端默认超时可能只有 10 秒而页面加载慢。解决在工具内部把timeout设小一点比如 8000并在客户端配置里调大超时。另外检查是不是wait_untilnetworkidle导致一直等不到网络空闲改成domcontentloaded立竿见影。4.3 点击没反应但选择器在 DevTools 里能定位现象click_element返回成功但页面没变化。原因元素被遮挡、在 iframe 里、或者需要先滚动到可视区域。解决先page.locator(selector).scroll_into_view_if_needed()再点击。iframe 里的元素要page.frame_locator()切换上下文。遮挡问题用forceTrue强制点击但慎用可能点到错误元素。4.4 内存涨得飞快跑几十次就 OOM现象服务器跑一段时间后进程被 kill。原因每次调用都new_page()没关或者会话字典只增不减。解决确保每个工具在 finally 里关 page如果是一次性操作会话模式加定时清理。另外 Playwright 的 browser context 比 page 更重别在循环里反复创建 context。4.5 模型传的参数类型不对工具直接崩现象模型把full_page传成字符串true而不是布尔值。原因MCP 协议虽然会做类型校验但有些客户端不严格。解决在工具函数里做防御性转换比如full_page str(full_page).lower() true。参数默认值给清楚模型看到full_page: bool False会更容易传对。5. 进阶技巧用 Playwright MCP Server 做多步任务与结果验证多步任务的关键是让模型自己决定下一步调什么工具。你不需要写死流程只要把工具注册好模型会按需组合。比如“登录后抓取订单列表”这个任务模型会先调navigate打开登录页再调click_element点用户名输入框调type_text输入账号再点登录最后调get_text取订单。你可以在工具返回值里加“当前页面标题”和“可见按钮列表”帮模型判断状态。验证方法我常用两种一是回放式验证把模型调用的工具序列和参数记下来人工检查是否符合预期二是断言式验证在关键步骤后加一个assert_text工具让模型自己确认页面出现了预期文本。比如mcp.tool() def assert_text(session_id: str, expected: str) - str: 检查页面是否包含预期文本 page _sessions.get(session_id) if not page: return 会话不存在 content page.locator(body).inner_text() if expected in content: return 验证通过 return f验证失败未找到: {expected}模型在登录后调assert_text(session_id, 欢迎回来)如果返回失败它会尝试重新登录或报错。这比你自己写 if-else 灵活得多。一个具体技巧给工具返回值加上“建议下一步”。比如click_element成功后返回点击成功当前页面有按钮: 提交, 取消模型看到就知道下一步可以点提交。这招在复杂表单里特别管用减少模型瞎猜。最后说个血泪教训别让模型直接操作生产环境的浏览器。我一般会起一个独立的浏览器实例连到测试账号和沙箱数据并且给每个会话设一个最大操作步数比如 50 步超了就强制关闭。后悔药没有但预防针可以打。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑