
API Capture & YAML Exporter 源码与实战系列 · 第二篇
上一篇我们聊了接口自动化的「最后一公里」:抓到的请求是静态快照,而自动化需要的是可重复执行的动态关系。 这一篇换个角度:为什么我不建议你继续用 Charles 和 F12 做「抓包转自动化」?
系列索引
-
第一篇:从「抓包两小时,写脚本一整天」说起:接口自动化的「最后一公里」痛点
-
第二篇:告别 F12 与 Charles:如何打造一款「懂业务」的浏览器原生抓包利器?(本篇)
-
第三篇:接口自动化的「灵魂」:如何让工具自动搞定上下游接口的参数关联?
-
第四篇:一键生成 YAML 用例:从「抓包」到「自动化执行」的无缝闭环落地
01 传统抓包工具的「阿喀琉斯之踵」
上一篇我们吐槽了接口自动化推行不下去的核心痛点------参数关联太痛苦。
很多同事会问:
「抓包直接用 Charles 或者浏览器 F12 不就行了吗?为什么要自己写个 Chrome 插件?」
作为在测试行业摸爬滚打十多年的「老兵」,我当然知道 Charles 的强大,也理解 F12 的便捷。但在「抓包转自动化」这个特定场景下,传统工具存在几个致命的弱点------而这些弱点,恰恰是这款插件要解决的突破口。
痛点一:代理与证书地狱

抓 HTTPS 必须配代理、下证书------这句话几乎是每个测试新人的「入学第一课」。
但现实往往比理论残酷:现代浏览器对证书校验越来越严格,企业内网经常有自己的根证书策略,App 端还有 SSL Pinning 保驾护航。
配置环境的过程,经常让新人崩溃,让老手皱眉。
痛点二:噪音太多,找核心 API 像大海捞针

你打开一个业务系统页面,F12 打开 Network 面板,瞬间成百上千个请求刷屏------JS、CSS、图片、埋点、第三方统计......
真正核心的业务 API 被淹没在噪音海洋里,找起来费时费力。
痛点三:脱离浏览器上下文

Charles 是独立于浏览器的代理工具,它不知道当前页面处于什么状态,也无法和 DOM 或 Storage 联动。
你抓完包导出的 .har 文件,还要手动清洗、格式化,才能导入 Postman 或 JMeter 使用。
所以我决定换一条路:利用 Chrome 的 Manifest V3 和 chrome.debugger API,打造一款长在浏览器侧边栏里的原生抓包利器。
它不需要配置代理,不需要安装证书,打开侧边栏就能抓------真正做到了「所见即所抓」。
02 架构总览:一个插件,三种角色
在深入代码之前,先看整体架构。它由四个核心文件组成,各司其职:
| 文件 | 角色 | 职责 | | --- | --- | --- | | manifest.json | 清单文件 | 声明权限、入口、配置页 | | background.js | 后台服务 | 核心抓包逻辑、Debugger 通信、Tab 生命周期管理 | | sidebar.html + sidebar.js | 侧边栏 UI | 请求列表、详情面板、YAML 导出 | | options.html + options.js | 配置页 | 多环境配置、正则测试工具 |
其中,background.js 是整个插件的「大脑」,负责与 Chrome 底层调试协议通信,完成请求的捕获和过滤。
侧边栏则是用户直接交互的界面,负责展示和管理抓到的数据。
接下来,逐行拆解 background.js 的核心逻辑,看看它是如何实现「精准、无侵入」的网络监听的。
03 核心揭秘:chrome.debugger 的网络监听魔法
打开 manifest.json,你会看到这样的权限声明:
bash
{
"manifest_version": 3,
"name": "API Capture & YAML Exporter",
"version": "1.0.0",
"permissions": [
"webRequest",
"storage",
"sidePanel",
"tabs",
"debugger",
"activeTab"
],
"host_permissions": ["<all_urls>"],
"background": {
"service_worker": "background.js"
},
"side_panel": {
"default_path": "sidebar.html"
},
"options_page": "options.html"
}
这里有两个关键声明值得注意:
第一,debugger 权限。 这是 Chrome DevTools Protocol(CDP)的入口。通过它,插件可以获得和 F12 几乎同等深度的网络观测能力------不仅能拿到请求头,还能拿到完整的请求体和响应体。
第二,sidePanel 权限。 这是 Manifest V3 引入的新 API,允许扩展在浏览器侧边栏中嵌入页面。相比传统弹窗(popup),侧边栏空间更大,更适合做复杂的数据展示和交互。
3.1 为什么不用 chrome.webRequest?

