用本地 LLM 驱动 invisible_playwright_mcp 的隐身浏览器:本地模型接入路线与真正的收益

原创2026-09-30 15:59:172,126 阅读
文章标签:人工智能AI Agent浏览器控制GUI 自动化MCP 服务

用本地 LLM 驱动 invisible_playwright_mcp 的隐身浏览器:本地模型接入路线与真正的收益

这篇技术指南回答一个高频问题:能不能用自己机器上的本地模型(例如通过 Ollama 跑的权重)来驱动 invisible_playwright_mcp 的隐身 Firefox?答案是"能,但入口不在 ui 命令上"——invisible_playwright_mcp 自身的界面只调用 OpenRouter,本地模型要走另一扇门:把它当做一个普通 MCP 客户端,把本项目的 MCP 服务器挂到一个已经自带本地模型的助手或客户端上。读完你会掌握:为什么 --ollama 不存在、本地路线与托管路线的分界点在哪、浏览器驱动任务对模型的具体要求(上下文长度、跨轮耐心、指令保持),以及"页面内容不离开本机"这个与模型能力无关的真正收益,还有一套无模型重放来区分浏览器故障与模型故障。

两条路线,以及那条并不真正本地的路线

先看清楚项目现状,避免到处找一个不存在的开关。

uvx invisible-playwright-mcp ui --openrouter-key ... 是 invisible_playwright_mcp 自带双栏界面的启动方式(左侧对话、右侧实时浏览器),它只连接 OpenRouter,不连任何本地端点。这并非一个可以通过旗标改到别处的默认值:在 src/invisible_playwright_mcp/llm.py 中,BASE_URL = "https://openrouter.ai/api/v1" 被直接硬编码,默认模型 z-ai/glm-5.3-flash 也是常量;同时 ui 命令在没有 key 时拒绝启动(cli.py 会把缺 key 转换成一行清晰的报错并退出 1)。如果你指望 --ollama 之类的东西,当前源码里不存在。

而底层的库具有相反的性质:invisible_playwright,这个通过 MCP 与界面对话的普通 Python 包(pip install invisible-playwright,Playwright API),完全不带模型。"本地"在这个项目里从来不是 invisible_playwright_mcp 自带界面上的一个设置项,它是一扇不同的门:一个已经在本地跑模型的助手或客户端,最常见的是通过 Ollama 之类的本地推理服务,然后把这个浏览器以任何 MCP 客户端挂服务器的方式挂上去。这条路线之所以可行,是因为 Ollama 自 2024 年起就在其 chat API 上支持工具调用(tools 参数入、tool_calls 响应出),它的 OpenAI 兼容端点接受同样的请求形状。哪些桌面客户端能把本地模型接到 MCP 上变化很快,本文不点名具体客户端——动手前请查阅你自己客户端的 MCP 文档。

一句话总结分界:界面 = 模型在 OpenRouter;MCP 服务器 = 模型由调用它的客户端决定(可以完全不在本地)。这两者用的是同一个浏览器、同一套工具、同一个引擎。

把浏览器挂到已经搞定模型的客户端上

挂载机制与模型住在哪里无关。以 Claude Code 为例(running-invisible-playwright-mcp-with-claude-code.md 有完整流程),两条命令、一次性安装:

claude plugin marketplace add feder-cr/invisible_playwright_mcp
claude plugin install invisible-playwright-mcp@feder-cr

第一条把本仓库注册为插件市场,第二条从其中安装 invisible-playwright-mcp 插件(默认用户级作用域)。插件带来两样东西:以 uvx invisible-playwright-mcp 启动的 MCP 服务器(无需 clone 或 pip install),以及一个 setup 技能——当 browser_open 报告引擎下载失败时,Claude 能照着它带你走完下载流程。Claude Code 自己的模型是托管的,这不妨碍它作为示例:一个围绕本地模型构建的客户端,用的是等价的命令或配置界面来添加 MCP 服务器。

不通过插件的客户端则写配置文件,配置块本体统一维护在 docs/mcp-server.md(多数客户端用顶层 mcpServers 键):

