Browser Control Skill:实现AI与浏览器安全高效协同的自动化框架
1. 项目概述:当AI成为你的浏览器副驾驶
如果你和我一样,每天有大量时间泡在浏览器里——查资料、填表单、操作各种SaaS后台,那你肯定也幻想过:要是能有个助手,帮我点点按钮、填填表格、上传个文件,该多省事。市面上确实有不少AI联网工具,但它们大多只能“读”网页,抓取文本摘要给你看。一旦需要“动手”操作——比如在飞书文档里编辑、在Jira里创建工单、在内部系统上传附件——这些工具就集体哑火了。
更头疼的是,你没法让AI直接用你正在用的浏览器。你已经在Chrome里登录了公司内网、企业微信、Notion,积累了宝贵的登录态(Session)。但现有的自动化方案,比如Playwright或Puppeteer,都得新开一个“干净”的浏览器实例,你的登录态带不过去,得重新登录一遍,麻烦不说,有些二次验证还过不了。另一种思路,用Chrome DevTools Protocol(CDP)直接连你的Chrome,虽然能共享登录态,但你和AI无法同时操作,它一动鼠标,你的页面就跟着跳,根本没法并行工作。
Browser Control Skill这个项目,就是为了解决这个核心痛点:让人和AI能和平、高效地共用一个真实的浏览器环境。它不是另一个网页爬虫,而是一个完整的浏览器操控技能包,让AI能真正成为你在浏览器里的“手”,帮你完成那些重复、繁琐的交互操作,而你,可以在一旁监督,或者完全放手让它去后台并行处理多个任务。
2. 核心设计思路:前台协作与后台并行的双模式架构
这个项目的聪明之处在于,它没有发明一种新的浏览器控制协议,而是巧妙地组合利用了现有操作系统的UI自动化能力和浏览器的底层调试协议,根据不同的任务场景,智能切换两种工作模式。
2.1 前台协作模式:基于AppleScript的“所见即所得”操作
当你正盯着某个页面,需要AI即时协助时,就适合用前台模式。例如,你打开了一个复杂的报销表单,想让AI帮你填写。在这个模式下,Skill通过AppleScript(在macOS上)来操控浏览器。
为什么是AppleScript?因为它能直接与最前端的应用程序窗口对话,实现“所见即所得”。当你执行/browse here 点击提交按钮时,Skill的底层逻辑是这样的:
- 通过AppleScript获取当前最前端的窗口信息,确认是Chrome。
- 向Chrome发送JavaScript指令,例如
document.querySelector(‘button[type=“submit”]’).click()。 - 这个点击动作会真实地发生在你眼前的页面上,你能看到按钮被按下、表单提交的整个过程。
注意:这种模式高度依赖UI层的稳定性。如果页面结构在AI操作期间突然变化(比如弹出一个加载动画),原先定位的元素可能失效,导致操作失败。因此,它最适合相对静态或你实时监控的页面。
2.2 后台并行模式:基于CDP的“隐身”多任务处理
当你想让AI在后台默默调研,不干扰你当前工作时,后台模式就派上用场了。比如,你需要同时查询5家竞品公司的官网信息。这时,Skill会切换到Chrome DevTools Protocol模式。
CDP模式是如何做到“隐身”的?关键在于targetId。Chrome为每一个标签页(Tab)、每一个扩展程序页面甚至每一个DevTools窗口都分配了唯一的targetId。后台模式的工作流程是:
- 通过CDP命令
Target.createTarget创建一个新的、隐藏的浏览器标签页(或直接使用一个后台标签页)。 - 获取这个新标签页的
targetId。 - 所有后续操作(导航、点击、截图)都通过CDP协议,精确发送到这个
targetId对应的标签页。 - 你的主窗口、你正在浏览的标签页,完全不受影响,因为CDP指令没有发送给它们。
这种基于协议的直接通信,跳过了图形界面,效率极高,也使得真正的并行操作成为可能。主Agent可以派生出多个子Agent,每个子Agent获得一个独立的targetId,操作自己专属的标签页,同时进行网页调研、数据提取等任务。
2.3 智能路由:五级通道与自动模式选择
不是所有任务都需要动用“浏览器”这个重型武器。Skill内置了一个智能调度层,我称之为“五级通道”。它会根据你的指令意图,自动选择最经济、最合适的工具链,优先级从高到低:
- WebSearch:如果只是简单的知识问答(如“特斯拉CEO是谁?”),直接调用搜索引擎API返回摘要,最快最省。
- WebFetch / Jina Reader:如果需要获取某个公开网页的文本内容,使用无头HTTP请求或专门的转码服务(如Jina AI的
jina.ai)将HTML转为干净的Markdown,避免启动浏览器。 - cURL / 原始HTTP:对于极其简单的页面或API接口,直接用最基础的HTTP客户端获取原始HTML。
- CDP 只读模式:当页面需要JavaScript渲染(如React单页应用),但又只需读取内容时,会启动一个轻量的CDP连接,执行脚本获取DOM状态,但不进行任何交互。
- CDP 完全控制模式:只有在前四级都无法满足时——即需要登录态、需要点击、填写、上传等交互操作时——才会启动完整的浏览器控制。
当你使用简单的/browse命令时,Skill会解析你的指令,自动判断该走哪条通道,是用前台模式还是后台模式。这种设计极大地提升了响应速度和资源利用效率。
3. 关键技术细节与实操要点
要让AI稳定、安全地操控浏览器,远不止发送点击指令那么简单。下面我拆解几个最关键的技术实现和实操中会遇到的问题。
3.1 元素定位:与现代前端框架共舞
现代网页大量使用React、Vue、Svelte等框架,DOM元素动态生成,传统的基于固定选择器(如#submit-btn)的定位方法非常脆弱。Skill在这方面做了大量适配。
核心策略:混合定位与等待策略
- 语义化属性优先:首先尝试通过
[data-testid]、[aria-label]、role等测试或无障碍属性定位。这些属性通常更稳定。 - 文本内容匹配:对于按钮、链接,使用XPath的
text()函数或CSS的:contains伪类(通过JavaScript实现)进行文本匹配。例如,找“提交”按钮。 - 视觉与坐标辅助:在复杂场景下,会结合元素的屏幕坐标和视觉特征(通过截图分析)进行辅助定位。
- 显式等待:在执行任何操作前,会注入等待逻辑,确保目标元素已经稳定地存在于DOM中并且处于可交互状态(如
element.isConnected && element.offsetParent)。
一个实际的代码片段示例(CDP Helper中):
async def wait_and_click(self, selector, max_wait=10): """等待元素出现并点击""" start_time = time.time() while time.time() - start_time < max_wait: try: # 通过CDP执行JavaScript来查找和等待元素 result = await self.client.send('Runtime.evaluate', { 'expression': f''' (function() {{ const el = document.querySelector({json.dumps(selector)}); if (el && el.offsetParent) {{ el.click(); return true; }} return false; }})() ''', 'awaitPromise': True }) if result.get('result', {}).get('value'): return True except Exception: pass await asyncio.sleep(0.5) raise ElementNotFoundError(f"Element {selector} not clickable after {max_wait}s")3.2 文件上传:绕过系统对话框的魔法
网页文件上传(<input type=“file”>)通常会触发操作系统的文件选择对话框,这是自动化的一大障碍。Skill通过CDP的Page.setFileInputFiles命令完美解决了这个问题。
操作原理:
- 首先定位到文件输入框元素。
- 通过CDP协议,直接将该元素的内部文件列表设置为指定的本地文件路径。
- 浏览器会认为用户已经通过对话框选择了文件,从而触发
onChange等事件。
关键实现:
async def upload_file(self, input_selector, file_path): """直接向文件输入框设置文件路径""" # 1. 先定位到元素,获取其backendNodeId node_info = await self.client.send('DOM.describeNode', { 'selector': input_selector }) backend_node_id = node_info['node']['backendNodeId'] # 2. 使用setFileInputFiles命令 await self.client.send('DOM.setFileInputFiles', { 'files': [os.path.abspath(file_path)], # 必须传绝对路径 'backendNodeId': backend_node_id })实操心得:这里必须传入文件的绝对路径。相对路径或
~/开头的路径浏览器无法识别。在实际集成时,需要先对用户传入的路径进行解析和标准化。
3.3 富文本编辑器操作:征服contenteditable
Notion、语雀、Gmail的编辑器都不是普通的<textarea>,而是contenteditable的<div>。向里面输入文字,需要模拟完整的光标定位、选区设置和输入事件。
策略:Skill放弃了模拟键盘事件(容易出错),直接使用CDP的Input.insertText命令。
async def insert_text_to_editor(self, editor_selector, text): """向富文本编辑器插入文字""" # 1. 聚焦到编辑器 await self.client.send('Runtime.evaluate', { 'expression': f'document.querySelector({json.dumps(editor_selector)}).focus()' }) # 2. 直接插入文本,CDP底层会处理光标和选区 await self.client.send('Input.insertText', { 'text': text })这种方法稳定可靠,因为它绕过了浏览器的事件系统,直接修改了编辑器的内容状态。
3.4 状态同步与错误恢复
浏览器自动化最怕的就是状态不同步。比如AI点击了一个按钮,以为页面跳转了,但实际因为网络延迟没跳,后续操作就会全部失败。
Skill的应对机制:
- 操作后状态验证:每个重要操作(如点击导航、提交表单)后,都会等待并检查页面状态是否如预期变化(如URL改变、特定元素出现)。
- 心跳与超时:长时间任务会定期通过CDP发送
Runtime.evaluate执行简单脚本(如1+1)来检测连接和页面是否存活。 - 异常捕获与重试:定义了分级错误(网络超时、元素未找到、脚本执行错误),并针对可重试错误(如元素未找到)设计了指数退避的重试逻辑。
- 上下文快照:在关键步骤前,会通过截图和关键DOM序列化,保存页面快照。如果后续步骤失败,可以尝试恢复到上一个已知状态点重新执行。
4. 安全边界与隐私保护设计
让AI操作你已登录的浏览器,安全是头等大事。Skill设计了三层防护,不是简单地“信任AI”,而是给它划定了清晰的行动边界。
4.1 第一层:域名级黑名单(硬拦截)
这是最外层的防护。Skill内置了一个敏感域名列表,并支持用户自定义扩展。当AI被要求访问这些域名时,会自动降级为“只读模式”。
默认黑名单类别:
- 金融支付:
*.bank.com,*.paypal.com,alipay.com,*.chase.com - 核心认证:
accounts.google.com,login.microsoftonline.com,*.okta.com - 云服务控制台:
console.aws.amazon.com,cloud.google.com,portal.azure.com - 企业内部敏感系统:可由用户通过配置文件
~/.browser-control/blocked_domains.txt添加。
当检测到目标URL在黑名单内,Skill会拒绝执行任何写操作(点击、填写、上传),仅允许截图和读取页面文本内容,并且会在返回结果中明确提示“该页面因安全策略已被限制为只读模式”。
4.2 第二层:元素级意图识别(软防护)
即使不在黑名单的页面上,某些敏感操作也需要被禁止。Skill会对AI解析出的操作指令进行元素级过滤。
过滤规则示例:
- 密码字段:任何
type=“password”的<input>元素,AI发出的fill命令会被自动忽略。 - 支付/购买按钮:通过元素文本、
aria-label、># 创建skills目录(如果不存在) mkdir -p ~/.claude/skills # 克隆项目 git clone https://github.com/d-wwei/browser-control-skill.git ~/.claude/skills/browser-control这会将所有核心代码、模块和适配器下载到本地。
步骤二:创建命令软链接Claude Code通过
~/.claude/skills/目录下的文件夹名来识别技能。我们需要创建一个软链接,让/browse命令指向正确的技能入口。# 进入技能目录 cd ~/.claude/skills # 创建指向browser-control技能内browse子技能的软链接 ln -sf browser-control/skills/browse browse现在,Claude Code就能识别
/browse命令了。步骤三:配置Chrome远程调试这是后台模式(CDP)能工作的前提。我们需要让Chrome允许外部连接。
- 完全关闭所有Chrome窗口。
- 通过命令行启动Chrome,并开启远程调试端口:
# macOS/Linux /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 # Windows (假设安装在默认路径) “C:\Program Files\Google\Chrome\Application\chrome.exe” --remote-debugging-port=9222 - 保持这个命令行窗口打开,不要关闭。此时访问
http://localhost:9222/json/version,如果能看到一串JSON信息,说明调试端口已成功开启。
步骤四:macOS额外权限(仅前台模式需要)如果你要使用AppleScript前台模式,需要授予终端或你的IDE(如VS Code)控制Chrome的权限。
- 打开系统设置 > 隐私与安全性 > 辅助功能。
- 点击左下角锁图标解锁。
- 将你使用的终端应用(如Terminal、iTerm2)或代码编辑器(如Visual Studio Code)添加到允许列表中。
步骤五:验证安装打开Claude Code,尝试一个简单命令:
/browse here 告诉我当前浏览器页面的标题是什么?如果一切正常,Claude会调用Skill,通过AppleScript获取你当前激活的Chrome标签页标题并返回。如果使用后台模式:
/browse bg 打开 https://httpbin.org/get 并返回页面内容这会通过CDP在后台打开一个标签页,获取内容后关闭。
5.2 其他AI平台适配要点
Skill的架构是平台无关的,核心逻辑在
skills/browser-control/目录下。对于其他平台,你需要的是“适配层”,即告诉平台如何调用这些核心能力。- OpenAI Codex / ChatGPT自定义GPT:将
adapters/codex/AGENTS.md文件的内容,作为你的Agent系统提示词(System Prompt)的一部分。这个文件定义了工具(Tools)的调用规范、输入输出格式以及使用示例。 - Cursor / Windsurf:这两个编辑器内置了AI Agent功能。将
adapters/cursor/.cursorrules或adapters/windsurf/.windsurfrules文件复制到你的项目根目录。这些规则文件会指导编辑器内的AI如何调用本地的browse命令脚本。 - Google Gemini CLI:如果你通过命令行与Gemini交互,可以参考
adapters/gemini/GEMINI.md中的说明,配置函数调用(Function Calling)来触发本地脚本。
共通的核心:无论哪个平台,最终都是调用
skills/browser-control/scripts/browse-cmd.sh这个统一的命令行入口。适配器的工作就是把你平台上的自然语言指令,转换成这个脚本能理解的参数。5.3 依赖管理与环境检测
Skill通过
scripts/check-deps.sh脚本自动检测环境。#!/bin/bash # 检查Node.js if ! command -v node &> /dev/null; then echo “错误:未检测到Node.js。后台模式需要Node.js 22+。” >&2 exit 1 fi # 检查Python3 if ! command -v python3 &> /dev/null; then echo “警告:未检测到Python3。部分高级写操作(如文件上传)将不可用。” >&2 fi # 检查Chrome调试端口 if ! curl -s http://localhost:9222/json/version > /dev/null; then echo “正在尝试启动Chrome调试代理...” # 尝试启动内置的代理脚本 node scripts/cdp-proxy.mjs --silent & fi首次运行
/browse命令时,这个脚本会自动执行。如果缺少关键依赖(如Node.js),它会明确报错。如果Chrome调试端口未开启,它会尝试自动启动一个轻量代理(但更推荐手动按步骤三的方式启动Chrome,更稳定)。6. 高级使用场景与排坑实录
掌握了基础部署,我们来看看一些更复杂的用法和实际使用中必然会遇到的“坑”。
6.1 场景:自动化跨平台数据收集与录入
假设你是一名市场分析师,每天需要从10个不同的新闻网站和行业博客收集信息,然后整理到Notion的数据库里。
传统做法:手动一个个打开网页,复制粘贴,枯燥且易错。使用Browser Control Skill:
/browse bg 执行以下任务: 1. 并行打开以下10个网址:[列表] 2. 从每个页面中,提取文章标题、发布时间、摘要和原文链接。 3. 打开我们的Notion市场情报数据库页面(https://notion.so/...)。 4. 为每篇文章在数据库中创建新的一行,并填入提取的字段。背后发生了什么:
- 主Agent解析指令,识别出这是典型的后台并行任务。
- 它派生出10个子Agent,每个子Agent通过CDP获得一个独立的
targetId,各自打开一个标签页去访问指定的网址。 - 每个子Agent使用预定义的或AI实时分析的选择器,从页面中抓取所需的结构化数据。
- 所有子Agent完成任务后,数据汇总给主Agent。
- 主Agent切换到前台模式(因为你已经登录了Notion),导航到指定的Notion页面,并开始操作数据库视图,一行行地创建和填写内容。
避坑技巧:对于Notion、Airtable这类复杂SPA,直接操作DOM可能不稳定。更好的做法是,让AI先通过CDP获取页面状态,然后通过模拟键盘快捷键(如
Cmd/Ctrl + N新建行,Tab切换字段)来操作,这更接近真人操作,成功率更高。Skill的站点经验记忆功能,会逐渐学习并记住这些特定站点的最佳操作策略。6.2 场景:处理动态加载与验证码
现代网页大量使用异步加载和反爬机制。
问题一:元素还没加载出来AI执行
click(‘.load-more-btn’)时,按钮可能还没被JavaScript渲染到页面上。解决方案:Skill中的所有交互操作都内置了显式等待。上面的点击命令在实际执行时,会被包装成“等待选择器.load-more-btn出现且可点击,最多等10秒,然后点击”。这需要在CDP Helper或AppleScript脚本中实现轮询检查逻辑。问题二:遇到Cloudflare Turnstile或hCaptcha验证码这是自动化工具的终极挑战。目前,任何自动化方案都无法可靠地绕过真正的验证码。应对策略:
- 识别与规避:Skill在访问页面时会检查是否有常见的验证码框架元素出现。如果检测到,会立即停止操作,并向用户报告:“页面出现验证码,需要人工干预”。
- 会话保持:有时验证码只在首次访问或特定操作后出现。Skill会尽量复用已有的浏览器会话(Cookie、LocalStorage),避免触发验证。
- 人工介入点:设计流程时,将可能触发验证码的操作(如频繁搜索、提交表单)作为可选的“断点”。AI可以执行验证码之前的所有步骤,然后停下来告诉你:“表单已填好,请手动完成验证码并点击提交”。
6.3 常见问题排查表
问题现象 可能原因 解决方案 执行 /browse无反应或报“技能未找到”1. 软链接未创建或创建不正确。
2. Claude Code技能目录路径不对。1. 检查 ~/.claude/skills/browse是否为指向browser-control/skills/browse的有效软链接。
2. 确认Claude Code的技能加载路径。可以尝试在Claude Code中直接输入!ls skills查看。后台模式命令超时或失败 1. Chrome未以调试模式启动。
2. 调试端口被占用或代理未运行。
3. 网络代理导致CDP连接失败。1. 确保已按步骤三启动Chrome。
2. 检查http://localhost:9222/json/version是否可访问。
3. 尝试关闭系统代理或VPN。运行scripts/check-deps.sh查看详细错误。前台模式报“应用程序未获得辅助功能权限” macOS的辅助功能权限未授予。 前往系统设置 > 隐私与安全性 > 辅助功能,将你使用的终端或IDE添加到允许列表。需要重启应用。 AI点击或填表不生效 1. 页面是React/Vue等框架渲染,元素选择器失效。
2. 操作速度太快,页面未响应。
3. 元素被遮挡或不可见。1. 尝试使用更稳定的选择器,如 [data-testid]。让AI先执行截图并标注命令,查看它识别出的元素ID。
2. 在指令中明确加入等待,如“等待2秒后点击提交”。
3. 使用scroll_into_view等命令确保元素在视口中。文件上传失败 1. 文件路径错误(相对路径或 ~)。
2. 目标<input>元素不是type=“file”。
3. 网站使用了自定义上传组件。1.始终使用绝对路径。可以在指令中写“上传 /Users/name/Desktop/file.pdf”。
2. 让AI先检查元素属性。
3. 对于自定义组件,尝试点击其触发元素(如“选择文件”按钮)来激活系统对话框,但这在前台模式下会卡住,后台模式无效。这是此类组件的限制。并行任务卡住或混乱 多个子Agent竞争同一资源(如Cookie),或某个子Agent任务失败未释放标签页。 1. 确保任务之间相对独立。避免让多个Agent操作同一域名下的敏感会话。
2. Skill有超时和清理机制,但极端情况下可能需要手动关闭Chrome的调试窗口并重启。6.4 性能优化与最佳实践
- 按需加载模块:Skill的
modules/目录下的功能模块是懒加载的。只有当你用到截图功能时,才会加载截图模块。这保证了技能的启动速度。 - 连接池与复用:对于频繁的CDP操作,Skill内部维护了一个轻量的连接池,避免为每个命令都建立新的WebSocket连接,大幅提升后台并行任务的效率。
- 指令尽可能具体:给AI的指令越模糊,它需要分析和尝试的次数就越多,越容易出错。对比:
- 差:
/browse here 填一下这个表。 - 好:
/browse here 在当前页面的表单中,找到‘姓名’输入框(可能带有name=‘username’或placeholder=‘请输入姓名’),填入‘张三’;找到‘邮箱’输入框,填入‘zhangsan@example.com’;然后点击绿色的‘提交’按钮。
- 差:
- 善用站点经验:Skill会学习。如果你经常操作某个网站(如公司内部的CRM),可以将成功的操作模式(如稳定有效的选择器、必要的等待时间)手动添加到
references/site-patterns/下的自定义配置文件中,下次AI操作同一网站时会优先采用,成功率会显著提升。
这个项目的精髓在于,它不追求全自动的“黑盒”魔法,而是提供了一个强大、可控的“工具箱”,将浏览器的操控权以一种安全、可理解的方式交给了AI。你和AI的关系,从“它说,你做”变成了“你说,它做,你监督”。这种协作模式,才是当下人机协同最务实、也最有潜力的方向。
