浏览器资源嗅探是怎么工作的:一条 M3U8 请求在 Cat-Catch 里的六道关卡

发布时间:2026/11/4 19:07:27
浏览器资源嗅探是怎么工作的:一条 M3U8 请求在 Cat-Catch 里的六道关卡

浏览器资源嗅探是怎么工作的:一条 M3U8 请求在 Cat-Catch 里的六道关卡

【免费下载链接】cat-catch猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch

猫抓 Cat-Catch(cat-catch)是一款浏览器资源嗅探扩展:被动监听页面网络请求,识别出视频、音频,并提供 M3U8 切片解析与合并下载器,结果可走浏览器保存、边下边存、在线 ffmpeg、aria2、MQTT 等通道。下面跟着一条 M3U8 请求,走一遍它从"被看见"到"落盘"的全过程,每到一个关卡展开一个机制,事实以 v2.7.4 的代码为准。

webRequest 双钩子拦截 → findMedia 多级过滤 → cacheData 按 tabId 分桶 → storage.session 落盘 → popup 读取 → m3u8.html 解析 → Downloader 切片合并 → 落盘通道

关卡一 · webRequest 怎么在两个时间点截住一次请求 📡

打个比方:门卫分两次登记。第一次是"出门",先把访客递的证件抄一份留底;第二次是"进门",拿同一张凭证把进出信息拼成一条完整记录。

js/background.js 里挂了两组webRequest钩子:

  • onSendHeaders:请求发出时触发,把requestHeaders按requestId存进内存 MapG.requestHeaders,顺便跑一遍用户配置的正则匹配。之所以要在这里暂存,是因为 Referer 这类请求头在响应阶段拿不到了。
  • onResponseStarted:收到响应第一个字节时触发,用同一个requestId取回暂存的请求头(取完即删),再交给findMedia做类型判断——此时content-type、content-length才可用。
  • onErrorOccurred:请求失败时清理暂存,防止 Map 泄漏。

留底的请求头不是全部留下。getRequestHeaders只挑"有用"的:referer、origin、cookie、authorization 等固定名单,外加名字里带auth|token|sign|key|ticket|session的x-头。这些头之后会跟着资源一起传给下载器,重放请求时带上,能提高下载成功率。

关卡二 · 是媒体吗:findMedia 的多级过滤 🧪

不是所有请求都值得入库,findMedia是一套"先粗后细"的筛选链:

阶段判断依据未通过的结果
全局前置G.enable开关、页面屏蔽列表(黑白名单)、OPTIONS预检直接丢弃
自定义正则用户配置的G.Regex(在onSendHeaders阶段先跑)可改写 URL,或标记该请求后续一律丢弃
文件扩展名URL 路径取扩展名,CheckExtension校验 + 大小判断break丢弃
Content-TypeCheckType,支持video/*这类通配break丢弃
attachment 文件名content-disposition里提取filename重新验扩展名丢弃
webRequest 类型标记data.type == "media"直接放行

其中扩展名和 Content-Type 的大小判断从 2.6.8 起支持表达式,比如>100 KB、500-1000 MB,等于给"多小算视频"加了一道可调的闸门。

过滤通过的资源还会做一层查重:按标签维护 URL 指纹集合,条目数到 500 就清空重算——避免大列表下逐条比对拖垮 CPU。

webRequest只看得到真实网络流量,抓不到blob:和 WebRTC 这类"隐身"数据。这块由注入页面主动补刀:webNavigation.onCommitted里按配置注入 catch-script/search.js(深度搜索)、缓存捕获、WebRTC 录制脚本,找到媒体后通过addMedia消息回传 background,重新进入findMedia走同一套过滤。manifest.json 的commands也提供了catch、deepSearch、m3u8等快捷键入口。

关卡三 · 嗅探列表存哪:cacheData 分桶与 99、9999 两个数字 📦

命中的资源按tabId分桶存进内存对象cacheData,每个标签页一个桶。写入不是每条都刷盘,而是一套节流:

  • 列表超过 100 条时,5 秒防抖攒一批再存;两次写入间隔不足 500ms 时延后 2 秒再存;
  • 单标签超过 99 条后停止逐条落盘,改为依赖chrome.alarms的save事件周期性补写,防止高频站点刷爆存储;
  • 单标签超过 9999 条(G.maxLength)直接清空该桶,防内存膨胀。

落盘目标是storage.session ?? storage.local——这一行"?? 降级"值得单独说。类比:仓库把"今天收货记录"放在门口的白板(内存级、随会话清空),把"仓库规章"锁在档案柜(持久化)里。2.5.3 之前嗅探列表写在storage.local,磁盘 IO 错误会导致扩展整体不可用;迁到storage.session(Chrome 104+)后高频写免了磁盘放大,低版本浏览器又靠??自动回落storage.local。代价是列表只活在当前会话,关浏览器即清空,无法跨会话恢复。偏好设置、屏蔽列表仍走local/sync,两类数据互不污染。

popup 打开时向 background 发getData消息,background 从cacheData[tabId]取出本标签的分桶数据,渲染出资源列表——js/popup.js 同时还会发一条HeartBeat消息,这是下一关的伏笔。

关卡四 · 后台被杀了怎么办:HeartBeat 保活与 500ms 自愈 💓

MV3 的 Service Worker 空闲约 5 分钟会被强杀,而嗅探列表就在它的内存里。猫抓的选择是和进程管理器打时间差:

// js/background.js —— 收到页面侧的 HeartBeat 长连接 chrome.runtime.onConnect.addListener(function (Port) { if (chrome.runtime.lastError || Port.name !== "HeartBeat") return; Port.postMessage("HeartBeat"); const interval = setInterval(function () { clearInterval(interval); Port.disconnect(); // 4分10秒主动断开,抢在5分钟强杀前 }, 250000); });

配合 js/content-script.js 页面对onDisconnect重连续连,以及每 25 秒一次的getPlatformInfo调用、webNavigation监听注册,共同把 Worker 拖在死亡线上。这是"保活"路线。

另一条是"自愈"路线:findMedia入口先检查全局变量是否初始化完毕,若进程刚被唤醒(G.initSyncComplete还没就绪),setTimeout500ms 后重放这次请求,给"醒来后的第一波流量"留缓冲。

Firefox 不需要这套:manifest.firefox.json 用background.scripts事件页常驻。代价是 Chromium 这套保活依赖其具体实现细节,浏览器升级随时可能让它失效——两条生命周期路线要长期双维护。

关卡五 · M3U8 切片下载器:乱序下载,但按序合并 🧵

用户勾选一条 M3U8 资源,会打开m3u8.html解析器。页面里用hls.js发loadSource,监听MANIFEST_LOADED、LEVEL_LOADED等事件把切片对象收集进_fragments,同时抓取EXT-X-KEY声明的密钥(深度搜索挖出的疑似密钥经catCatchAddKey消息也能汇入)。

真正干活的是 js/m3u8.downloader.js 的Downloader类(js/m3u8.js 中new Downloader(fragments, 线程数)实例化,默认 6 线程)。它的核心思路是下载和推送解耦:

// js/m3u8.downloader.js —— 按原始顺序推送 buffer sequentialPush() { if (!this.events["sequentialPush"]) { return; } for (; this.pushIndex < this.fragments.length; this.pushIndex++) { if (this.buffer[this.pushIndex]) { this.emit('sequentialPush', this.buffer[this.pushIndex]); // 推给合并/流写出 delete this.buffer[this.pushIndex]; continue; } break; // 这一片还没回来就停住等,绝不出乱序 } }

index只管"取下一片去下载",pushIndex单独负责"按顺序往外发",buffer按切片下标存放,所以下载可以完全乱序、并行,而输出永远有序。围绕它还有一套字段各管一件事:

  • errorIndexes+autoRetry:失败切片记入集合,2.7.1 起开启自动重试,最多MAX_RETRIES3 次,按500ms × 次数退避;
  • controller:每片一个AbortController,支持整单中止或只停某一片;
  • pipeline(use注册):buffer 到手后过处理管线,可挂"剔除伪装 PNG 头部"的预处理、AES 解密、转 mp4 转封装;
  • byteRange:遇到EXT-X-BYTERANGE标签(2.6.8 起支持合并)时,自动给fetch加Range请求头。

针对"一次性 URL"(刷新后 404 的临时 m3u8 地址),2.7.0 的解法是让 content-script 用fetch(url, { cache: "force-cache" })直接从浏览器缓存里把 m3u8 文本捞出来,不再依赖还能访问的服务器。

关卡六 · 落盘通道与版本边界 💾

合并完成的 buffer 有几条出口,按场景选:

通道触发说明
chrome.downloads.download默认 / 自动下载浏览器原生下载,可指定文件名
边下边存(StreamSaver)勾选StreamSaver走 lib/StreamSaver.js 的文件流,sequentialPush数据边下边写,直播流适用;估算超过 2GB 时会弹窗强制切到该模式
在线 ffmpeg勾选 ffmpegcatCatchFFmpeg消息投给 ffmpeg 网页标签,2.6.8 起支持嵌套 iframe 模式,免开新标签
aria2 / MQTT / 本地程序send2local数据发送把资源元数据(含完整请求头)POST 给指定地址,交给外部下载器

边界方面,几个硬门槛值得记住:

  • Chromium 114 以下缺少sidePanelAPI,扩展无法使用(2.6.3 修复记录);
  • storage.session要求 Chrome 104+,低版本回落storage.local,磁盘写放大的老风险仍在;
  • Firefox 需 113+(strict_min_version),且深度搜索、缓存捕获、WebRTC 录制等注入脚本类功能要 Firefox 128+(2.5.7);
  • 深度搜索/缓存捕获是注入页面的主动脚本,2.6.5、2.6.6、2.7.3 反复修复过"导致某些网站无法显示/使用",2.6.5 因此加入"全局强制屏蔽"兜底;
  • 单标签 99 条后停写、9999 条上限清空,高频站点下列表可能被截断。

能直接搬走的有两样:一是"webRequest 双钩子 + 按requestId暂存请求头"的模式,任何做流量嗅探的扩展都绕不开;二是index / pushIndex / errorIndexes / controller四字段状态机,把乱序下载、顺序合并、失败重试、可中断四个场景各管一段。需要提醒的是,HeartBeat 保活本质是和浏览器进程管理抢时间,若你的扩展要长寿,更稳的做法是让后台状态可重建、被杀后能自愈——猫抓的 500ms 重放正是这条路线的补丁。

【免费下载链接】cat-catch猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考