- 文件表标注脚本版本,成品视频由 gitignore 挡在库外 - 实现要点补 @connect 全白名单与网络容错三层防线 - 验收结果补 2026-10-08 实战全集下载成功 - 新增踩坑 11: @connect 漏域名被油猴静默拦截的特征与解法 - 新增踩坑 12: 代理节点瞬断指纹(remote.ip 为空 + 对齐重启时间)
6.6 KiB
6.6 KiB
huangguo-video
黄果(huangguo.me)短剧抓包下载工具。从 Reqable 抓包记录还原 AES-128 加密的 HLS 视频。
环境
- macOS + uv 包管理
ffmpeg(合并分片用,系统已装)- Reqable 3.2.7+(MCP 功能,可选)
cd huangguo-video
uv sync # 自动建 .venv 并装依赖(pycryptodome)
pyproject.toml里固定了system-certs = true:本机走 MITM 代理抓包,uv 自带证书库不认系统 CA,没这条uv sync会报 TLS 错。
文件说明
| 文件 | 用途 |
|---|---|
hls_dl.py |
通用 HLS 下载器:拉 m3u8 → 并发下分片 → AES-128 解密 → ffmpeg 合并 |
reqable_mcp.py |
Reqable MCP 客户端:命令行翻抓包记录、调用 Reqable 的 50+ 个工具 |
huangguo_video_dl.user.js |
油猴脚本 v1.1.3:详情页一键全集下载,打包 ZIP 存到 ~/Downloads |
成品视频(
*.mp4/*.zip)由.gitignore挡在库外,不入库。
用法一:HLS 直接下载
# 整条流(m3u8 地址,从 Reqable 复制完整 URL)
uv run python hls_dl.py "https://xxx.m3u8?id=..&ticket=.." \
--referer "https://huangguo.me/" -o 视频.mp4
# 单个 .ts 分片
uv run python hls_dl.py x --ts-url "https://xxx/seg.ts?md=.." -o test.ts
# 需要走代理时(默认直连)
uv run python hls_dl.py "..." --proxy http://127.0.0.1:7890
要点:
- 自动识别 master/media 多级播放列表,选最高码率
- AES-128 自动解密,无
IV时按 HLS 规范用EXT-X-MEDIA-SEQUENCE序号做 IV(0,1,2,…) - 合并优先
ffmpeg -f concat -c copy(无损),没有 ffmpeg 回退二进制拼接 - 单次 m3u8 可能只返回部分分片(分页/轮换),拿不到完整列表时走用法二
用法二:从 Reqable 记录还原视频
MCP 是 stdio 型子进程,reqable_mcp.py 封装了握手:
# 列出所有 MCP 工具
python3 reqable_mcp.py list
# 调用任意工具
python3 reqable_mcp.py call capture_live_get_by_id '{"id": 4289}'
# 快捷搜索
python3 reqable_mcp.py m3u8
典型还原流程:
from reqable_mcp import McpClient
c = McpClient(); c.initialize()
# 1. 按关键词搜记录,拿 ID 列表
r = c.call('capture_live_filter', {'filters': [{'type': 'keyword', 'pattern': 'm3u8'}]})
ids = json.loads(r['content'][0]['text'])
# 2. 取详情:m3u8 响应体在 response.body.text,enkey 的 key 是 base64
# 分片密文被 Reqable 落盘,response.body.text 是本地文件路径
d = json.loads(c.call('capture_live_get_by_id', {'id': 4289})['content'][0]['text'])
# 3. key + 序号 IV 解密 → ffmpeg 合并(见 git log 里的一次性脚本)
三件套:完整 m3u8(记录的响应体) + AES key(/enkey 响应,base64) + 分片密文(Reqable 本地缓存文件,缺失的段用记录里的 URL 在线补)。
用法三:油猴脚本一键全集下载
huangguo_video_dl.user.js — 安装到 Tampermonkey/Violentmonkey 后,打开任意详情页 https://huangguo.me/detail/<slug>/:
- 剧集区出现 「⬇ 全集下载」 按钮
- 点击 → 自动读取剧名、遍历全部集数、逐集下载解密转封装 → 打包
剧名.zip存入 ~/Downloads - ZIP 内文件名:
剧名_1.mp4、剧名_2.mp4…(按data-ep排序)
实现要点(全部在浏览器内完成,零后端):
| 环节 | 方案 |
|---|---|
| 跨域请求 | GM_xmlhttpRequest(@connect huangguo.me + *.rlkfus.cn + *.cloudfront.net + * 兜底) |
| 网络容错 | 失败重试 2 次(1s)→ 降级原生 fetch(CDN 带 ACAO: *)→ 完整错误显示到面板 |
| 剧名/集数 | [data-drama-title] / data-total-episode / a.detail-episode[data-ep] |
| m3u8 地址 | play 页 .dplayer-video 的 data-url,拼 /h5/m3u8/ 前缀 |
| AES-128 解密 | WebCrypto subtle.decrypt,IV = media sequence 大端 |
| TS → MP4 | mux.js mp4.Transmuxer(输出 fMP4) |
| 打包 | 手写 STORE ZIP(每集 CRC32 后转 Blob 卸到堆外,只拼头尾字节) |
| 付费集 | 识别 data-coin-dialog-open,自动跳过并在完成提示里列出 |
已验证:
- Node 仿真管线:下载解密转封装 21.9MB/3.7s;200MB 打包 1 秒,
testzip()CRC 全过,RSS 294MB - 实战验收(2026-10-08):《高三爱情故事》全集一键下载成功,ZIP 落入 ~/Downloads ✅
踩坑记录
- Reqable MCP 端口:默认连
127.0.0.1:9000,但该端口被 adb 占了,Reqable 实际监听 9002,必须mcp-server --port 9002(reqable_mcp.py已写死)。 - TLS 证书:抓包环境有 MITM 证书,
uv/curl/裸urllib都可能报CERTIFICATE_VERIFY_FAILED。请求走hls_dl.http_get()(显式不走代理)或设system-certs = true。 - 无 IV ≠ 用 key 当 IV:HLS 规范是用 media sequence 号大端填充 16 字节,搞错会花屏。
- m3u8 地址短时效:
ticket/md签名会过期,分片 URL 同理;完整的列表和分片尽快落盘(Reqable 的本地缓存就是保险)。 - 裸
.ts报 Invalid data:十有八九是 AES 密文,先找#EXT-X-KEY。 - m3u8 相对路径前缀:play 页
data-url是v3/...m3u8,必须拼https://huangguo.me/h5/m3u8/,拼成根路径直接 404。 - 接口不校验 Referer:play 页 / m3u8 / 分片 / enkey 全链路去掉 Referer 照常(浏览器脚本本来也伪造不了),这是油猴方案可行的前提。
- m3u8 单次即全集:每次请求返回的就是该集完整列表(如第 1 集 18 段);之前遇到的"3 段广告"是另一个轮换位,别混淆。
mux.js全局名:浏览器 UMD 分支挂的是muxjs(不是mux,vm 实测),脚本里写window.muxjs || window.mux兼容;Node 加载需npm i global提供 shim,入口mux.mp4.Transmuxer。- JSZip
generateAsync卡 90%:Firefox 下多集大文件(每集 ~20MB×N)会永久挂死——所有Uint8Array压在 JS 堆上引发 GC 风暴。解法是弃用 JSZip,每集算完 CRC 立即new Blob([bytes])卸到堆外,ZIP 组装只拼 local header/central dir/EOCD 字节 + Blob 引用,200MB 1 秒完成。 @connect漏域名 = 油猴静默拦截:同一站点不同剧集可能走不同 CDN(《陪玩假女友》→rlkfus.cn,《高三爱情故事》→cloudfront.net),白名单没盖住时请求不发也不报权限错,只是 GMonerror网络错误。特征:Reqable 里 key 请求有、分片零记录。解法:@connect *兜底。- 代理节点瞬断的指纹:Reqable 记录
connection.remote.ip为空 = 请求进了代理但上游没建连(网络层问题,不是脚本问题);拿记录时间戳对ps -o lstart -p <mihomo_pid>看重启时间即可破案。此时段国内域名 200、国外 CDN 全挂,正是分流节点故障的典型特征。