你可能会问:Chrome 不是提供了 chrome.webRequest 吗?为什么还要用更复杂的 debugger?
答案很简单:chrome.webRequest 拿不到响应体。
它只能在请求发送前拦截到请求头和部分请求体,对响应阶段的完整数据(尤其是 JSON 响应体)无能为力。而做接口自动化,响应体恰恰是断言和参数提取的核心数据来源。
chrome.debugger 则不同------它直接复用 DevTools 的底层能力,通过 Network.enable 开启监听,可以捕获完整的网络事件流:
Network.requestWillBeSentNetwork.responseReceivedNetwork.loadingFinished
配合 Network.getResponseBody,可以拿到任意请求的完整响应数据。
3.2 抓包核心流程:三步走

background.js 中 Debugger 事件监听的核心实现:
bash
chrome.debugger.onEvent.addListener(async (source, method, params) => {
if (!isCapturing) return;
const config = getCurrentConfig();
if (!config) return;
// 第一步:请求发送时,记录请求信息
if (method === 'Network.requestWillBeSent') {
const { requestId, request, timestamp } = params;
const url = request.url;
if (!isUrlMatchConfig(url, config)) return;
if (!isApiRequest(url)) return;
requestMap.set(requestId, {
requestId,
url,
method: request.method,
requestHeaders: request.headers || {},
requestBody: parseRequestBody(request),
timestamp,
status: null,
responseHeaders: {},
responseBody: null,
config: config.id
});
}
// 第二步:响应返回时,记录状态码和响应头
if (method === 'Network.responseReceived') {
const { requestId, response } = params;
const entry = requestMap.get(requestId);
if (!entry) return;
entry.status = response.status;
entry.responseHeaders = response.headers || {};
entry.mimeType = response.mimeType;
}
// 第三步:请求完成时,获取响应体并持久化
if (method === 'Network.loadingFinished') {
const { requestId } = params;
const entry = requestMap.get(requestId);
if (!entry) return;
try {
const resp = await chrome.debugger.sendCommand(
{ tabId: source.tabId },
'Network.getResponseBody',
{ requestId }
);
entry.responseBody = tryParseJson(resp.body);
} catch {
entry.responseBody = null;
}
capturedRequests.push({ ...entry });
requestMap.delete(requestId);
broadcastUpdate();
}
});
这段逻辑可以概括为「请求记录 → 响应标记 → 完成归档」三步走:
-
请求发送时
:拿到 URL、方法、Headers、Body,先过两道过滤(Host 匹配 + 静态资源排除),再以
requestId为 key 暂存; -
响应返回时
:回填状态码和响应头;
-
请求完成时
:通过
Network.getResponseBody拿完整响应体,解析 JSON 后归档,并广播刷新侧边栏。
这种设计的好处是:请求和响应分阶段获取,避免单次事件数据量过大;即使响应体很大(比如文件下载),也不会阻塞主流程。
04 智能过滤:如何做到「只抓我想抓的」
抓包最怕噪音。如果每个 JS、CSS、图片都跑到侧边栏,那这个工具和 F12 就没区别了。
background.js 里设计了一套基于 Host 和正则的双层过滤引擎。
4.1 第一道滤网:静态资源排除

bash
function isApiRequest(url) {
const skipExt = [
'.js', '.css', '.png', '.jpg', '.jpeg', '.gif',
'.ico', '.svg', '.woff', '.woff2', '.ttf', '.map', '.html'
];
try {
const p = new URL(url).pathname.toLowerCase();
if (skipExt.some(e => p.endsWith(e))) return false;
if (url.startsWith('chrome-extension://') || url.startsWith('chrome://')) {
return false;
}
} catch {
return false;
}
return true;
}
解析 URL 路径,检查后缀是否在跳过列表里,同时排除 Chrome 内部页面和扩展页面。
4.2 第二道滤网:Host 白名单 + 正则模块提取

