Tabula相册整理工具2026
27.5MB · 2026-04-07
这篇文章记录的不仅是一次技术逆向,更是一次人机协作的思维实验。
整个过程中,Claude 负责了几乎全部的技术执行——下载、分析 JS、破解签名、解密数据、写入数据库。但每一次关键突破,都来自人的判断和直觉:
AI 是你手中最锋利的刀,但往哪里切,切多深,什么时候该换把刀——这些决策永远是人做的。
传统逆向工程需要精通汇编、密码学、协议分析,门槛极高。但在 AI 时代,这些技术细节可以交给 AI 处理。真正稀缺的能力变成了:
所以,如果你读完这篇文章只记住一件事,我希望是这个:
好了,进入正文。
以下是整个过程中的真实对话。你会看到一个有趣的模式:AI 反复得出"到此为止"的结论,而人反复用直觉和追问推翻这个结论。 四次"到头了",四次被打脸,每一次都打开了新世界的大门。
一开始的想法很朴素:站要关了,先把页面存下来。Claude 对比了 wget、反向代理、wayback_machine_downloader、monolith 四种方案。
Claude 自动检查 Ruby 环境、安装 gem、执行下载。8 分半后,196 个文件落地。
本地服务跑起来,打开一看——满屏 404,CSS/JS/SVG 全丢了。wayback_machine_downloader 只下载了 HTML,没拉静态资源。
Claude 从 HTML 里 grep 出 54 个静态资源路径,批量补全。又发现 CSS 里嵌着 Wayback 的 URL 前缀(/web/20260219094859im_/...),修了 33 处。又补下载了 23 张图片。来来回回折腾了三轮。
到这里,本地镜像算是完整了。但我不满足于静态页面——
这是第一次追问。 存档站只有 HTML,但原站肯定有 API。数据在哪?
Claude 分析了 JS 文件,发现原站是 SSR 直出的,客户端几乎没有 API 调用。视频播放用 DPlayer + HLS,但 m3u8 链接带时效 token,早就失效了。
第一条路,走到头了。
这就是思维灵活性的第一个体现:原站没有 API?那换个站找。 mod.run 是 M 平台的企业官网,也许有数据接口。
Claude 下载 Nuxt.js bundle,从 minified JS 中硬扒出 6 个 API 定义。逐个测试:navi/infoV2 返回首页数据,config 暴露了 CDN 地址 。但没有 list 接口,只能通过 media/detail 按 ID 逐个查。
我手里还有一张牌——官方 TG 频道的消息记录。629 条消息,296 个编号。但一比对,TG 频道的编号 ID、madou.com 的存档 ID、mod.run 的 mediaId,三套体系完全不兼容。
既然没有 list,那就暴力枚举。Claude 算出 4 个 ID 集群、13124 个待扫描 ID。
先用我自研的 hs-net 库开 30 并发——秒被 403。降到 5 并发加延迟——还是被封连接。最后老老实实用 Python urllib 单线程、每个请求间隔 0.5 秒,像蚂蚁搬家一样逐个验证。
结果出来了:49 条有效视频。 百万级 ID 范围里只有 49 条。mod.run 就是个企业展示站,放了几十条精选片段而已。
数据虽然少,但每一条都得存完整。逐步补全字段。
这是人机协作的经典场景:AI 负责分析代码,人负责在浏览器里观察行为。 我在 Chrome DevTools 里抓到了新站的 API 请求,连 appId=888 这个关键参数都直接给了。
返回 68 条视频。Claude 从 Wayback Machine 获取 JS chunk,确认整个新站只导出了这一个 API 调用。
mod.run 48 + madou.com 68 = 116 个编号,零重叠。两个站的内容库完全互补——mod.run 偏老片,madou.com 偏新片。但 TG 频道有 212 个编号,API 只覆盖了 38.7%。
还有 61.3% 的数据不知道在哪。
Claude 下载了所有 Vite chunk,确认只有一个 API。
Claude 的结论: "madou.com 新站就这一个数据接口,68 条就是全部了。"
这句话是整篇文章的转折点。 也是我想传达的核心理念的最好注脚——AI 给出的"结论",往往只是当前视角下的最优解。换一个视角,整个局面可能完全不同。
AI 说到头了。但我的直觉告诉我:一个运营了多年的平台,昨天才关站,不可能只留下 116 条数据。数据一定还在某个地方,只是我们没找到入口。
Claude 被这句话"激活"了,开始重新审视所有线索:回到 mod.run 的 config 接口,提取出一串关联域名——mdapp12.com、mdbba02.com、ge.dfg3t534.cc、admin.qsxon.com,以及多个 CloudFront 域名。
Claude 发现 madou.com DNS 已指向 0.0.0.0——站确实关了。但灵机一动,试了 --resolve madou.com:443:104.21.0.0 绕过 DNS,直连 Cloudflare 边缘节点——居然还在响应!
更刺激的发现:扫描后端路由,30+ 个 API 接口全部存在,只是返回 {"msg":"no token"}。这是一个完整的 Go CMS 系统——media、actor、category、tag、banner,应有尽有。数据就在里面,只是锁着。
没有。注册接口也在 token 中间件后面。
Claude 探测到 admin/login 字段是 account + password,还发现接口会告诉你"该账号不存在"(用户枚举漏洞)。但 admin、test、demo、madou 全部不存在。管理员用的是自定义账号名。
这条路,又卡住了。
又一次追问。 这次 Claude 换了个方向——回到最初下载的 196 个存档 HTML 页面,grep 出所有外部域名。
在一堆广告链接和 CDN 地址中间,藏着 7 个 CloudFront 域名。其中一个 de1j8jm5ajr3l.cloudfront.net——返回了一个活着的站点。
标题叫 "B站点"。
这不是 M 平台,但下载它的 JS bundle(487KB)后发现——160+ 个 API 接口,跟 mod.run 同一套后端系统! 而且包含了完整的签名密钥和加密配置:
const Vn = {
http_request_key: "jR6dO6fT1yD9zY7u", // 就这样明文写在前端 JS 里
http_response_key: "vEukA&w15z4VAD3kAY#fkL#rBnU!WDhN",
guest_skey: "wM5wK1jP3lK7pS2t",
};
一个存档页面里的广告域名,最终指向了 24000 条数据。 这就是为什么说"存档 HTML 是线索金矿"。
接下来是纯技术活:破解 HMAC-SHA1 签名让请求通过验证,破解 AES-256-CBC 把加密的响应解开。签名算法从 JS 里直接抄,解密算法最大的坑是 JS 的 Array.splice() 会修改原数组——密钥派生过程中 6 次 splice,必须精确追踪每一步的数组状态。
游客登录被禁了("账户禁止登陆"),但 Claude 逐个测试接口权限,发现 media/home 不需要登录就能访问:
api_call("/api/app/media/home", {"id": 1}) # → 2707 条!
api_call("/api/app/media/home", {"id": 2}) # → 12327 条!! 原创影视!
从 116 条到 24726 条。翻了 213 倍。
6 个分类全量拉取,Redis db=2 存原始 JSON。
TiDB 建库建表,24,342 条入库。其中 M 平台编号 194 条。
| 指标 | 数值 |
|---|---|
| 人类发送的消息 | ~30 条 |
| 关键转折性指令 | 5 条 |
| Claude 读取的文件 | 200+ 个 |
| Claude 执行的命令 | 300+ 条 |
| Claude 下载的 JS bundle | 8 个 |
| 被限流/封禁次数 | 3 次 |
| 说"到头了"被打脸次数 | 4 次 |
| # | 指令 | 背后的思维 | 效果 |
|---|---|---|---|
| 1 | "我在另一个站发现的api接口" + curl | 原站没数据?换个入口 | 打开 mod.run 数据源 |
| 2 | appId=888 的 curl 命令 | 浏览器抓包,人机配合 | 直接拿到新站 API |
| 3 | "我不信,没有不透风的墙" | 拒绝接受"到头了" | 数据量翻 213 倍 |
| 4 | "应该会有公开的接口的啊" | 不死心,继续推 | 找到 CloudFront 金矿 |
| 5 | "我全都要...本地有redis...docker里的tidb" | 明确需求 + 提供基础设施 | 快速入库 |
注意第 3 条:这不是一条技术指令,而是一个信念。 它没有告诉 AI 该做什么,但它改变了 AI 的搜索方向。这就是人在 AI 协作中最核心的价值——判断和决策。
| 指标 | 数值 |
|---|---|
| 对话总时长 | 约 4-5 小时 |
| 数据来源 | 5 个(Wayback Machine、mod.run、madou.com、CloudFront、TG 频道) |
| 破解的加密算法 | 2 个(HMAC-SHA1 签名、AES-256-CBC 响应加密) |
| 发现的 API 接口 | 160+ 个 |
| 最终获取的视频数据 | 24,342 条 |
| 提取的M 平台编号 | 179 个 |
| 存储 | Redis + TiDB |
存档站 HTML 页面 : 196 条
mod.run API : 49 条 ← AI:"这就是全部了"
madou.com 新站 API : 68 条 ← AI:"确实只有这一个接口"
三源合计去重 : 116 个编号 ← AI:"公开能拿的到头了"
↓
"我不信,没有不透风的墙"
↓
CloudFront 域名发现 :24,342 条 ← !!!
每一次说"到头了",都被打脸。 但说到底,不是 AI 错了——在当时的信息下,它的判断是合理的。是人的"不合理的坚持"打开了新的可能性。
站要关了,第一反应是保存。就像火灾现场,先抢救能抢救的。
| 方案 | 优点 | 缺点 |
|---|---|---|
| wget mirror | 简单粗暴 | Wayback URL 重写混乱 |
| 反向代理 | 动态,不用下载 | 依赖 archive.org |
| wayback_machine_downloader | 专为 Wayback 设计,自动还原 URL | 需要 Ruby |
一条命令搞定:
wayback_machine_downloader --from 20260209 --to 20260210 -d ./site
下载的 196 个 HTML 页面只是骨架。补全经历了三轮:
wombat 运行时,Python 脚本批量清理/web/20260219094859im_/... 正则替换为 /static/...本地可完整浏览的静态站点。但没有 API 数据——原站是 SSR 直出,客户端几乎没有数据接口。视频 m3u8 链接带时效 token,早已失效。
收获:196 个 HTML 页面。 当时觉得只是存个档,没想到后来正是这些 HTML 里嵌入的一个广告域名,最终指向了 24000 条数据。
mod.run 是 M 平台的企业官网(Nuxt.js),下载其前端 bundle,从 minified JS 中提取到 API 路由定义:
var lr = {
SITEINFO: "/api/app/navi/infoV2",
CONFIG: "/api/app/config",
VIDEO_DETAIL: "/api/app/media/detail",
// ... 一共 6 个
};
关键发现:
{path}从 Nuxt SSR 的 __NUXT__ 数据中提取已知 ID,发现分布在 3 个集群(242640~242648, 666865~667142, 1184741~1184779)。
30 并发扫描→秒被 403。5 并发→还是封。最后单线程 0.5s 间隔逐个跑。
结果:49 条。 百万级 ID 范围里只有 49 条。mod.run 就是个门面。
| 来源 | ID 体系 | 示例 |
|---|---|---|
| TG 频道 | 编号(文件名) | MD-0332.mp4 |
| madou.com 存档 | 文章 ID(超长) | 2006372122030268417 |
| mod.run | 自增数字 | 1184759 |
三套系统完全不兼容。 不同的团队、不同的数据库、不同的时代。
madou.com 不再是 SSR 站,变成了 Vue SPA。我在浏览器 Network 面板里抓到了一个 POST 请求:
curl 'https://madou.com/api/stat/landPageConfig/getByType'
--data-raw '{"appId":888,"pageNum":1,"pageSize":10,"type":2}'
返回 68 条视频。人看浏览器,AI 分析代码,配合得刚好。
分析过程中发现 madou.com DNS 已指向 0.0.0.0。但 Cloudflare 边缘节点还没清理缓存!
# DNS 已死
$ dig madou.com +short
0.0.0.0
# 但直连 Cloudflare 还活着
$ curl --resolve "madou.com:443:104.21.0.0" 'https://madou.com/api/stat/...'
# → {"code":200, "data":[...]}
经验:DNS 死亡 ≠ 服务死亡。 CDN 边缘节点的缓存清理有延迟,这个时间窗口就是机会。
用 --resolve 绕过 DNS 后,扫描后端路由:
/api/stat/media/list → {"msg":"no token"}
/api/stat/media/detail → {"msg":"no token"}
/api/stat/actor/list → {"msg":"no token"}
/api/stat/admin/login → {"code":10000, "msg":"Param error"} ← 公开!
30+ 个接口全部存在,只是锁在 token 后面。 这是一个完整的 Go CMS——后端根本没关,只是前端 DNS 断了。
但没有 token 就是没有 token。admin/login 试了常见账号全部不存在。
此时的总分:116 个编号。 Claude 说到头了。
Claude 被这句话推动,重新审视所有已有材料。回到最初下载的 196 个 HTML 页面,提取所有外部域名。
在广告链接、CDN 地址、统计脚本中间,找到了 7 个 CloudFront 域名。逐个探测,de1j8jm5ajr3l.cloudfront.net 返回了一个活着的站——标题叫 "B站点"。
一个存档页面里的广告位域名,指向了一个活跃的视频站。 这个站跟 M 平台用同一套技术体系。
下载其 index-BY9lizwW.js(487KB),提取到完整的 API 列表和加密配置:
// 签名密钥、解密密钥、游客登录密钥——全部明文写在前端 JS 里
const Vn = {
http_request_key: "jR6dO6fT1yD9zY7u",
http_response_key: "vEukA&w15z4VAD3kAY#fkL#rBnU!WDhN",
guest_skey: "wM5wK1jP3lK7pS2t",
};
直接调用返回 非法请求。从 JS 提取签名函数 N2():
# HMAC-SHA1 签名
msg = f"{token}&{api_path}&{x_user_agent}&{timestamp}&{nonce}"
sign = hmac.new("jR6dO6fT1yD9zY7u".encode(), msg.encode(), hashlib.sha1).hexdigest()
header = f"timestamp={ts};sign={sign};nonce={nonce}"
签名通过了,但响应是加密的:{"data":"vA32vSvstudnfQfwmTn0U+...","hash":true}
从 JS 提取解密函数 ib(),核心是 3 轮 SHA-256 哈希从 response_key + 12 字节 salt 派生 AES key 和 IV。
最大的坑:JS 的 Array.splice() 会修改原数组。 密钥派生过程中有 6 次 splice 操作,每次都改变了数组的内容和长度。必须像 debugger 一样逐步追踪每个变量的状态。
第一次解密成功时返回的内容是:"DevID can not be null!" —— 虽然是个错误信息,但解密本身是对的! 那一刻的成就感难以描述。
游客登录返回 code=6004, "账户禁止登陆"。看似又要卡住了。
但 Claude 逐个测试 160+ 个接口的权限,发现好几个不需要 token:
/api/app/search/hot/words → 200
/api/app/publish/contents → 200 有视频数据!
/api/app/media/home → 需要 id 参数...
# 试试 id=1
api_call("/api/app/media/home", {"id": 1})
# → code=200, mediaList: 2707 items!
# id=2?!
api_call("/api/app/media/home", {"id": 2})
# → code=200, mediaList: 12327 items!!! 原创影视!!
24726 条。不需要登录。签名对了就行。
| 分类 | 视频数 |
|---|---|
| 用户原创 | 2,707 |
| 原创影视 | 12,327 |
| 海外影视 | 5,501 |
| 社交实录 | 3,199 |
| 吃瓜社会 | 821 |
| 动漫 | 171 |
| 合计 | 24,726 |
Redis db=2 存原始 JSON,TiDB madou 库存结构化数据。最终入库 24,342 条(去重后)。
Wayback Machine 存档
↓ wayback_machine_downloader
存档站 HTML (196 页)
↓ grep 提取外部域名 ← 关键线索就藏在这里
发现 mod.run、CloudFront、CDN 域名
↓ Nuxt.js bundle 分析
mod.run API (6 个接口, 49 条)
↓ 用户浏览器抓包
madou.com 新站 API (1 个接口, 68 条)
↓ --resolve 绕过 DNS 死亡
发现 30+ 隐藏接口(需 token)
↓ 存档 HTML 中的 CloudFront 域名
de1j8jm5ajr3l.cloudfront.net ("B站点")
↓ JS bundle (487KB) → 160+ API + 加密配置
破解 HMAC-SHA1 签名
↓ 签名通过
破解 AES-256-CBC 响应加密
↓ 解密成功
发现 media/home 不需要登录
↓ id=1~9 逐个拉取
24,342 条视频数据 → Redis + TiDB
请求签名 (x-api-key):
HMAC-SHA1(
key = "jR6dO6fT1yD9zY7u",
msg = "{token}&{apiPath}&{X-User-Agent}&{timestamp}&{nonce}"
)
响应解密:
Base64(data) → salt(12B) + ciphertext
→ 3轮 SHA-256 派生 AES key(32B) + IV(16B)
→ AES-256-CBC 解密 → PKCS7 unpad → JSON
| 工具 | 用途 |
|---|---|
| wayback_machine_downloader | Wayback Machine 整站镜像 |
curl + --resolve | 绕过 DNS 死亡直连 Cloudflare |
| Python (Cryptodome) | AES-256-CBC 解密 |
| Python (hmac) | HMAC-SHA1 签名 |
| Chrome DevTools | 抓包发现 API |
| Redis | 中间数据存储 |
| TiDB | 结构化数据入库 |
| hs-net (自研) | HTTP 并发扫描(被限流了) |
| hssp (自研) | 爬虫框架,提供 Cryptodome 环境 |
| 成果 | 数量 |
|---|---|
| 存档站静态页面 | 196 页 + 全部静态资源 |
| mod.run 视频数据 | 49 条 |
| madou.com 新站数据 | 68 条 |
| CloudFront 完整内容库 | 24,342 条 |
| M 平台编号视频 | 194 条 / 179 个编号 |
| TG 频道编号 | 212 个 |
| 破解的加密算法 | HMAC-SHA1 + AES-256-CBC |
| 发现的 API 接口 | 160+ 个 |
整个过程中有 4 次 AI 认为"到头了"的时刻:
每一次突破,都不是靠更强的技术力量,而是靠换一个角度重新审视问题:
这就是我说的"思维灵活性"。 工具可以学,算法可以查,但面对"看似无路可走"时选择"再试一个方向"的思维模式——这是 AI 暂时还不具备的。
AI 时代,每个人手里都有一把足够锋利的刀。区别在于,你是对着一堵墙反复砍,还是退后一步,发现旁边有扇没关严的窗。
本文记录于 2026-04-03,madou.com 于前一日(4月2日)宣布永久关停,本文为关停次日的数据抢救全过程。 使用工具:Claude Code (Claude Opus 4.6) + wayback_machine_downloader + curl + Python + Redis + TiDB 整个过程中,人类提供方向和判断,AI 提供技术执行力。这就是 AI 时代的逆向工程。