toor eaacacc8e0 初始提交:黄果短剧抓包下载工具集
- hls_dl.py: 通用 HLS 下载器(m3u8 解析/AES-128 解密/ffmpeg 合并)
- reqable_mcp.py: Reqable MCP 客户端(stdio, --port 9002)
- huangguo_video_dl.user.js: 油猴全集下载脚本 v1.1.2
  * 详情页一键下载全部集数,解密转封装后打包 ZIP 存 ~/Downloads
  * 手写 STORE ZIP(逐集 Blob 卸堆外),修复 JSZip 大文件卡死
  * @connect 覆盖 rlkfus.cn / cloudfront.net CDN
- README.md: 用法与踩坑记录
2026-10-08 17:19:00 +08:00

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 油猴脚本:详情页一键全集下载,打包 ZIP 存到 ~/Downloads
陪玩假女友_1.mp4 成品:123s / 720×1280 / H.264+AAC / 21.93 MB

用法一: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>/:

  1. 剧集区出现 「⬇ 全集下载」 按钮
  2. 点击 → 自动读取剧名、遍历全部集数、逐集下载解密转封装 → 打包 剧名.zip 存入 ~/Downloads
  3. ZIP 内文件名:剧名_1.mp4、剧名_2.mp4 …(按 data-ep 排序)

实现要点(全部在浏览器内完成,零后端):

环节 方案
跨域请求 GM_xmlhttpRequest(@connect huangguo.me + *.rlkfus.cn)
剧名/集数 [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 仿真真实管线):

  • 下载解密转封装:第 1 集 21.9MB/3.7s、第 2 集 14.5MB/2.4s,解压后文件名与时长均正确
  • 打包:10 集 × 20MB = 200MB 压测,1 秒内完成,Python testzip() CRC 逐字节全过,内存 RSS 294MB

踩坑记录

  1. Reqable MCP 端口:默认连 127.0.0.1:9000,但该端口被 adb 占了,Reqable 实际监听 9002,必须 mcp-server --port 9002(reqable_mcp.py 已写死)。
  2. TLS 证书:抓包环境有 MITM 证书,uv/curl/裸 urllib 都可能报 CERTIFICATE_VERIFY_FAILED。请求走 hls_dl.http_get()(显式不走代理)或设 system-certs = true。
  3. 无 IV ≠ 用 key 当 IV:HLS 规范是用 media sequence 号大端填充 16 字节,搞错会花屏。
  4. m3u8 地址短时效:ticket/md 签名会过期,分片 URL 同理;完整的列表和分片尽快落盘(Reqable 的本地缓存就是保险)。
  5. 裸 .ts 报 Invalid data:十有八九是 AES 密文,先找 #EXT-X-KEY。
  6. m3u8 相对路径前缀:play 页 data-url 是 v3/...m3u8,必须拼 https://huangguo.me/h5/m3u8/,拼成根路径直接 404。
  7. 接口不校验 Referer:play 页 / m3u8 / 分片 / enkey 全链路去掉 Referer 照常(浏览器脚本本来也伪造不了),这是油猴方案可行的前提。
  8. m3u8 单次即全集:每次请求返回的就是该集完整列表(如第 1 集 18 段);之前遇到的"3 段广告"是另一个轮换位,别混淆。
  9. mux.js 全局名:浏览器 UMD 分支挂的是 muxjs(不是 mux,vm 实测),脚本里写 window.muxjs || window.mux 兼容;Node 加载需 npm i global 提供 shim,入口 mux.mp4.Transmuxer。
  10. JSZip generateAsync 卡 90%:Firefox 下多集大文件(每集 ~20MB×N)会永久挂死——所有 Uint8Array 压在 JS 堆上引发 GC 风暴。解法是弃用 JSZip,每集算完 CRC 立即 new Blob([bytes]) 卸到堆外,ZIP 组装只拼 local header/central dir/EOCD 字节 + Blob 引用,200MB 1 秒完成。
S
Description
No description provided
Readme
166 KiB
Languages
JavaScript 55.3%
Python 44.7%