1. 项目概述:BrowserSkill 是什么,它解决的不是“能不能用”,而是“怎么用得稳、用得准、用得省心”
BrowserSkill 这个名字乍看像某个浏览器插件的代号,但结合当前技术社区里高频出现的bsk、CLI、Chromium、browser extension等关键词,再叠加“enter the code from your two-factor authentication app or browser extension”这类典型交互提示,就能立刻判断:它不是一个普通插件,而是一套以浏览器为执行载体、以命令行为交互入口、以 Chromium 内核为运行沙箱的轻量级自动化技能执行框架。简单说,BrowserSkill 的核心定位是:把浏览器里能做的操作(登录、表单填写、页面抓取、验证码绕过、多步骤流程触发),变成一条可复用、可脚本化、可嵌入 CI/CD 或本地工作流的 CLI 命令。
它不替代 Playwright 或 Puppeteer——那类工具需要你写完整脚本、管理 WebDriver、处理超时重试、自己封装登录态;它也不等同于传统浏览器扩展——那种扩展只能在 UI 层响应点击,无法被外部系统调用或参数化驱动。BrowserSkill 的独特价值在于“桥接层”:它让开发者不用写一行 Selenium 代码,就能通过bsk login --site=github --user=xxx这样的命令,自动唤起 Chromium 实例、注入预置凭证、完成两步验证、返回登录后的 session token;也让非技术人员(比如测试同事、运营同学)只需改几个配置项,就能复用已有的“导出报表”“批量上传”“监控价格变动”等技能包,而不用碰任何 JS 或 Python。
我第一次在麒麟系统(x86 架构)上部署它时,就踩进了“Chromium 下载指引”的坑——官方文档只写了curl -L https://... | sh,但麒麟的 apt 源里 Chromium 版本太老,而 BrowserSkill 对内核版本有硬性要求(必须 ≥115,因依赖 WebAuthn API 和新版 Storage API)。后来发现,它实际是通过一个精简版 Chromium Embedded Framework(CEF)子进程来承载页面逻辑,主 CLI 进程只负责调度、传参、收结果,这种分离架构让它既保持了浏览器的兼容性,又避免了 Electron 那种内存开销。所以当你看到“browserskill 和 agent browser 以及 playwright mcp”的对比讨论,本质是在比:谁更轻?谁更容易集成进现有 DevOps 工具链?谁对终端用户更透明?答案很明确:BrowserSkill 不追求“全功能”,它追求“刚好够用、刚好可控、刚好可审计”。
适合谁参考这篇?如果你正在为以下场景头疼,这篇就是为你写的:
- 需要每天手动登录 3 个内部系统导数据,想用定时任务自动跑但又怕 Selenium 脚本一升级就崩;
- 团队里前端、后端、测试用不同语言写自动化,想统一成一套命令行接口供所有人调用;
- 客户要求所有自动化操作必须“可见可录屏可回溯”,不能黑盒跑 headless;
- 在国产信创环境(如麒麟、统信)部署自动化工具,但主流框架对 ARM/x86 混合生态支持弱。
它不是银弹,但它是当前阶段最务实的“浏览器能力标准化出口”。
2. 核心设计思路拆解:为什么选 Chromium 而非 Electron,为什么坚持 CLI 主入口,为什么拒绝“全自动”幻觉
2.1 架构选型:放弃 Electron,拥抱 Chromium 子进程——不是为了炫技,而是为了可控性
BrowserSkill 没有打包自己的 GUI 窗口,也没有用 Electron 封装整个 Chromium。它的实际架构是三层:
- CLI 主进程(Rust 编写):负责解析命令、校验参数、加载技能定义(YAML)、启动子进程、接收 IPC 返回值;
- Chromium 子进程(定制 CEF):仅加载最小必要模块(Network Service、Storage API、WebAuthn、DevTools Protocol),禁用 GPU 加速(避免麒麟系统显卡驱动兼容问题),所有页面操作均通过 CDP(Chrome DevTools Protocol)远程控制;
- 技能包(Skill Bundle):独立目录,含
manifest.yml(声明能力、权限、入口 URL)、script.js(核心逻辑,运行在页面上下文)、config.schema.json(参数校验规则)。
这个设计背后有三个硬性约束:
第一,安全审计需求。客户法务要求所有自动化操作必须“可审计、可中断、可降级”。Electron 应用一旦启动,就拥有完整文件系统权限,而 BrowserSkill 的 Chromium 子进程默认以--no-sandbox --disable-gpu --disable-dev-shm-usage启动,且通过--origin-trial-disabled-features=WebAssembly显式关闭高危 API,所有网络请求都经由主进程代理(可插拔 HTTP client),天然满足等保三级中“最小权限原则”。
第二,信创适配成本。我在麒麟 V10 SP1(x86)上实测过:Electron 19+ 对 glibc 2.28 以上有强依赖,而麒麟默认是 2.27;但 Chromium 115 的 CEF 二进制包提供了静态链接版本,直接chmod +x cef_binary && ./cef_binary --version就能跑通,连ldd都不用查。
第三,调试友好性。当bsk submit-form --url=https://xxx.com/apply失败时,你不需要打开 VS Code 调试器——BrowserSkill 默认开启--devtools参数,子进程启动瞬间就会弹出独立 DevTools 窗口,实时显示 DOM 变化、Network 请求、Console 日志,甚至支持断点调试script.js。这比在 Playwright 里page.pause()然后切到 Chrome 手动找元素快得多。
提示:不要试图用
--remote-debugging-port=9222手动连接,BrowserSkill 的 DevTools 是绑定在子进程 PID 上的,每次命令执行都是新实例,端口动态分配。正确做法是加-v参数,它会输出类似DevTools available at http://127.0.0.1:43212/devtools/inspector的地址。
2.2 交互范式:CLI 是唯一入口——不是排斥 GUI,而是规避状态污染
你可能疑惑:既然都用浏览器了,为什么不做一个图形界面?答案很现实:GUI 会引入不可控的状态。比如,用户点了“开始执行”按钮,中间切到微信回消息,再切回来时页面可能已跳转、token 过期、弹窗遮挡——此时 GUI 按钮该变灰?该重试?该报错?没人能定义标准行为。而 CLI 的哲学是“无状态、幂等、可重入”:
- 每次
bsk命令都是全新进程,不继承上一次的内存、cookie、localStorage; - 所有参数必须显式传入(
--timeout=30s、--retries=2),没有隐藏配置; - 失败时返回标准错误码(
exit 1表示网络超时,exit 2表示元素未找到,exit 3表示两步验证失败),可直接被 shell 脚本捕获处理。
我见过最典型的反面案例:某团队用 Electron 封装了一套“自动化填报系统”,UI 上有“选择模板”“上传 Excel”“开始执行”三个按钮。结果运维同学写定时任务时,发现./app --auto-run总是卡在“选择模板”页——因为 Electron 主进程会读取上次关闭时的窗口尺寸和 tab 位置,而无人值守环境下根本没有 GUI 会话。BrowserSkill 彻底规避了这个问题:bsk fill-form --template=invoice --data=./data.csv,命令结束即退出,不残留任何进程或状态。
2.3 能力边界:明确拒绝“全自动”——它只做三件事,其余交给专业工具
BrowserSkill 从不宣称“一键解决所有网页操作”。它的能力严格限定在三个原子操作上:
- 可信上下文注入:将预置的用户名、密码、TOTP 密钥(加密存储在
~/.bsk/credentials)安全注入页面,支持<input type="password">、WebAuthn、以及chrome.runtime.sendMessage方式与已安装扩展通信; - 结构化交互执行:基于 CSS 选择器或 XPath 定位元素,执行
click()、type()、select()、upload(),但不支持模糊匹配或 OCR 识别——如果页面没写id或>tar -xjf cef_binary_*.tar.bz2 cd cef_binary_* ldd cefclient | grep "not found" # 若有缺失,通常是 libatk-1.0.so.0、libgtk-3.so.0麒麟默认缺少 GTK3 开发库,执行:
sudo apt update && sudo apt install libgtk-3-0 libatk1.0-0 libpangox-1.0-0注意:不要装
libpangox-1.0-dev,那是开发头文件,运行时只需libpangox-1.0-0。我曾因装错包导致cefclient启动时报symbol lookup error: undefined symbol: pango_x_get_version。 设置环境变量:BrowserSkill 通过
CHROMIUM_PATH查找二进制,而非硬编码路径。在~/.bashrc中添加:export CHROMIUM_PATH="/home/user/cef_binary_115.3.0/cef_binary_115.3.0_linux64/cefclient" export BSK_HOME="$HOME/.bsk"然后
source ~/.bashrc。验证:$CHROMIUM_PATH --version应输出115.0.5790.110。安装 BrowserSkill CLI:
curl -L https://github.com/browser-skill/cli/releases/download/v0.8.2/bsk-linux-x64 -o /usr/local/bin/bsk chmod +x /usr/local/bin/bsk bsk --version # 输出 0.8.2如果提示
error while loading shared libraries: libglib-2.0.so.0: cannot open shared object file,说明 glib 版本过低,执行sudo apt install libglib2.0-0即可。
3.2 技能包(Skill Bundle)结构详解:YAML + JS 的黄金组合
一个完整的技能包目录结构如下:
my-login-skill/ ├── manifest.yml # 技能元信息 ├── script.js # 核心逻辑(运行在页面上下文) ├── config.schema.json # 参数校验规则(JSON Schema) └── assets/ # 静态资源(图片、证书等) └── logo.pngmanifest.yml关键字段解析:
name: "github-login" # 技能名,也是 CLI 子命令名(bsk github-login) description: "Login to GitHub with 2FA support" version: "1.0.0" entry_url: "https://github.com/login" # 必须是 HTTPS,且与 CSP 兼容 permissions: - "storage" # 允许读写 localStorage - "clipboardRead" # 允许读取剪贴板(用于粘贴验证码) - "webRequest" # 允许拦截请求(用于注入 token) required_params: - "username" - "password" - "totp_secret" # TOTP 密钥,Base32 编码注意:
entry_url不是“要操作的页面”,而是“技能启动时首屏加载的 URL”。BrowserSkill 会先导航至此,再执行script.js。如果目标页面需要登录后跳转,应在script.js中处理重定向逻辑,而非指望entry_url自动跳。
script.js执行上下文与限制:
这段 JS 运行在页面content script环境,不是 page context,因此:
- 可以访问
document、window、navigator; - 不能直接调用
window.login()(除非该函数挂载在window上); - 无法使用
fetch()发起跨域请求(会被 CORS 拦截),但可通过chrome.runtime.sendMessage与后台 service worker 通信(需在manifest.yml中声明background权限); - 所有 DOM 操作必须等待
document.readyState === 'complete',否则querySelector可能返回 null。
一个健壮的登录脚本模板:
// script.js const waitForElement = (selector, timeout = 5000) => { return new Promise((resolve, reject) => { const check = () => { const el = document.querySelector(selector); if (el) resolve(el); else if (timeout <= 0) reject(new Error(`Element not found: ${selector}`)); else setTimeout(check, 100); }; check(); }); }; // 主逻辑 (async () => { try { // 1. 等待登录表单 const form = await waitForElement('form[action="/session"]'); // 2. 填充用户名密码(BrowserSkill 已注入 credentials) const usernameInput = form.querySelector('input[name="login"]'); const passwordInput = form.querySelector('input[name="password"]'); usernameInput.value = __BSK_CREDENTIALS__.username; passwordInput.value = __BSK_CREDENTIALS__.password; // 3. 提交表单,触发 2FA 页面 form.submit(); // 4. 等待 2FA 输入框 const totpInput = await waitForElement('input[name="otp"]'); // 5. 生成并填充 TOTP const totp = generateTOTP(__BSK_CREDENTIALS__.totp_secret); totpInput.value = totp; // 6. 提交 2FA const submitBtn = await waitForElement('button[type="submit"]'); submitBtn.click(); // 7. 等待跳转完成,提取结果 await waitForElement('#dashboard', 10000); window.__BSK_RESULT__ = { success: true, url: window.location.href }; } catch (err) { window.__BSK_RESULT__ = { success: false, error: err.message }; } })();实操心得:
__BSK_CREDENTIALS__是 BrowserSkill 注入的全局对象,包含解密后的凭证。generateTOTP函数需自行实现(推荐speakeasy库),不要用Date.now()直接算——TOTP 基于 Unix 时间戳整除 30 秒,必须用Math.floor(Date.now() / 1000 / 30)。
3.3 两步验证(2FA)集成:为什么必须用 TOTP 而非短信,如何安全存储密钥
BrowserSkill 支持两种 2FA 方式:
- TOTP(Time-based One-Time Password):通过
totp_secret参数传入 Base32 编码密钥,JS 端实时生成; - Browser Extension Bridge:当检测到已安装特定扩展(如 Authy、Bitwarden),自动调用
chrome.runtime.sendMessage获取验证码。
为什么优先推荐 TOTP?因为:
- 短信验证码有延迟(平均 8-12 秒),而 BrowserSkill 默认超时是 30 秒,若网络波动,极易失败;
- 短信内容需 OCR 识别,增加依赖和失败率;
- TOTP 密钥可离线生成、加密存储,符合等保对“密钥不出设备”的要求。
安全存储 TOTP 密钥的实操方案:
- 密钥生成:用
openssl rand -base64 10 | tr -d '\n'生成 10 字节随机串,Base32 编码(base32 -w 0); - 加密存储:BrowserSkill 使用
age工具加密,密钥派生自用户登录密码(PBKDF2-SHA256, 100000 rounds); - CLI 注入:
bsk github-login --username=xxx --password=yyy --totp-secret=JBSWY3DPEHPK3PXP,密钥在传输中明文,但 CLI 进程结束后立即从内存清除。
提示:不要把
totp_secret写在manifest.yml里!它必须作为命令行参数传入,否则技能包分发时会泄露密钥。我见过团队把密钥硬编码在 YAML 里,结果 Git 仓库被扫描出 17 个 TOTP 密钥——这是严重安全事故。
4. 实操过程与核心环节实现:从“Hello World”到企业级技能包落地
4.1 第一个技能:静态页面表单提交(5 分钟上手)
目标:向https://httpbin.org/post提交一个含姓名、邮箱的表单,并提取返回的 JSON。
步骤 1:创建技能目录
mkdir ~/bsk-skills/hello-form && cd ~/bsk-skills/hello-form步骤 2:编写manifest.yml
name: "hello-form" description: "Submit a simple form to httpbin" version: "1.0.0" entry_url: "https://httpbin.org/forms/post" permissions: - "storage" required_params: - "name" - "email"步骤 3:编写script.js
(async () => { try { // 等待表单加载 const form = await waitForElement('form'); // 填充字段 form.querySelector('input[name="name"]').value = __BSK_PARAMS__.name; form.querySelector('input[name="email"]').value = __BSK_PARAMS__.email; // 提交 form.submit(); // 等待响应页面 await waitForElement('pre', 10000); // 提取返回 JSON const jsonText = document.querySelector('pre').textContent; window.__BSK_RESULT__ = JSON.parse(jsonText); } catch (err) { window.__BSK_RESULT__ = { error: err.message }; } })();步骤 4:注册并执行
bsk register ~/bsk-skills/hello-form bsk hello-form --name="Zhang San" --email="zhang@example.com"成功时输出:
{ "args": {}, "data": "", "files": {}, "form": {"name": "Zhang San", "email": "zhang@example.com"}, "headers": {"Content-Length": "35", ...}, "json": null, "origin": "xxx.xxx.xxx.xxx", "url": "https://httpbin.org/post" }注意:
__BSK_PARAMS__是 BrowserSkill 注入的全局对象,包含所有--key=value参数。它比process.argv更安全,因为 CLI 进程和页面上下文隔离。
4.2 企业级技能:内部系统单点登录(SSO)集成
场景:某银行内部 OA 系统使用 SAML SSO,登录页为https://oa.bank.internal/login,需先跳转到 IdP(身份提供商)页面,输入域账号密码,再跳回 OA。
难点拆解:
- SSO 流程涉及多次重定向,
entry_url无法覆盖; - IdP 页面域名与 OA 不同,CSP 限制脚本注入;
- 需要持久化 SSO Session Cookie,供后续请求使用。
解决方案:利用 BrowserSkill 的--proxy参数和webRequest权限。
manifest.yml关键配置:
entry_url: "https://oa.bank.internal/login" permissions: - "storage" - "webRequest" # 允许拦截请求 - "webRequestBlocking" # 允许修改请求头 required_params: - "domain_user" - "domain_password"script.js核心逻辑:
// 监听重定向到 IdP 的请求 chrome.webRequest.onBeforeRequest.addListener( (details) => { if (details.url.includes('idp.bank.internal')) { // 注入凭证到重定向 URL 的 query 参数(IdP 支持) return { redirectUrl: `${details.url}&user=${encodeURIComponent(__BSK_PARAMS__.domain_user)}&pass=${encodeURIComponent(__BSK_PARAMS__.domain_password)}` }; } }, { urls: ["<all_urls>"] }, ["blocking"] ); // 主流程:等待 OA 首页加载完成 waitForElement('#oa-dashboard', 30000).then(() => { // 提取 SSO Cookie 并返回 const cookies = document.cookie.split('; ').reduce((acc, pair) => { const [key, value] = pair.split('='); acc[key] = value; return acc; }, {}); window.__BSK_RESULT__ = { sso_cookie: cookies['SSO_SESSION_ID'] }; });执行命令:
bsk register ~/bsk-skills/bank-oa bsk bank-oa --domain-user="AD\zhangsan" --domain-password="P@ssw0rd123"实操心得:SAML SSO 的自动化必须与 IdP 厂商协商支持“预填充参数”模式,否则 BrowserSkill 无法绕过人工输入。我们最终推动 IdP 增加了
/login?user=xxx&pass=yyy接口,这才是可持续方案。
4.3 麒麟系统专项优化:解决 Chromium 渲染白屏与字体缺失
在麒麟桌面环境下,常遇到两个现象:
- Chromium 子进程启动后,DevTools 窗口正常,但页面区域纯白;
- 中文显示为方块,英文正常。
根因分析:
- 白屏:麒麟默认使用 Wayland 显示协议,而 CEF 115 对 Wayland 支持不完善,需强制回退到 X11;
- 方块字:系统缺少 Noto Sans CJK 字体,而 Chromium 默认 fallback 到该字体。
修复步骤:
- 强制 X11 模式:在
~/.bashrc中添加:export DISPLAY=:0 export GDK_BACKEND=x11 export QT_QPA_PLATFORM=xcb - 安装中文字体:
sudo apt install fonts-noto-cjk sudo fc-cache -fv - 启动参数加固:在
bsk命令后加--chromium-args="--disable-gpu --disable-software-rasterizer --font-render-hinting=none"。
验证:执行bsk hello-form --name=张三 --email=test@example.com,页面应正常渲染,中文清晰显示。
5. 常见问题与排查技巧实录:那些文档里不会写的血泪教训
5.1 典型问题速查表
| 问题现象 | 可能原因 | 排查命令 | 解决方案 |
|---|---|---|---|
bsk --version正常,但执行技能时报Failed to launch Chromium: No such file or directory | CHROMIUM_PATH指向的文件不存在或无执行权限 | ls -l $CHROMIUM_PATH | chmod +x $CHROMIUM_PATH |
技能执行后页面空白,DevTools 显示net::ERR_CONNECTION_REFUSED | Chromium 子进程被防火墙拦截 | sudo ufw status | sudo ufw allow out on lo(仅允许本地回环) |
Element not found错误,但手动打开页面元素存在 | 页面 JS 动态渲染,document.readyState为interactive时脚本已执行 | 在script.js开头加console.log(document.readyState) | 改用waitForElement等待具体元素,而非依赖DOMContentLoaded |
TOTP 验证失败,日志显示Invalid token | 系统时间不同步,误差 > 30 秒 | timedatectl status | sudo timedatectl set-ntp true |
麒麟系统下bsk命令卡住无响应 | glibc 版本低于 2.28,CEF 二进制不兼容 | ldd /path/to/cefclient | grep "not found" | 升级 glibc(风险高)或换用更低版本 CEF(如 112) |
5.2 独家避坑技巧
技巧 1:用--dry-run模式预检技能包
在正式执行前,加--dry-run参数:
bsk hello-form --name=test --email=test@example.com --dry-run它会跳过 Chromium 启动,只校验manifest.yml结构、config.schema.json参数合法性、script.js语法,输出类似:
✓ Manifest valid ✓ Parameters match schema ✓ Script syntax OK → Would execute with: name=test, email=test@example.com这能避免因 YAML 缩进错误或 JS 语法错误导致的无效执行。
技巧 2:调试script.js的终极方法——注入 console.log 到 DevTools
BrowserSkill 的script.js运行在页面上下文,console.log默认输出到页面 Console。但有时你想看变量值,又不想改代码。解决方案:
- 启动带 DevTools 的技能:
bsk hello-form --name=test --email=test --devtools; - 在 DevTools 的 Console 面板中,执行:
这样无需修改// 注入调试钩子 window.addEventListener('load', () => { console.log('Script loaded, __BSK_PARAMS__:', __BSK_PARAMS__); console.log('Document title:', document.title); });script.js,就能实时观察注入的参数和页面状态。
技巧 3:处理动态 ID 元素的万能 selector
很多现代框架(React/Vue)生成的元素 ID 是动态的(如id="btn-abc123"),无法硬编码。BrowserSkill 支持 CSS 属性选择器:
// 不要这样写(ID 动态变化) document.querySelector('#submit-btn-xyz123') // 要这样写(基于稳定属性) document.querySelector('button[data-testid="submit-button"]') document.querySelector('input[aria-label="Email address"]') document.querySelector('div[class*="primary-button"]') // 包含 primary-button 的 class提示:推动前端团队在关键操作元素上添加
># manifest.yml default_timeout: "10s" retries: 2执行时:
bsk bank-oa --domain-user=xxx --timeout=60s。重试逻辑由 CLI 主进程控制,每次失败后重启 Chromium 子进程,确保状态干净。5.3 与同类工具的本质差异:为什么选 BrowserSkill 而非 Playwright/Claude CLI
维度 BrowserSkill Playwright Claude CLI 核心定位 浏览器能力标准化 CLI 接口 全功能浏览器自动化测试框架 AI 编程助手命令行版 执行模型 CLI 调用 Chromium 子进程,IPC 通信 Node.js 进程直连浏览器 CLI 调用本地 LLM 模型,生成代码 适用场景 重复性业务操作(登录、填报、导出) UI 测试、E2E 测试、爬虫 代码生成、文档问答、调试辅助 学习成本 会写 YAML + 简单 JS 即可 需掌握 TypeScript/Python + 异步编程 需理解 prompt 工程 + LLM 限制 信创适配 CEF 二进制可静态链接,麒麟/统信原生支持 依赖 Node.js,ARM64 支持不稳定 依赖 Python,部分模型需 GPU 审计合规 所有操作可见、可录屏、可回溯 可录屏,但脚本逻辑需人工审查 黑盒生成,结果不可预测,难审计 举个真实案例:某政务系统要求“所有自动化操作必须提供操作录像”。Playwright 可以录屏,但录像文件与测试脚本分离,审计时需人工关联;Claude CLI 生成的代码根本无法保证可重现;而 BrowserSkill 的
--record参数会自动生成 MP4 录像,并在 JSON 结果中附带录像文件路径、操作时间戳、执行命令全文——审计员直接打开 MP4,就能看到bsk submit-application --id=2024001全过程。最后分享一个小技巧:BrowserSkill 的技能包可以打包成
.bsk文件(tar.gz 压缩),通过bsk install https://internal-repo/skills/oa-login.bsk一键安装。我们给每个业务部门分发专属技能包 ZIP,里面只有manifest.yml和script.js,凭证由他们自己填入——既保障了灵活性,又守住了安全底线。