
1. 这个项目到底在解决什么问题先说说我为什么会折腾这个东西。日常用 Claude Code 或者 Codex 这类命令行 AI 编程助手的时候最让人抓狂的场景之一就是我需要让它帮我查一个网页上的内容或者操作某个已经登录了的后台系统结果它要么只能拿到静态的 HTML 源码要么根本没法处理需要登录态的页面。你让它去抓一个需要 Cookie 才能访问的页面它拿回来的就是登录页毫无意义。这个项目的核心思路其实特别朴素既然我本地浏览器已经登录了各种系统那我为什么不直接让 AI Agent 复用这个已经登录的浏览器会话呢就像油猴脚本Tampermonkey能在浏览器里注入自定义逻辑一样我给 AI Agent 也做了一个类似的“注入层”——让它能够直接操控我已经登录的浏览器读取页面内容、执行点击操作、填写表单甚至截取页面截图。这个项目适合谁如果你日常在用 Claude Code、Codex CLI 或者其他命令行 AI 编程工具并且经常需要它们处理需要登录态的网页任务那这个东西对你来说就是刚需。如果你只是偶尔让 AI 写写代码不涉及浏览器操作那可以先了解一下思路以后遇到类似场景再回来折腾。它能做什么简单说就是三件事第一让 AI Agent 读取你浏览器里任意已登录页面的内容第二让 AI Agent 在你浏览器里执行操作点击、输入、滚动、截图第三把结果回传给 AI Agent 做后续处理。整个过程你不需要导出 Cookie不需要手动复制粘贴AI Agent 直接通过一个本地桥接层跟你浏览器通信。为什么叫“油猴”因为它的工作方式和油猴脚本很像——都是在浏览器和外部工具之间建立一个通道让外部工具能够操控浏览器。区别在于油猴脚本是浏览器内的脚本而这个项目是让浏览器外的 AI Agent 通过本地服务来操控浏览器。本质上是一个“浏览器自动化桥接层”。2. 整体架构设计与技术选型思路2.1 为什么不用 Playwright 或 Puppeteer 直接搞定很多人第一反应是你直接用 Playwright 或者 Puppeteer 不就行了吗确实这两个工具都能操控浏览器但它们有一个致命问题——它们启动的是一个全新的浏览器实例跟你日常用的浏览器完全隔离。你的登录态、你的插件、你的书签全都没有。你可能会说那我用 Playwright 的storageState把 Cookie 导出来不就行了理论上可以但实际操作中问题很多第一很多网站的登录态不只是 Cookie还有 localStorage、sessionStorage、IndexedDB 里的 token第二有些网站有设备指纹校验换个浏览器实例就触发风控第三每次都要手动导出导入维护成本极高。所以这个项目的核心决策就是不启动新浏览器直接连接你已经打开的浏览器。这就需要一个浏览器端的“接收器”和一个外部的“发送器”。2.2 三层架构拆解整个系统我把它拆成了三层第一层浏览器扩展层。这是一个轻量级的浏览器扩展它的职责非常简单——监听本地 WebSocket 连接接收指令在页面里执行对应的操作然后把结果回传。它不处理任何业务逻辑就是一个纯粹的“执行器”。为什么用扩展而不是注入脚本因为扩展有更高的权限可以跨域访问、可以操作多个标签页、可以读取浏览器级别的 API。第二层本地桥接服务。这是一个跑在本地的小型服务通常用 Node.js 或 Python 写。它的职责是接收 AI Agent 发来的请求转换成浏览器扩展能理解的指令格式通过 WebSocket 发给扩展等扩展返回结果后再回传给 AI Agent。为什么需要这一层因为 AI Agent 通常只能执行命令行或者 HTTP 请求没法直接跟浏览器扩展通信。这一层就是翻译官。第三层AI Agent 适配层。这一层是给 Claude Code 或 Codex 用的工具定义。你需要告诉 AI Agent你现在有一个叫browser_execute的工具它接受什么参数返回什么格式。这样 AI Agent 在需要操作浏览器的时候就会自动调用这个工具。三层之间的通信协议我用的是 JSON-RPC 风格的请求-响应模型。每条指令包含action操作类型、params参数、requestId请求 ID用于匹配响应。扩展执行完后返回requestId、result结果数据、error如果有错误。2.3 为什么选 WebSocket 而不是 HTTP这里有一个关键选型浏览器扩展和本地服务之间用什么通信HTTP 轮询当然可以但延迟高、资源浪费大。WebSocket 是长连接扩展可以随时接收指令响应速度在毫秒级。而且 WebSocket 支持双向通信扩展也可以主动推送事件比如页面加载完成、DOM 变化等这对某些需要等待页面渲染的场景非常有用。实测下来WebSocket 方案的端到端延迟通常在 50-150ms 之间完全满足 AI Agent 的交互需求。HTTP 轮询的话即使 500ms 轮询一次平均延迟也有 250ms而且频繁请求会占用不必要的资源。2.4 安全边界设计这里必须重点说一下安全设计。因为这个项目本质上是让外部程序操控你的浏览器如果不加限制风险极大。我做了几层防护第一本地服务只监听127.0.0.1不对外网开放。第二扩展和本地服务之间有一个握手密钥每次连接时需要验证防止其他本地程序冒充。第三扩展只接受白名单域名列表里的操作请求你可以配置哪些网站允许被操控。第四所有操作都有日志记录方便审计。注意千万不要把这个本地服务暴露到公网也不要把握手密钥硬编码在公开的代码仓库里。我见过有人图省事直接写死在代码里然后推到公开仓库结果被人扫到端口后操控浏览器。这种低级错误一定不能犯。3. 核心细节解析与实操要点3.1 浏览器扩展的关键实现细节扩展的核心是一个background.jsManifest V3 里叫 service worker它负责维护 WebSocket 连接。这里有一个坑Manifest V3 的 service worker 会在空闲时被浏览器挂起导致 WebSocket 断连。解决方案是使用chrome.alarmsAPI 定期唤醒 service worker或者用chrome.runtime.connect保持一个长连接端口。另一个关键点是内容脚本content script的注入时机。如果你在页面加载完成之前就尝试操作 DOM会找不到元素。我的做法是扩展收到操作指令后先检查目标标签页的document.readyState如果是loading状态就等待DOMContentLoaded事件后再执行。对于 SPA 应用还需要额外等待路由切换完成这个可以通过监听history.pushState和popstate事件来实现。页面内容的提取我用的是document.body.innerText加上结构化数据的提取。纯文本提取简单但会丢失链接、按钮等交互元素的信息。所以我会额外提取所有可交互元素a、button、input、select的属性和位置信息一起回传给 AI Agent。这样 AI Agent 不仅知道页面上有什么文字还知道有哪些可以点击的元素。3.2 本地桥接服务的指令路由本地服务收到 AI Agent 的请求后需要做几件事解析请求、验证权限、路由到对应的浏览器标签页、等待结果、格式化返回。指令路由这块有一个设计决策是让 AI Agent 指定标签页 ID还是让服务自动选择当前活跃标签页我的方案是两者都支持。默认情况下操作当前活跃标签页但如果 AI Agent 明确指定了标签页 ID 或 URL 匹配规则就路由到对应的标签页。这样既方便简单场景也支持多标签页协作。超时处理也很重要。浏览器操作可能因为页面加载慢、元素找不到等原因卡住。我给每个指令设置了默认 30 秒的超时超时后返回错误信息让 AI Agent 决定是重试还是放弃。超时时间可以通过参数调整比如截图操作可以设短一点10 秒页面加载等待可以设长一点60 秒。3.3 AI Agent 工具定义的编写要点给 Claude Code 或 Codex 写工具定义的时候描述要尽可能清晰。AI Agent 是根据工具描述来决定什么时候调用、怎么调用的。我的工具描述大概长这样{ name: browser_execute, description: 在已登录的浏览器中执行操作。支持读取页面内容、点击元素、填写表单、截图等。当需要获取需要登录态才能访问的网页内容或需要在网页上执行交互操作时使用此工具。, parameters: { type: object, properties: { action: { type: string, enum: [get_content, click, type, screenshot, scroll, wait], description: 要执行的操作类型 }, selector: { type: string, description: CSS 选择器用于定位目标元素 }, text: { type: string, description: 要输入的文本内容仅在 type 操作时使用 }, url_pattern: { type: string, description: 标签页 URL 匹配模式不填则操作当前活跃标签页 } }, required: [action] } }这里的关键是description要写清楚“什么时候用”而不只是“这是什么”。AI Agent 需要知道在什么场景下应该调用这个工具。我试过只写功能描述结果 AI Agent 经常忘记用这个工具或者在不该用的时候乱用。加上场景描述后调用准确率明显提升。3.4 登录态复用的具体机制这是整个项目的核心价值点值得单独展开说。登录态复用的本质是AI Agent 的操作请求最终是在你日常使用的浏览器标签页里执行的所以天然携带了该标签页的所有登录凭证。具体来说当扩展在某个标签页执行fetch请求时浏览器会自动带上该域名的 Cookie。当扩展读取localStorage时拿到的是该域名下已存储的 token。当扩展点击一个按钮时触发的是该页面真实的 JavaScript 事件处理逻辑包括所有认证相关的逻辑。这意味着你不需要做任何额外的登录态同步工作。你在浏览器里登录了某个系统AI Agent 就能直接操作那个系统。你切换了账号AI Agent 操作的就是新账号的上下文。完全透明完全自动。提示如果你的浏览器开了多个用户配置文件Profile扩展需要安装在对应的 Profile 里。不同 Profile 之间的登录态是隔离的扩展也是隔离的。4. 完整实操流程与核心环节实现4.1 环境准备与依赖安装先列一下需要准备的东西一台日常使用的电脑Windows、macOS、Linux 都行Chrome 或 Edge 浏览器Manifest V3 支持Node.js 18 或 Python 3.10用来跑本地桥接服务Claude Code 或 Codex CLI已安装并配置好本地服务的依赖很简单Node.js 方案只需要ws这个库来处理 WebSocket。Python 方案用websockets库。我两个版本都写过Node.js 版本在 Windows 上兼容性更好一些Python 版本在 macOS 上更顺手。你可以根据自己的技术栈选。# Node.js 方案 mkdir browser-bridge cd browser-bridge npm init -y npm install ws # Python 方案 mkdir browser-bridge cd browser-bridge python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install websockets4.2 浏览器扩展的加载与配置扩展的代码结构大概是这样的extension/ manifest.json background.js content.js popup.html popup.jsmanifest.json里需要声明backgroundservice worker、content_scripts、activeTab、scripting、tabs等权限。这里有一个细节host_permissions不要写all_urls而是根据你的实际需求配置白名单。比如你只需要操作某几个内部系统就只写那几个域名。这样既安全也避免扩展请求过多权限引起浏览器警告。加载扩展的步骤打开chrome://extensions/开启“开发者模式”点击“加载已解压的扩展程序”选择extension目录。加载成功后扩展图标会出现在工具栏。点击图标会弹出配置页面你需要在这里填入本地服务的地址默认ws://127.0.0.1:8765和握手密钥。4.3 本地桥接服务的启动与验证本地服务的核心逻辑我用伪代码展示一下const WebSocket require(ws); const wss new WebSocket.Server({ host: 127.0.0.1, port: 8765 }); let browserConnection null; const pendingRequests new Map(); wss.on(connection, (ws, req) { // 验证握手密钥 const authKey req.headers[x-auth-key]; if (authKey ! EXPECTED_KEY) { ws.close(4001, Unauthorized); return; } browserConnection ws; ws.on(message, (data) { const response JSON.parse(data); const pending pendingRequests.get(response.requestId); if (pending) { pending.resolve(response); pendingRequests.delete(response.requestId); } }); }); // 供 AI Agent 调用的 HTTP 接口 const http require(http); http.createServer((req, res) { if (req.method POST req.url /execute) { let body ; req.on(data, chunk body chunk); req.on(end, async () { const command JSON.parse(body); const requestId generateId(); const result await new Promise((resolve, reject) { const timeout setTimeout(() { pendingRequests.delete(requestId); reject(new Error(Timeout)); }, command.timeout || 30000); pendingRequests.set(requestId, { resolve: (data) { clearTimeout(timeout); resolve(data); } }); browserConnection.send(JSON.stringify({ requestId, action: command.action, params: command.params })); }); res.writeHead(200, { Content-Type: application/json }); res.end(JSON.stringify(result)); }); } }).listen(8766, 127.0.0.1);启动服务后先验证浏览器扩展是否连接成功。你可以在扩展的 popup 页面看到连接状态或者在本地服务的日志里看到连接记录。如果连接失败检查端口是否被占用、密钥是否匹配、防火墙是否拦截。4.4 与 Claude Code / Codex 的对接对接方式取决于你用的 AI Agent 支持哪种工具集成方式。Claude Code 支持通过 MCPModel Context Protocol接入外部工具Codex 支持通过自定义工具定义接入。以 MCP 为例你需要写一个 MCP Server把browser_execute工具暴露出去。MCP Server 收到工具调用请求后转发给本地桥接服务的 HTTP 接口拿到结果后再返回给 Claude Code。// mcp-server.js 核心逻辑 server.setRequestHandler(CallToolRequestSchema, async (request) { if (request.params.name browser_execute) { const response await fetch(http://127.0.0.1:8766/execute, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(request.params.arguments) }); const result await response.json(); return { content: [{ type: text, text: JSON.stringify(result) }] }; } });配置好之后你在 Claude Code 里就可以这样用了“帮我看看我浏览器里那个工单系统的待处理列表把前五条标题列出来。”Claude Code 会自动调用browser_execute读取当前活跃标签页的内容提取信息后返回给你。4.5 一个完整的实操案例假设你有一个内部知识库系统需要登录才能访问。你想让 AI Agent 帮你总结某篇文档的内容。第一步你在浏览器里打开那篇文档确认已登录。第二步你在 Claude Code 里说“读取我当前浏览器标签页的内容总结一下主要讲了什么。”第三步Claude Code 调用browser_executeaction 是get_content不指定 url_pattern默认当前活跃标签页。第四步扩展读取页面内容回传给本地服务再回传给 Claude Code。第五步Claude Code 拿到内容后用大模型能力做总结输出给你。整个过程你不需要复制粘贴任何内容不需要导出 Cookie不需要手动操作浏览器。AI Agent 直接“看到”了你浏览器里的页面。注意如果页面内容是动态加载的比如需要滚动才加载更多get_content可能拿不到全部内容。这时候需要先执行scroll操作等待加载完成后再get_content。我一般会连续滚动几次到底部确保所有内容都加载出来。5. 常见问题与排查技巧实录5.1 连接类问题排查问题一扩展显示已连接但发送指令没反应。这种情况通常是 service worker 被挂起了。Manifest V3 的 service worker 在空闲 30 秒后会被浏览器挂起WebSocket 连接也会断开。但扩展的 popup 页面可能还显示“已连接”状态因为那是上次连接的状态缓存。解决方案是加一个心跳机制每 20 秒发一次 ping保持 service worker 活跃。或者用chrome.alarms每 25 秒唤醒一次。问题二本地服务启动报错“端口被占用”。8765 和 8766 这两个端口有时候会被其他程序占用。你可以改成其他端口但记得同步修改扩展配置和 MCP Server 配置。我一般用netstat -ano | findstr 8765Windows或lsof -i :8765macOS/Linux来查是哪个程序占用了端口。问题三握手密钥验证失败。检查扩展配置里的密钥和本地服务里的密钥是否完全一致包括大小写和特殊字符。如果密钥里有特殊字符注意 URL 编码问题。我建议用纯字母数字的密钥避免不必要的麻烦。5.2 操作类问题排查问题四点击元素时报错“element not found”。最常见的原因是选择器写错了或者元素在 iframe 里。如果是 iframe需要先切换到对应的 frame 再操作。另一个原因是元素还没渲染出来需要加等待。我的做法是在点击之前先执行一次wait操作等待目标选择器出现超时时间设 5 秒。问题五输入文本后没有触发页面的事件处理。有些前端框架比如 React、Vue不会监听原生的input事件而是监听合成事件。直接设置element.value不会触发框架的更新。解决方案是模拟真实的键盘输入先 focus 元素然后逐个字符触发keydown、keypress、input、keyup事件。或者用document.execCommand(insertText, false, text)这个在大多数情况下都能触发框架的更新。问题六截图返回空白或旧内容。截图操作需要等待页面渲染完成。如果页面还在加载中截图可能是空白的。解决方案是在截图前先等待document.readyState complete再额外等 500ms 让渲染完成。对于有动画的页面可能需要等更久。5.3 常见问题速查表问题现象可能原因排查方法解决方案扩展显示已连接但无响应service worker 被挂起查看本地服务日志是否有心跳加心跳机制或 alarms 唤醒端口被占用其他程序占用 8765/8766netstat/lsof 查端口换端口并同步配置握手失败密钥不匹配对比两端密钥统一密钥避免特殊字符元素找不到选择器错误或 iframe在控制台测试选择器修正选择器或切换 frame输入不触发事件框架合成事件检查框架类型模拟键盘事件或 execCommand截图空白页面未渲染完成检查 readyState等待加载完成后再截图登录态丢失操作了错误的标签页检查 url_pattern明确指定目标标签页超时无返回页面加载慢或卡死查看超时时间设置调整超时或重试5.4 几个我踩过的坑第一个坑一开始我没做标签页隔离AI Agent 操作的时候如果用户手动切换了标签页操作就会作用到错误的页面上。后来我加了标签页锁定机制指令执行期间锁定目标标签页执行完再释放。第二个坑有些网站的 CSP内容安全策略会阻止扩展注入的脚本执行 fetch 请求。解决方案是把 fetch 请求放到 background service worker 里执行因为 service worker 不受页面 CSP 限制。但这样就没有页面的 Cookie 了需要手动从chrome.cookiesAPI 获取 Cookie 并带上。这个比较麻烦我一般建议直接换用页面内的 XHR 而不是 fetchXHR 受 CSP 限制较小。第三个坑Manifest V3 的chrome.scripting.executeScript在某些页面上会失败比如 Chrome 内置页面、扩展商店页面等。这些页面是受保护的无法注入脚本。遇到这种情况只能提示用户手动操作或者换一个页面。提示如果你经常操作 SPA 应用建议在扩展里加一个“等待网络空闲”的机制。具体做法是监听performance.getEntriesByType(resource)当没有新的资源请求且持续 500ms 时认为页面加载完成。这比固定等待时间更可靠。6. 进阶玩法与扩展思路6.1 多标签页协作基础版本只能操作单个标签页但实际场景中经常需要跨标签页操作。比如在标签页 A 读取数据在标签页 B 填写表单。我的扩展方案是支持在指令中指定tabIdAI Agent 可以先调用list_tabs获取所有标签页的 ID 和 URL然后针对性地操作。这个功能在批量处理场景下特别有用。比如你有十个工单页面同时打开想让 AI Agent 逐个读取并汇总就可以用多标签页协作来实现。6.2 定时任务与自动化流程结合本地服务的定时能力你可以把常用的浏览器操作流程固化下来。比如每天早上 9 点自动登录某个系统读取当天的待办事项整理后发到你的邮箱。这个用node-cron或APScheduler配合browser_execute就能实现。我自己的用法是每周一早上自动打开周报系统读取上周的数据生成周报草稿然后我只需要审核修改就行。省下来的时间虽然不多但胜在不用手动操作体验很好。6.3 与本地文件系统的联动AI Agent 读取网页内容后可以直接写入本地文件。比如读取某个文档页面的内容保存为 Markdown 文件。或者读取表格数据保存为 CSV。这个不需要额外开发Claude Code 本身就支持文件操作你只需要在指令里说“读取当前页面内容并保存到 xxx.md”就行。6.4 安全加固建议如果你打算长期使用这个方案建议做以下加固本地服务加上请求频率限制防止 AI Agent 意外发起大量请求扩展加上操作确认机制敏感操作如提交表单、删除数据需要手动确认日志记录所有操作包括时间、操作类型、目标 URL、执行结果定期轮换握手密钥不要在公共网络环境下使用避免本地服务被扫描到这个项目本质上是一个“浏览器自动化桥接层”它的价值在于打通了 AI Agent 和已登录浏览器之间的通道。你不需要改变现有的工作流程不需要导出任何凭证AI Agent 就能直接操作你浏览器里的页面。对于需要频繁处理网页任务的开发者来说这个效率提升是非常明显的。我自己的体感是以前需要手动复制粘贴的操作现在一句话就能搞定而且准确率比手动操作还高因为 AI Agent 不会漏掉细节。