输入一个网页地址 → 用真实浏览器把页面跑起来 → 抓出它真正在播的视频 → 多线程下载 → 按需要转成 MP4 / 只留画面 / 只留声音。
图形界面(tkinter),只用 Python 标准库,没有 pip 依赖。
双击 run.bat。或者命令行 python app.py。
| 需要 | 作用 | 不装的后果 |
|---|---|---|
| Python 3.10+ | 跑界面(tkinter,标准库自带) | 没法运行 |
| ffmpeg | 转 MP4 / 去音轨 / 抽音轨 / 合并音视频 | 只能原样保存,格式相关的都做不了 |
| Edge 或 Chrome | 嗅探要用它 | 没法嗅探 |
winget install Gyan.FFmpeg
装完重开程序,启动时日志会告诉你 ffmpeg 找到没有。
- 填网页地址,点「浏览器嗅探」(或直接回车);
- 程序会临时开一个浏览器把页面跑起来,等 5~30 秒;
- 中间列出抓到的地址,按体积从大到小排序,默认选中第一个(通常就是主视频);
- 选好「下载内容」和「下载线程」;
- 点「下载选中」;成品在
downloads\,双击任务行可直接打开文件。
| 选项 | 说明 |
|---|---|
| 视频 + 音频(MP4) | 默认。音视频是分开的两条流时,会自动补下声音轨再合并 |
| 仅视频(无声 MP4) | 只要画面,用 ffmpeg 去掉音轨 |
| 仅音频(.m4a 原样) | 只要声音,不重编码(原样搬,瞬间完成) |
| 仅音频(.mp3 转码) | 只要声音,转成 mp3(兼容性最好,但会重编码) |
选「仅音频」时,如果站点本来就是音视频分流(抖音这类),程序只会去下那条声音轨, 完全不去碰视频 —— 实测同一个视频:完整 48 MB / 5.2 秒,而只要声音是 12 MB / 2.9 秒。
因为另外两条路都不成立:
- 直接看 HTML(静态嗅探):对抖音这类站点根本不可能抓到。 实测那类页面 72 KB 里连一条 http 链接都没有 —— 视频地址是 JS 跑起来之后 才另外调接口拿的。留着它只会"看着能用、在真正需要的站点上失效"。
- yt-dlp:对抖音要求
Fresh cookies (not necessarily logged in) are needed, 而 Chrome/Edge 127+ 之后 Cookie 库加了应用绑定加密,yt-dlp 直接读会报Failed to decrypt with DPAPI(已知问题 #10927, 换最新版也无解)。绕过去要手动导出cookies.txt,麻烦且不稳。
浏览器嗅探把这些问题一次解决掉,而且不需要碰任何 Cookie。
程序启动一个真实的 Edge/Chrome(默认无头),像一个真人那样打开页面、 让 JS 跑完、让视频真的播起来,然后:
- 问 DOM「你在播什么」 —— 读
document.title和<video>信息; - 看网络层 —— 从 CDP(Chrome DevTools Protocol)收下所有媒体响应;
- 问服务器真实大小 —— 对每个候选发一次 1 字节的
Range请求拿真实体积。 这一步很关键:浏览器只缓冲开头一小段(实测 35 MB 的视频只传了约 700 KB), 光看"传输了多少"会把大小估错几十倍。页面上还常有个一两百 KB 的装饰性小视频, 不按真实体积排就会把它当主视频; - 按体积排序 —— 真正的视频 35.6 MB,音频轨 12.3 MB,装饰性小视频 194.5 KB;
- 音视频分流就合并 —— DASH 站点把画面和声音拆成两条流分别下,
播放器再实时合起来。只下画面那条,出来的 MP4 就是哑的。
程序识别出来后会把声音轨也下了,用
-map 0:v:0 -map 1:a:0 -c copy无损合成。
拿到地址之后下载仍然是我们自己下的(多线程 + ffmpeg), 浏览器的作用只是"把真实地址问出来"。
勾上 「嗅探时显示浏览器窗口」,程序会把浏览器窗口显示出来,
你可以手动登录或过验证码。登录状态保存在 browser_profile\ 里,下次不用再来一遍。
「下载线程」可设 1 / 2 / 4 / 8 / 16 / 32,默认 8:
用 HTTP Range 把一个文件切成 N 段并发下,各线程往同一文件的不同偏移直接写。
另外多个视频会同时下载(2 个任务并行)。
分段判断错了会直接产出坏文件,所以每一层都退守:
- 先用
Range: bytes=0-0探测(不用 HEAD,很多服务器拿 405 拒掉); 回206才算支持,回200说明服务器把整份塞回来了 → 退回单线程; - 有的服务器探测时回 206、真要分段时又回 200 —— 下载途中立刻识别,退回单线程重下;
- 文件小于 2 MB 不分段;
- 下完校验大小必须等于
Content-Length,少一个字节都不当成功; - 请求带
Accept-Encoding: identity—— 服务器一压缩,字节偏移就对不上文件偏移了。
多线程不是万能的:服务器不限速时,8 线程和 1 线程差不多; 真正提速的是「服务器限制单条连接速度」的场景。 注:HLS(
.m3u8)由 ffmpeg 拉流,线程数不参与。
ffmpeg 能把几乎任何编码塞进 MP4(连 PCM、DTS 都写得进去),但播放器放不出来。 所以不看「ffmpeg 有没有报错」,而是按编码白名单判断:
| 级别 | 做法 | 场景 |
|---|---|---|
| 1 | -c copy 无损重封装 |
MKV(H.264+AAC) → MP4,秒级,画质零损失 |
| 2 | -c:v copy -c:a aac 只转音频 |
MKV+DTS/FLAC/PCM,几秒,视频不动 |
| 3 | libx264 + aac 整体重编码 |
视频编码 MP4 放不了(MPEG-2、VP8) |
每一级做完都用 ffprobe 回头验产出:「不要音轨」时验证确实没音轨, 「只要声音」时验证确实没画面 —— 不满足就自动降级重来。
HLS 清晰度:ffmpeg 拿到主播放列表只取第一个变体,而主列表常把低清排前面 (实测某个流有 1080p,ffmpeg 却只下 720p)。所以程序自己解析主列表挑最高清晰度。 例外:主列表带独立音频轨时不挑,原样交给 ffmpeg,否则会把音轨丢掉。
- 无头浏览器可能被个别站点识别;勾「显示浏览器窗口」通常能绕过;
- 首次嗅探一个站点要等 5~30 秒(等页面把视频跑起来);
- 标题用
document.title,不做猜测性裁剪(视频标题 - 某某网校的后缀会留着), 因为「第1讲 - 入门」这种正经标题也含-,误删比留着噪音更糟。想干净自己改「保存为」; - 不做断点续传,失败重试是从头重下;
- 页面里的地址有时效(带签名),嗅探完要尽快下,放久了会 403;
- 不支持
blob:/data:源,不支持 DRM 加密视频; - 直播流会一直下,需要手动「取消选中任务」。
WebVideoSniffer/
├─ run.bat 一键启动(用 pythonw,不留黑框)
├─ app.py 图形界面(tkinter)
├─ browser.py 浏览器嗅探:手写最小 WebSocket + CDP 驱动真实 Edge/Chrome
├─ engine.py 引擎:多线程分段下载 / HLS 拉流 / 转 MP4 / 去音轨 / 抽音轨 / 合并
├─ downloads/ 成品目录(自动创建)
├─ browser_profile/ 浏览器嗅探用的独立配置(登录状态存这儿,自动创建)
└─ README.md 本文件
依赖:只有 Python 标准库。 外部程序只用到 ffmpeg 和浏览器。
抓不到任何地址 勾上「嗅探时显示浏览器窗口」,看看页面是不是要求登录、过验证码,或者视频要手动点播放。 有些站点在无头模式下行为不同。
下载报「服务器返回的是网页而不是视频」 地址过期了(这类地址常带签名和时效)。重新嗅探一次即可。
转出来的 MP4 没声音 不会发生 —— 音视频分流会自动合并,音频编码不合适会自动转 AAC。 真遇到了请把日志贴出来。
多线程开了没用 / 更慢了 说明这个服务器不限单连接速度,或线程太多被限流。调回 1~4 试试。
选了「仅音频」还是把视频下了 只在该站点确实是音视频分流、且程序识别出了那条声音轨时才能跳过视频。 单一完整流的站点必须先把文件下下来再抽音轨,做不到"只下声音"。
请尊重网站版权,只下载你有权保存的内容。本工具不破解 DRM。