1. 拆解需求:小程序短剧下载到底难在哪
1.1 为什么短剧内容不能直接右键保存
做过小程序相关开发或者逆向分析的人都知道,微信小程序的媒体资源加载方式和普通网页有本质区别。普通网页里一个<video>标签,源地址往往直接写在 HTML 里,打开开发者工具就能看到.mp4链接。但小程序不一样,它的页面渲染层和逻辑层是分离的,视频地址通常由 JavaScript 在运行时动态拼接,而且很多短剧平台会对资源链接做时效性签名和防盗链校验。
具体来说,短剧类小程序常见的保护手段有这么几层:第一层是接口鉴权,请求视频地址时需要携带token或者session_id;第二层是 URL 签名,链接里带sign、expires这类参数,过期就失效;第三层是 Referer 校验,服务器会检查请求来源是不是自家域名;第四层是分片传输,视频被切成若干.ts片段,通过.m3u8索引文件组织。这四层叠加下来,你就算侥幸拿到一个链接,复制到浏览器里大概率也是 403 或者播放几秒就断。
所以整个任务的核心不是“下载”这个动作本身,而是如何完整地还原客户端请求的上下文,把鉴权信息、签名参数、请求头都补齐,让服务器认为你就是那个正常播放的小程序。这就是为什么需要抓包工具配合 Python 脚本来做——抓包负责“看清楚”,Python 负责“复现出来”。
1.2 抓包工具选型:为什么是 Charles 而不是 Fiddler 或 Wireshark
热词里同时出现了 Charles、Fiddler、Wireshark、tcpdump,这几个工具我都深度用过,这里说下我的选型逻辑。
Wireshark 和 tcpdump 属于网络层抓包,拿到的是原始 TCP/IP 数据包。对于 HTTPS 流量,你需要配置密钥日志才能解密,而且解出来的数据是二进制流,要自己重组 HTTP 报文,分析成本极高。它适合排查底层网络问题,不适合做应用层协议分析。
Fiddler 是老牌工具,Windows 上体验很好,但它的证书机制在移动端配置时偶尔会有兼容性问题,尤其是 Android 高版本系统对用户证书的限制越来越严。
Charles 的优势在于:中间人代理模式成熟、证书安装流程清晰、对移动端支持好、能直接看到请求响应明文、支持重写和断点。对于小程序这种走 HTTPS 的场景,Charles 基本是首选。热词里提到的“charles 抓包都出现 unknown”和“charles 证书安装过了 windows 抓包还是 unknown”,这两个问题后面我会专门讲排查方法,这是新手最容易卡住的地方。
至于“charles 雷电模拟器”这个组合,是因为很多人没有多余的安卓真机,用雷电模拟器跑微信,再让模拟器走 Charles 代理,这样抓包环境更可控。这个方案可行,但要注意模拟器的网络模式设置。
1.3 整体技术路线图
把整个流程拆成阶段来看,逻辑会清晰很多:
| 阶段 | 目标 | 核心工具 | 关键产出 |
|---|---|---|---|
| 环境搭建 | 让 Charles 能解密 HTTPS | Charles + 证书 | 可读的请求明文 |
| 流量捕获 | 定位视频相关接口 | Charles + 微信 | 接口 URL 和参数 |
| 协议分析 | 搞清鉴权和签名逻辑 | Charles + 浏览器 | 请求头、参数规律 |
| 脚本复现 | 用 Python 批量下载 | requests + ffmpeg | 完整视频文件 |
| 异常处理 | 应对失效和分片 | Python 重试逻辑 | 稳定下载流程 |
这个路线不是拍脑袋定的,是我踩过几次坑之后总结的顺序。很多人一上来就写 Python 脚本,结果发现请求一直 403,回头再去看抓包,发现少了个Referer头。先把流量看明白,再动手写代码,这个顺序不能反。
2. 环境搭建:Charles 抓包配置的完整流程
2.1 Charles 安装与基础代理设置
Charles 的安装本身没什么难度,官网下载对应系统版本,一路下一步就行。真正需要配置的是代理参数。打开 Charles,进入Proxy -> Proxy Settings,默认端口是 8888,这个端口记住,后面手机和模拟器都要填。
这里有个细节:Enable transparent HTTP proxying这个选项建议勾上,它能让 Charles 自动处理一些非标准端口的流量。另外在Proxy -> SSL Proxying Settings里,先添加一条*:443的规则,意思是拦截所有 443 端口的 HTTPS 流量。但注意,不要一开始就开全局 SSL 代理,因为微信的很多流量是加密的,全开会导致 Charles 里一堆 unknown 和报错,反而干扰你定位目标接口。我的做法是先不开启,等确认了目标域名再针对性添加。
2.2 证书安装:电脑端和移动端的关键差异
证书是 Charles 抓 HTTPS 的核心。电脑端安装相对简单:Help -> SSL Proxying -> Install Charles Root Certificate,然后导入到系统信任区。Windows 上要手动把证书拖到“受信任的根证书颁发机构”,macOS 上要在钥匙串里设置为“始终信任”。
移动端就麻烦一些。以 Android 为例,步骤是:手机连上和电脑同一个 WiFi,WiFi 设置里配置手动代理,主机名填电脑 IP,端口填 8888。然后在手机浏览器访问chls.pro/ssl下载证书。下载完之后,关键来了:Android 7.0 以上,用户安装的证书默认不被应用信任,只有系统级证书才行。这就是为什么很多人证书装了,抓包还是 unknown。
解决办法有两个:一是用低版本 Android 或者模拟器(雷电模拟器默认是 Android 7,可以改),二是把证书装到系统分区(需要 root)。我一般推荐用雷电模拟器,因为它可以方便地切换 Android 版本,而且 root 权限好拿。热词里“charles 雷电模拟器”能成为搜索词,说明这条路是大家验证过的。
注意:证书安装完成后,一定要在 Charles 里确认
SSL Proxying已经对目标域名生效。可以在Proxy -> SSL Proxying Settings里看到已启用的规则列表。
2.3 雷电模拟器的网络配置要点
雷电模拟器跑微信抓包,有几个设置必须调对。第一,模拟器的网络模式建议用“桥接模式”,这样它和电脑在同一网段,代理设置更稳定。第二,在模拟器的 WiFi 设置里,长按已连接的网络,修改网络,高级选项里把代理改成手动,填电脑的局域网 IP 和 8888 端口。第三,如果模拟器里微信打不开或者网络异常,检查电脑防火墙有没有拦截 8888 端口。
我实测下来,雷电模拟器 + Charles 的组合在抓小程序流量时成功率很高,因为模拟器环境干净,没有真机上那些乱七八糟的后台流量干扰。但要注意,模拟器里的微信版本不要太新,太新的版本对代理的检测更严格,有时候会直接拒绝连接。
3. 流量分析:从一堆请求里找到视频接口
3.1 如何快速定位短剧视频请求
打开 Charles 开始录制,然后在模拟器里打开目标短剧小程序,点开一集开始播放。这时候 Charles 里会刷出大量请求,怎么快速找到视频相关的?
我的经验是看几个特征:请求 URL 里带.m3u8、.mp4、.ts的,这是最直接的;响应体大小特别大的,视频分片通常几百 KB 到几 MB;Content-Type 是video/mp4或application/vnd.apple.mpegurl的。在 Charles 里可以按大小排序,视频请求一般排在前列。
如果短剧用的是 m3u8 分片,你会先看到一个.m3u8请求,返回的是一个文本列表,里面列着所有.ts分片的地址。然后播放器会逐个请求这些.ts文件。这时候你要做的是把.m3u8的完整 URL 和请求头记下来,因为下载分片时需要同样的鉴权信息。
3.2 读懂请求头里的鉴权信息
找到视频请求后,右键Copy cURL Request,把请求完整复制出来。这里面藏着所有你需要的信息。重点看这几个头:
Authorization或token:身份凭证,通常是一串 JWT 或者自定义字符串Referer:来源页,很多服务器会校验这个User-Agent:客户端标识,有些服务器会检查是不是微信Cookie:会话信息,可能包含session_idRange:分片请求时的字节范围
还有一个容易忽略的是 URL 本身的查询参数,比如?sign=xxx&t=xxx&us=xxx。这些参数往往是动态生成的,有有效期。你需要分析它们的生成规律,或者干脆在有效期内快速下载。
3.3 判断视频是直链还是分片
这一步决定了你后面 Python 脚本的写法。判断方法很简单:看响应内容。如果是一个完整的.mp4文件,响应体直接就是二进制视频数据,那说明是直链,下载最简单。如果响应是一个文本,里面有很多.ts结尾的行,那就是 m3u8 分片,需要先下载索引再逐个下载分片最后合并。
还有一种情况是.mpd格式,这是 DASH 协议的分片,处理起来比 m3u8 复杂一些,但原理类似。短剧小程序目前主流还是 m3u8,因为兼容性好、实现简单。
| 类型 | 索引文件 | 分片格式 | 合并方式 |
|---|---|---|---|
| 直链 MP4 | 无 | 无 | 直接保存 |
| HLS | .m3u8 | .ts | ffmpeg 合并 |
| DASH | .mpd | .m4s | ffmpeg 合并 |
4. Python 脚本实现:从单集下载到批量处理
4.1 请求复现:把 cURL 翻译成 Python 代码
拿到 cURL 之后,最省事的办法是用工具自动转换。网上有很多 cURL to Python 的在线工具,但我不建议完全依赖它们,因为生成的代码往往不够健壮。我的做法是手动提取关键部分,用requests库重写。
核心代码结构大概是这样:
import requests headers = { "User-Agent": "Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 ...", "Referer": "https://servicewechat.com/xxxxx", "Authorization": "Bearer xxxxx", } url = "https://example.com/video/xxxxx.m3u8" resp = requests.get(url, headers=headers, timeout=15) print(resp.status_code) print(resp.text[:500])先跑通这一个请求,确认能拿到 m3u8 内容,再往下做。如果返回 403,就逐个检查请求头,看看少了哪个。最常见的就是 Referer 和 User-Agent,这两个补上基本能解决大部分问题。
4.2 m3u8 解析与分片下载逻辑
拿到 m3u8 文本后,解析出所有.ts分片地址。m3u8 的格式很简单,以#开头的行是元信息,不以#开头的行就是分片地址。有些 m3u8 里的地址是相对路径,需要和 m3u8 的 URL 做拼接。
import os from urllib.parse import urljoin base_url = "https://example.com/video/xxxxx.m3u8" m3u8_text = resp.text ts_urls = [] for line in m3u8_text.splitlines(): line = line.strip() if line and not line.startswith("#"): ts_urls.append(urljoin(base_url, line)) print(f"共 {len(ts_urls)} 个分片")下载分片时,建议加一个重试机制,因为网络抖动很常见。每个分片保存为独立的.ts文件,文件名按序号命名,方便后面合并。
os.makedirs("ts_cache", exist_ok=True) for i, ts_url in enumerate(ts_urls): for attempt in range(3): try: r = requests.get(ts_url, headers=headers, timeout=20) if r.status_code == 200: with open(f"ts_cache/{i:04d}.ts", "wb") as f: f.write(r.content) break except Exception as e: print(f"分片 {i} 第 {attempt+1} 次失败: {e}")这里有个细节:分片下载最好控制并发数。用ThreadPoolExecutor开 4 到 8 个线程就够了,开太多容易被服务器限流,反而更慢。我试过开 32 个线程,结果一半请求被拒,得不偿失。
4.3 分片合并与格式转换
所有分片下载完后,用 ffmpeg 合并。ffmpeg 的 concat 协议可以直接读取一个文件列表来合并:
# 生成文件列表 for f in ts_cache/*.ts; do echo "file '$f'" >> filelist.txt; done # 合并 ffmpeg -f concat -safe 0 -i filelist.txt -c copy output.mp4-c copy表示不重新编码,直接复制流,速度极快。合并出来的 mp4 可以直接播放。如果遇到音画不同步或者时间戳问题,去掉-c copy,让它重新编码,但会慢很多。
提示:ffmpeg 需要提前安装并加入系统 PATH。Windows 上可以去官网下载编译好的版本,解压后把 bin 目录加到环境变量里。
4.4 批量下载的队列管理
如果要下载整部短剧,需要把每一集的 m3u8 地址都拿到。这些地址通常在剧集列表接口里返回,或者在播放页面的网络请求里能抓到。把地址收集到一个列表里,然后循环调用上面的下载函数。
批量下载要注意两点:一是加延时,每集之间 sleep 几秒,避免触发风控;二是断点续传,记录已下载的集数,中断后可以从上次的位置继续。我一般用一个简单的 JSON 文件记录进度:
import json progress_file = "progress.json" def load_progress(): if os.path.exists(progress_file): with open(progress_file, "r") as f: return json.load(f) return {"completed": []} def save_progress(progress): with open(progress_file, "w") as f: json.dump(progress, f)这样即使脚本中途挂了,重新跑的时候也不会重复下载已经完成的集数。
5. 常见问题与排查技巧实录
5.1 Charles 显示 unknown 的几种原因
“charles 抓包都出现 unknown”是搜索热词里高频出现的问题。unknown 的意思是 Charles 无法解密这个 HTTPS 请求,原因通常有这几个:
第一,SSL Proxying 没有配置对应的域名。Charles 默认只解密你手动添加的域名,其他域名显示 unknown 是正常的。解决办法是在SSL Proxying Settings里加上目标域名,或者直接加*:443通配。
第二,证书没有真正被信任。电脑端要检查证书是否在“受信任的根证书颁发机构”里;移动端要确认是系统证书还是用户证书。Android 7 以上用户证书不被应用信任,这是最常见的坑。
第三,应用使用了证书固定(SSL Pinning)。有些小程序会校验服务器证书的指纹,这种情况下 Charles 的中间人证书会被拒绝。应对方法需要更高级的手段,比如 hook 掉证书校验逻辑,但这超出了本文的范围,而且不同小程序情况不同。
第四,代理没有生效。检查手机或模拟器的代理设置是否正确,电脑防火墙是否放行了 8888 端口。
5.2 请求返回 403 的排查顺序
Python 脚本跑起来返回 403,按这个顺序排查:
- 检查
Referer头是否和抓包时一致 - 检查
User-Agent是否完整复制 - 检查
Authorization或token是否过期 - 检查 URL 里的签名参数是否还在有效期内
- 检查是否有
Cookie遗漏 - 检查请求频率是否过高被限流
我遇到最多的情况是签名过期。短剧平台的视频链接签名有效期通常只有几分钟到几十分钟,如果你抓包后隔了很久才跑脚本,链接早就失效了。解决办法是抓包后尽快测试,或者分析签名生成算法在脚本里动态生成。
5.3 分片下载中断和合并失败的应对
分片下载中断的原因一般是网络波动或者服务器限流。应对策略是每个分片独立重试,并且记录哪些分片失败了,最后统一补下。合并失败最常见的原因是分片文件损坏或者不完整,可以用 ffmpeg 的-err_detect参数来忽略一些错误,但最好的办法还是确保每个分片都完整下载。
还有一个坑是分片顺序。m3u8 里的分片顺序就是播放顺序,但如果你用多线程下载,保存的文件名一定要按索引编号,不能按下载完成顺序。我见过有人用时间戳命名,结果合并出来顺序全乱。
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| Charles 显示 unknown | 证书未信任或域名未配置 | 检查证书信任状态,添加 SSL Proxying 规则 |
| 请求返回 403 | 请求头缺失或签名过期 | 补全 Referer/UA,重新抓包获取新链接 |
| 分片下载失败 | 网络抖动或限流 | 降低并发,增加重试次数 |
| 合并后无法播放 | 分片损坏或顺序错误 | 校验分片完整性,按索引顺序合并 |
| 脚本跑一会就断 | token 过期 | 加入 token 刷新逻辑或缩短下载间隔 |
5.4 几个我踩过的坑和独家技巧
第一个坑:微信小程序的 User-Agent 不是固定的。不同版本的微信、不同系统,UA 都不一样。如果你直接复制网上的 UA,很可能被识别。正确做法是从 Charles 里复制真实的 UA。
第二个坑:有些短剧平台会对 IP 做限制。同一 IP 短时间内请求太多,会被临时封禁。解决办法是控制下载速度,或者换网络环境。这个我没有深入测试过换 IP 的方案,因为涉及的东西比较敏感,建议大家还是控制频率为主。
第三个技巧:用 Charles 的 Map Local 功能做调试。当你怀疑某个参数的问题时,可以把响应映射到本地文件,反复测试脚本逻辑,不用每次都重新抓包。这个功能在Tools -> Map Local里配置。
第四个技巧:保存 Charles 会话。抓包分析往往不是一次能搞定的,把会话保存下来(File -> Save Session),下次直接打开继续分析,不用重新操作一遍小程序。
6. 合规提醒与技术边界
6.1 技术学习的合理范围
写到这里必须说清楚:这套技术方案的目的是学习网络协议分析、HTTP 请求复现和 Python 自动化脚本编写。抓包分析本身是网络工程和逆向工程中的基础技能,广泛应用于接口调试、性能优化、安全测试等正当场景。
但把技术用于下载有版权保护的内容,或者绕过平台的付费机制,这涉及法律风险。短剧内容通常有明确的版权归属,付费观看是正常的商业模式。技术能力应该用在正道上,比如帮自己的项目做接口调试,或者分析自己有权访问的数据。
6.2 替代的学习路径
如果你想练习抓包和 Python 脚本,有很多合法的场景:抓取公开的天气 API 数据、分析自己网站的访问日志、调试自己开发的移动应用接口。这些场景同样能锻炼请求分析、参数构造、异常处理的能力,而且没有任何合规风险。
我在实际工作中,抓包用得最多的场景是排查线上接口问题。比如用户反馈某个功能异常,但日志里看不出问题,这时候抓包看实际请求和响应,往往能快速定位。这个技能的价值在这里,而不是在下载别人家的付费内容。
6.3 关于工具使用的建议
Charles、Fiddler、Wireshark 这些工具都是正规的网络分析软件,广泛应用于开发、测试、运维领域。学习它们的使用方法是提升技术能力的正当途径。但工具本身中性,怎么用取决于人。我的建议是:把精力放在理解协议原理和提升工程能力上,而不是研究怎么突破别人的防护。
Python 的 requests 库、ffmpeg 的音视频处理、多线程下载的并发控制,这些都是非常有价值的工程技能。把它们用在正经项目上,比如做一个自己的媒体资源管理工具,或者给公司做一个接口自动化测试框架,这些才是能写进简历、能提升职业竞争力的东西。
最后分享一个我在实际项目中的体会:抓包分析最大的价值不是拿到某个具体的数据,而是培养一种“看透请求本质”的思维方式。当你能从一个 URL 和几个请求头里读出整个鉴权逻辑的时候,这种能力在任何后端开发、接口对接、问题排查的场景里都用得上。这才是花时间研究这些东西的真正回报。