{
  "mcpServers": {
    "stealth": {
      "command": "uvx",
      "args": ["invisible-playwright-mcp"]
    }
  }
}

换汤不换药的部分在于:无论谁来调用,另一端的包 invisible_playwright_mcp、它暴露的工具名、以及首次使用时下载的约四分之一 GB 的引擎(cli.py 会在端口开放前先把引擎拉到磁盘,进度打在一行终端输出上,并缓存共享)都完全一样。服务器根本不知道调用它的模型跑在你的 GPU 上还是别人的服务器上,这是设计使然:它只看到工具调用。 想提前把下载做完,或下载失败后重试,可以单独跑:

uvx invisible-playwright fetch

浏览器驱动任务对模型的真正要求

which-model-to-use-with-invisible-playwright-mcp.md 详细拆解了这个任务实际锻炼模型的哪些能力,每一条对本地模型同样成立:

  • 每一轮都要输出格式良好的工具调用。invisible_playwright_mcp 的循环(agent.py 中唯一的循环)会让模型收到任务与浏览器工具,回复一句思考与通常一个工具调用,工具执行、结果进入 transcript、再调模型。格式不良的参数会被打回重试,而每次重试都是一整轮全额计费、且毫无进展的调用。
  • 在系统提示下严格遵循指令。代理被要求先观察再行动、一次一个动作、任务完成即停止调用工具。一直调用工具的模型烧轮数;不读页面就作答的模型在编造结果。
  • 阅读长而杂乱的上下文。任务中后期 transcript 大部分是抽取出的页面文本,模型要在噪声里找出那个价格,并注意到某次点击什么都没改变。
  • 知道何时算完、何时已卡住。循环没有轮数上限,模型不停止调用工具就一直转下去,所以"跑偏"是最贵的失败。

其中前两条(上下文长度、跨轮保持)在小规模上会变得更难。

上下文长度是第一道要跨过的门槛。 一次对普通页面的结构化读取本身就有数千字符,而在"每次重发整个 transcript"的循环里(API 无状态,第 12 轮要为任务、此前所有思考与所有工具结果再付一次费),那一次读取在之后每一轮都要再付一遍。中途耗尽上下文的模型,无论推理多好都完不成任务。读你实际拉取的具体模型 tag 的上下文窗口,而不是该系列的宣传数字,并且拿一次真实的页面读取(而非 hello-world 提示词)去对照。

第二是跨轮的耐心。 真实任务很少一两轮就结束;普通硬件上的本地推理通常比资源充足的托管 API 每轮更慢,而这个等待会随轮数逐轮累积放大,比面对快速 API 时更明显。

机器按模型来配,而不是按浏览器来配。 浏览器这边是小的、可测量的:self-hosted-ai-agent.md 的实测数字是 613 MB 磁盘(浏览器解包后 547.6 MB,Python 侧仅 65.7 MB)与约 900 MB 常驻内存(整个进程树),相对你即将加载的权重是舍入误差。本地推理才是决定内存与显存预算的那一半账。

小模型会在哪里丢掉主线

在花一个下午调试错误的东西之前,先诚实地看待失败模式。随着一份不断增长的 transcript 走过多轮之后,较小的模型往往出现这三种症状之一:重复点击已经试过的元素、丢掉原始指令里的某条约束、或者步骤还没做完就宣布任务完成。这正是 browser-problem-or-model-problem.md 为任何模型列出的同类模型侧症状——较小的本地模型只是更早、更频繁地撞上它们,因为那份同样在增长、最终连前沿模型都会被拖累的上下文,会更早若干轮压垮小模型。这不是浏览器的缺陷:页面加载了,工具调用成功了,模型只是在自己多步任务的线程上掉了线。

与能力无关的那个真正收益:隐私

即使一个本地模型在第 18 轮磕绊,它在第 1 轮就已经交付了真正的好处:它读到的页面内容去了你本机的进程,没有越过本机一步。OpenRouter 路线则把同样的文本发给你所选模型最终由哪个提供商服务——仅仅是路由一个请求到托管 API 而已。对于你不想被记录在第三方服务器上的页面——内部仪表盘、账户页、任何敏感内容——本地模型是唯一一条无论模型能力如何都守住这点的路线。这是主动接受一个更弱模型的理由,而不是期待一个强模型的理由。这也是"本地"值得的维度:能力通常是交换出去的筹码,隐私才是买回来的东西。