真正聪明的过滤在 isUrlMatchConfig:
bash
function isUrlMatchConfig(url, config) {
if (!config) return false;
return parseHostFilter(config.hostFilter)
.some(r => url.startsWith(r.host));
}
function parseHostFilter(hostFilterStr) {
if (!hostFilterStr) return [];
return hostFilterStr.trim().split('\n').map(line => {
const parts = line.trim().split('|');
return {
host: parts[0]?.trim() || '',
feature: parts[1]?.trim() || '',
tenant: parts[2]?.trim() || '',
role: parts[3]?.trim() || ''
};
}).filter(item => item.host);
}
用户的 hostFilter 配置格式是:
bash
https://jsonplaceholder.typicode.com|jsonplaceholder||
https://jsonplaceholder.typicode.com/posts|posts||
每行用 | 分隔四个字段:主机地址、功能模块名、租户信息、角色信息。
这个设计很有讲究:插件在抓包的瞬间,就已经知道这个请求属于哪个业务系统、哪个模块、哪个租户环境------这些信息后面会直接用于 YAML 的结构化组织。
4.3 正则提取:让 URL「说话」

除了 Host 白名单,配置里还有两个正则字段:
-
featureRule:从路径提取功能模块,例如
/api/([^/]+)/能从/api/order/create提取order; -
subdirRule:提取子目录,例如
/api/[^/]+/v\d+/([^/]+)/能从/api/order/v1/user/list提取user。
这些结果,会在生成 YAML 时用于组织目录结构------让导出的用例天然具有清晰的模块层次。
05 生命周期管理:别让插件「拖垮」浏览器
做过 Chrome 扩展的同学都知道,chrome.debugger 有个巨坑:
用户关闭页面或切换 Tab 时,如果没有及时 detach,插件就会报错,甚至导致浏览器卡顿。
想象一下:你正在抓包,突然收到一条紧急消息,切到另一个 Tab 去回复。如果忘了停止抓包就关掉页面,后台的 Debugger 监听不会自动断开,后续这个 Tab 再被打开就会出问题。
所以 background.js 里加了严密的生命周期管理:
bash
// Tab 关闭时自动分离 Debugger
chrome.tabs.onRemoved.addListener(tabId => detachDebugger(tabId));
async function detachDebugger(tabId) {
if (!attachedTabs.has(tabId)) return;
try {
await chrome.debugger.detach({ tabId });
attachedTabs.delete(tabId);
console.log(`[Debugger] 已从 Tab ${tabId} 分离`);
} catch (e) {
console.error('[Debugger] 分离失败:', e);
}
}
附加 Debugger 之前,还会做 Tab 合法性校验:
bash
function isAttachableTab(tab) {
if (!tab || !tab.url) return false;
const blocked = [
'chrome://', 'chrome-extension://', 'devtools://',
'edge://', 'about:', 'data:', 'javascript://'
];
for (const prefix of blocked) {
if (tab.url.startsWith(prefix)) return false;
}
return tab.url.startsWith('http://') || tab.url.startsWith('https://');
}
这些「防御性编程」的细节,保证了一个测试工具最基本的素质:不添乱、不崩溃、不拖慢浏览器。
06 配置管理:让工具适应业务,而不是让人适应工具
一款好的测试工具,不应该让测试人员去适应工具的固定模式,而应该让工具去适应业务场景。
这就是 options.html + options.js 这套配置系统存在的理由。
6.1 多环境配置:一套工具,多个系统

配置页里可以创建多个系统配置,每个包含:
-
配置名称(如「测试演示(JSONPlaceholder)」)
-
Host 过滤规则
-
功能模块正则
-
子目录正则
-
默认租户 / 角色
-
断言成功值(响应体中表示成功的字段值)
插件启动时自动加载配置,首次使用还会创建一个默认测试配置:
bash
// 默认配置(JSONPlaceholder - 无需真实后端)
{
id: 'config_test_demo',
name: '测试演示(JSONPlaceholder)',
featureRule: '/([^/]+)/',
subdirRule: '/[^/]+/(\\d+)',
hostFilter: 'https://jsonplaceholder.typicode.com|jsonplaceholder||'
}
好处是:安装后无需任何配置就能立即体验抓包(用 JSONPlaceholder 这个公开测试 API),大大降低上手门槛。
6.2 正则测试器:配置不出错的安全网

配置正则最怕什么?配错了不知道,抓不到包了也不知道原因。
所以配置页内置了一个正则测试工具:
bash
<div id="regexTestCard">
<h3>正则表达式测试</h3>
<div class="form-grid">
<div class="form-group">
<label>测试 URL</label>
<input type="text" id="testUrl"
placeholder="https://jsonplaceholder.typicode.com/posts/1" />
</div>
<div class="form-group" style="justify-content:flex-end; padding-top:18px;">
<button id="btnRunTest" class="btn btn-warning" onclick="runRegexTest()">
执行测试
</button>
</div>
</div>
<div id="regexResult" style="margin-top:10px; font-size:12px;"></div>
</div>
输入一个真实 API URL,点「执行测试」,就能即时验证 featureRule 和 subdirRule 是否能正确提取模块名和子目录。
功能看似简单,但实际使用中极大地减少了配置调试时间。
6.3 配置导入导出:团队协作的基石

