Files
huangguo-video/README.md
T
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

118 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# huangguo-video
黄果(huangguo.me)短剧抓包下载工具。从 Reqable 抓包记录还原 AES-128 加密的 HLS 视频。
## 环境
- macOS + [uv](https://docs.astral.sh/uv/) 包管理
- `ffmpeg`(合并分片用,系统已装)
- Reqable 3.2.7+(MCP 功能,可选)
```bash
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 直接下载
```bash
# 整条流(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` 封装了握手:
```bash
# 列出所有 MCP 工具
python3 reqable_mcp.py list
# 调用任意工具
python3 reqable_mcp.py call capture_live_get_by_id '{"id": 4289}'
# 快捷搜索
python3 reqable_mcp.py m3u8
```
典型还原流程:
```python
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 秒完成。