补充一个事实基线:invisible_playwright_mcp 的 OpenRouter key 只用于 OpenRouter API,并且在启动浏览器引擎前会从子进程环境中按名按值剥离(仓库里有用测试钉住这一行为),但这改变不了"页面文本进入托管模型请求"这一数据流事实。

区分模型问题与浏览器问题:无模型重放

browser-problem-or-model-problem.md 描述的无模型重放非常契合本地设置,因为两者前提相同:什么都不发出去,什么都不花。用 invisible_playwright 库直接重放失败的步骤,不挂任何模型——同样的引擎、同样的指纹工作、同样的连接路径,减去模型。如果失败复现,那么任何模型(本地或托管)都修不了它;如果重放干净通过,本地模型就是那个变量,下一步是换更大的本地模型,或在同一任务、同一 --seed(保持浏览器身份恒定,让模型成为唯一被动的变量)上做一次托管运行,再下结论说整个本地路线不合适。

下面是该页实际执行过的重放模板(对公开爬取沙箱跑过,输出 Travel | 11 results):

from invisible_playwright import InvisiblePlaywright

with InvisiblePlaywright(seed=7) as browser:
    page = browser.new_page()
    page.goto("https://books.toscrape.com/", wait_until="domcontentloaded")
    page.click('a[href*="category/books/travel"]')
    page.locator('h1:text("Travel")').wait_for()
    print(page.locator("h1").inner_text(), "|",
          page.locator("form strong").first.inner_text(), "results")

一个用命换来的细节写在里面:click 触发跳转后,要 wait_for() 目标页特有的元素再读取,否则第一版片段会自信地报出旧页面的标题。

诊断顺序建议:先读失败 transcript(分界线往往一眼可见:工具结果里的拦截页是浏览器侧,健康页面上的错误点击是模型侧)→ 无模型重放失败步骤 → 同一任务两个模型对比 → 一次只改一个变量。注意两边会互相喂养:中途开始拒绝的页面让称职的模型看起来像迷路了;对失败猛点重试的模型把一次软拒绝打成硬拦截。如果 transcript 两边都有症状,先修浏览器侧,它是上游。

常见问题速答

我能在 invisible_playwright_mcp 自带的界面上用本地模型吗? 不能。uvx invisible-playwright-mcp ui 只调用 OpenRouter,这在源码里是硬编码的。本地模型意味着把这个浏览器改挂到一个不同的客户端上、走 MCP。

本地模型做浏览器代理工作,实际买到的是什么? 主要是隐私:页面内容留在你的机器上,而不是到达托管模型的提供商。能力通常是交换出去的筹码,不是目标。

为什么本地模型似乎做到一半就忘了任务? transcript 每轮都在增长,较小模型对早期指令或工具结果的把握通常比较早松动。同样的压力落在每个模型身上;小的只是失败得更早。

一定要用 Ollama 吗? 不是硬性要求。任何会说 MCP 工具调用、且已把本地模型安排妥当的客户端——无论它怎么服务那个模型——都用同样的方式挂这个浏览器。当前哪些客户端能做这事变得太快,此处无法可靠点名。

怎么判断一次坏运行是模型的问题还是浏览器的问题? 用无模型方式对着库重放失败步骤:能复现,浏览器侧有责,与任何模型无关;不能复现,模型就是变量。

延伸阅读

关键结论

本地模型改变这个浏览器的地方只有一处:模型运行的位置。invisible_playwright_mcp 自身界面只连 OpenRouter、没有本地模型旗标(源码可见),本地路线的正解是把这个浏览器以 MCP 服务器身份挂到已跑本地模型的客户端上——服务器只看见工具调用,不知道也无需知道模型在哪。真正值得为此接受一个较弱模型的原因与能力无关:页面内容不离开你的机器。在动手前,用无模型重放先分清浏览器侧与模型侧,再决定是换更大的本地模型、做一次托管对照运行,还是调整指令。

登录后查看全文
invisible_dots