支持 JSON 格式的导入导出:
bash
function exportConfigs() {
const json = JSON.stringify(
{ systemConfigs: configs, currentConfigId },
null,
2
);
const blob = new Blob([json], { type: 'application/json;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `api_capture_configs_${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
}
你可以把自己的配置导出分享给团队,大家导入后即使用相同的过滤规则和正则,保证团队内部的抓包配置一致性。
07 侧边栏体验:抓包也可以很优雅
最后看看用户直接交互的侧边栏。
sidebar.html 构建了一个完整的单页应用结构:
bash
<!-- 顶部工具栏 -->
<div class="toolbar">
<span class="app-title">API Capture</span>
<button id="optionsBtn" class="btn btn-icon" title="系统配置">配置</button>
</div>
<!-- 配置选择栏 -->
<div class="config-bar">
<label class="config-label">系统:</label>
<select id="configSelect" class="config-select"></select>
<span id="captureStatus" class="status-badge status-stopped">已停止</span>
</div>
<!-- 操作按钮栏 -->
<div class="action-bar">
<button id="startBtn" class="btn btn-success">开始抓包</button>
<button id="stopBtn" class="btn btn-danger" disabled>停止</button>
<button id="clearBtn" class="btn btn-warning">清空</button>
<button id="exportBtn" class="btn btn-primary">导出 YAML</button>
</div>
<!-- 过滤栏 -->
<div class="filter-bar">
<input id="filterInput" type="text" placeholder="过滤 URL / 方法..." class="filter-input" />
<select id="methodFilter" class="method-filter">
<option value="">全部方法</option>
<option value="GET">GET</option>
<option value="POST">POST</option>
<option value="PUT">PUT</option>
<option value="DELETE">DELETE</option>
</select>
</div>
<!-- 请求列表 -->
<div id="requestList" class="request-list">...</div>
<!-- 详情面板 -->
<div id="detailPanel" class="detail-panel hidden">
<div class="detail-tabs">
<button class="tab-btn active" data-tab="overview">概览</button>
<button class="tab-btn" data-tab="request">请求体</button>
<button class="tab-btn" data-tab="response">响应体</button>
<button class="tab-btn" data-tab="yaml">YAML 预览</button>
</div>
<div id="detailContent" class="detail-content"></div>
</div>
侧边栏的设计哲学是「轻量、直观、不打断工作流」:
-
配置切换
:顶部下拉选择当前系统配置,切换后立即生效;
-
一键抓包
:点击开始,插件自动附加 Debugger,页面上的请求全部捕获;
-
实时过滤
:按 URL 关键词和方法类型实时筛选;
-
详情面板
:点请求卡片滑出,看概览、请求体、响应体、YAML 预览;
-
拖拽排序
:调整用例执行顺序。
sidebar.css 里用了大量 CSS 变量和现代特性(flex、过渡动画、自定义滚动条),在功能完整的同时也照顾了视觉体验。
08 小结:工具的价值在于「无感」
回顾一下,这篇从传统抓包工具的痛点出发,拆解了插件在抓包层面的技术实现:
-
基于
chrome.debugger而非chrome.webRequest,实现了全量请求体/响应体捕获; -
通过 Host 白名单 + 静态资源过滤 + 正则模块提取的三层过滤,确保侧边栏只展示核心业务 API;
-
严密的 Tab 生命周期管理,保证插件不拖垮浏览器;
-
灵活的多环境配置,让一套工具适配多个业务系统;
-
内置正则测试和配置导入导出,降低配置门槛和团队协作成本。
但抓包只是第一步。
抓下来的数据如何变成自动化脚本?接口之间的参数关联如何自动搞定?
下一篇是本系列的高潮:《接口自动化的「灵魂」:如何让工具自动搞定上下游接口的参数关联?》 ------深入 sidebar.js 的核心算法,看插件如何像一位老测试一样,自动识别接口依赖并建立变量引用。
互动时间
你用抓包工具时,遇到过最抓狂的问题是什么?HTTPS 证书配置,还是噪音请求太多?
欢迎在评论区留言(注意隐去敏感信息),我会在后续文章里针对性解答。
如果这篇对你有帮助,也欢迎转给正在被抓包折磨的同事。