简介:这是一份面向m3u8视频下载与在线提取需求的实用工具包,提供网页端与脚本端两种使用方式,适合经常处理流媒体视频的内容运营、技术爱好者以及前端开发者。工具通过解析m3u8清单文件,自动获取全部TS分片并合并输出,大幅简化了原本繁琐的下载流程。资源包共47个文件,压缩后仅1.68MB,核心由HTML页面、JavaScript脚本、CSS样式及配置说明构成,同时附带接口示例、部署相关文件与常用图片资源,便于用户快速部署到网站或进行二次开发。包内集成了用户下载脚本、HLS播放器、DPlayer播放器与AES解密脚本,覆盖视频地址解析、播放预览、分片加密解密与最终合并等关键环节,有助于开发者理解整套下载器的实现原理并落地到自己的项目。目前已有5924人学习/下载,无论想快速上手使用,还是借鉴代码思路,都能从这个紧凑而完整的工具包中获得帮助。
1. m3u8 在线下载工具解决的那个实际问题:网页能播却存不下来的视频
一段回放在网页播放器上流畅得毫无问题,导出到本地却得到一个 0 字节文件;一场直播录播明明已经结束,下载工具却永远停在“正在解析”。遇到这些情况,多半不是工具差,而是没搞清 m3u8 在线下载工具的前提:工具并不会凭空变出视频,它要先拿到播放器真正请求的那个 m3u8 索引地址,再按清单把 ts 切片取回本地。这篇文章就按这个标题讲的落地路径写:从浏览器抓出索引、用命令行工具下载、处理 AES-128 加密和防盗链,最后给出我踩过坑后固定的检查习惯。适合保存录播课、整理演示录像、做离线备份的从业者和爱好者;如果你只想一键搞到别人平台的加密内容,本文不解决,也不该解决。
2. 取链是第一道关卡:m3u8 索引结构、抓包路径和地址可用性检查
2.1 看懂 m3u8 清单:哪些行是地址,哪些行是要求
m3u8 本质是一个纯文本播放清单,不像 MP4 那样把音视频数据放在一个文件里。它每一行要么是#开头的标签,要么是一个切片地址。下载工具读这个文件,才知道该按什么顺序、从哪里拉取视频片段。
看一个最常见的媒体列表:
#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:10 #EXT-X-MEDIA-SEQUENCE:0 #EXTINF:10.000, https://cdn.example.com/path/seg_00001.ts #EXTINF:10.000, https://cdn.example.com/path/seg_00002.ts #EXTINF:10.000, https://cdn.example.com/path/seg_00003.ts #EXT-X-ENDLIST这个列表里值得关注的就几行:
| 标签 | 作用 | 下载时要处理的事 |
|---|---|---|
#EXTM3U | 文件头,表示这是标准播放列表 | 第一行不是它,基本可以判定拿错了地址 |
#EXTINF | 后面紧跟的地址对应的切片时长 | 用来预判总时长,也能用来发现列表不完整 |
#EXT-X-KEY | 加密方式、密钥地址和 IV | 出现它就说明切片是 AES-128 加密的 |
#EXT-X-ENDLIST | 列表结束标记 | 没有它说明可能是直播流,下载需要加时长限制 |
还有一种情况,你抓到的第一层 m3u8 里没有#EXTINF,而是很多#EXT-X-STREAM-INF。这是主播放列表,每个 STREAM-INF 指向一条不同码率的子列表:
#EXTM3U #EXT-X-STREAM-INF:BANDWIDTH=2600000,RESOLUTION=1920x1080 https://cdn.example.com/1080p/index.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360 https://cdn.example.com/360p/index.m3u8我一般会先看一眼这类结构,因为很多在线工具提供的“清晰度选择”就是在这一层实现的。命令行工具通常默认选最高码率,想下低清晰度就得手动取子列表地址。
2.2 从播放器页面里扣出真实地址:Network 面板三步抓链
地址不会自己出现在页面源码里,但播放器必须请求它,所以浏览器开发者工具是最稳定的取链入口。我的固定操作顺序是:
- 打开视频播放页,让视频开始播放。按 F12 切到 Network 面板,再按 Ctrl+R 强制刷新,这样播放器发起的第一批请求一定会出现在列表里。
- 过滤框输入
m3u8,把类型切到 Fetch/XHR。如果没结果,切到 Media 再搜一次,新版浏览器会把 HLS 请求归到媒体类型。还是没有,就清空过滤,直接找文件名里带index.m3u8、playlist.m3u8或chunklist.m3u8的项。 - 找到请求后右键复制地址即可,不需要复制完整 cURL。多码率情况下,响应体里出现
#EXT-X-STREAM-INF就说明还需要往下一层找,继续点开 Preview 里的地址再确认一次。
一个容易忽略的点:有些站点是先请求一个 JSON 接口,播放器拿到playlistUrl字段后再去拉 m3u8。这时候 Network 里只搜m3u8可能搜不到,我一般会先搜playlist或m3u8Url这类字段名,找到接口后从响应体里取地址,比来回刷播放器更快。
2.3 地址拿到别急着下载:curl 先验三件事
拿到地址直接扔给下载工具,失败时没人告诉你为什么。我习惯先花十秒验证,用一条 curl 把它拉下来看内容:
curl -sL -o /tmp/m3u8_check.txt \ -w "HTTP状态码: %{http_code} 类型: %{content_type} 大小: %{size_download} 字节\n" \ "https://cdn.example.com/path/index.m3u8" head -n 3 /tmp/m3u8_check.txt这里-o保存响应体,-w打印状态码、Content-Type 和大小,head看前几行。要验证的三件事分别是:
第一,状态码是不是 200。出现 403 说明缺 Referer 或 Cookie,出现 404 说明地址里的路径本身有问题。第二,Content-Type 是不是application/vnd.apple.mpegurl或application/x-mpegURL。如果返回text/html,说明你拿到的不是真实流地址,而是防盗链页面或错误页。第三,响应第一行是不是#EXTM3U,并把整个文件里有几个#EXT-X-KEY记下来,这决定后面下载时要不要处理加密。
这一步能挡掉至少一半的无效下载,也方便在后面的避坑排障时快速定位。地址不对时,不要反复换下载器,先回头检查这里。
3. 把 m3u8 拉成本地视频:三种可复现的下载与合并方式
3.1 不加密的 HLS 用 ffmpeg 一条命令解决
地址没问题、也没有#EXT-X-KEY的时候,最稳的办法是让 ffmpeg 直接处理整个列表:
ffmpeg -hide_banner -y \ -i "https://cdn.example.com/path/index.m3u8" \ -c copy output.mp4-i后面可以直接放网络地址,也可以放本地 m3u8 文件;-c copy表示对视频和音频轨道做流拷贝,不做转码,速度快而且不损失画质;-y是覆盖同名输出文件。加-hide_banner只是为了在日志里少看几行编译信息,排错时真正有用的是-v error,建议改成:
ffmpeg -v error -y \ -i "https://cdn.example.com/path/index.m3u8" \ -c copy output.mp4这样 ffmpeg 只在出错时打印信息,下载过程干净很多。如果给的地址是主播放列表,ffmpeg 会自动选择清晰度最高的子流;想要固定某个码率,就把地址换成子列表那一层的 m3u8。
这里有个边界要知道:-c copy要求源数据的编码格式能被目标容器接受。绝大多数 HLS 里的 H.264 + AAC 写入 MP4 都没问题,但偶尔遇到编码参数奇怪的流,ffmpeg 会报 muxer 不支持。这时先输出成.ts文件再二次封装,比强行转码更省时间:
ffmpeg -v error -y -i "index.m3u8" -c copy output.ts ffmpeg -v error -y -i output.ts -c copy output.mp43.2 带 AES-128 密钥的 m3u8:先看 EXT-X-KEY,再决定是否人工接管
加密的 m3u8 里会出现一行类似这样的内容:
#EXT-X-KEY:METHOD=AES-128,URI="https://cdn.example.com/key.bin",IV=0x9f7c...下载工具要正常工作,必须先请求这个URI拿到密钥,再用 AES-128-CBC 把每个 ts 切片解密。先确认索引里是否加密:
grep -n "EXT-X-KEY" /tmp/m3u8_check.txt大多数情况下,ffmpeg 自己会完成这件事:它读取EXT-X-KEY,请求密钥,解密切片,全程不需要你在命令行里指定密钥。真正要人工介入的,是密钥请求被服务器拦截、导致下载中途报错或花屏的场景。
我的自救流程是先单独把密钥下载下来:
curl -e "https://web.example.com/" -o key.bin \ "https://cdn.example.com/key.bin"-e是带 Referer 请求,很多 CDN 对密钥地址的校验比对切片更严格。下载成功后,启动一个本地 HTTP 服务,把 m3u8 里对应的 URI 改成http://127.0.0.1:8000/key.bin:
python3 -m http.server 8000 & sed 's|URI="https://cdn.example.com/key.bin"|URI="http://127.0.0.1:8000/key.bin"|' \ /tmp/m3u8_check.txt > local_index.m3u8 ffmpeg -v error -y -i local_index.m3u8 -c copy output.mp4需要注意,本地服务只代理密钥文件,ts 切片仍然走原始绝对地址,所以不影响下载速度。若 m3u8 里出现多个#EXT-X-KEY,说明不同切片区段用了不同的密钥,改动时要把每一处都对应替换,不能图省事统一成一个。
3.3 片段多、链接带签名时:curl 按序下载加 concat 合并
有时在线工具的下载速度极慢,或者某个 ts 地址带时效签名,分批拉反而比一条流水线更容易成功。我一般会用脚本按序号下载,再做一次拼接:
cd /tmp/video_work i=0 while IFS= read -r line; do case "$line" in \#*|"") continue ;; esac i=$((i+1)) curl -s --retry 3 -o "seg_$(printf '%04d' "$i").ts" "$line" done < index.m3u8 for f in seg_*.ts; do echo "file '$f'" >> concat.txt done ffmpeg -v error -f concat -safe 0 -i concat.txt -c copy output.ts脚本里grep -v '^#'的替代写法是直接判断行首,凡是以#开头或空行就跳过,剩下的行全是切片地址。--retry 3让 curl 在瞬时网络错误时自动重试,对 CDN 抖动很有用;给切片重命名成固定四位序号,是为了避免签名 URL 里的?auth_key=...使 curl 保存出奇怪的文件名,也保证后续按序合并。
-f concat -safe 0表示用 concat demuxer 读取文本列表,-safe 0允许列表里的相对路径和特殊字符。这一步输出.ts而不是.mp4更稳妥,因为此时内容仍是 TS 封装,之后需要 MP4 再做一次-c copy转封装。
4. 下载时真正会卡住你的四个网络问题:防盗链、直播列表、密钥请求和相对路径
4.1 403 防盗链:给下载请求补上 Referer、User-Agent 和 Cookie
最典型的报错是 ffmpeg 刚开始跑就出现403 Forbidden,或者几秒后所有切片请求都被拒绝。原因通常是 CDN 对请求头做了校验:要求 Referer 必须是播放页域名,有时还会校验 User-Agent 和 Cookie。
我传头的写法是这样:
ffmpeg -v error \ -user_agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" \ -headers $'Referer: https://web.example.com/\r\nCookie: sessionid=abc123\r\n' \ -i "https://cdn.example.com/path/index.m3u8" \ -c copy output.mp4-user_agent单独设置浏览器标识,-headers传剩余请求头。这里必须用$'...'引号,因为-headers要求每个头之间用回车换行分隔,\r\n直接写在双引号里会被当作普通字符而不是换行。末尾也要留一个\r\n。Cookie 从浏览器开发者工具的网络请求里复制,不需要格式化,原样贴进去即可。
如果目标站点只做 Referer 校验,那么只传-headers "Referer: xxx"就够;同时校验 Cookie 时,缺失的表现通常是列表能拿到、切片 403,或者反过来列表 403、切片正常。这种不一致常让人误判,建议宁可一次性全带上。
4.2 没有 ENDLIST 的直播列表:用时长上限来约束下载
在线下载工具遇到直播流时表现得最明显:进度条一直在转,文件迟迟不出现。因为直播 m3u8 没有#EXT-X-ENDLIST,播放器需要反复拉取最新列表,下载工具如果也在等“列表结束”,就会形成死等。
ffmpeg 对它也一样,你不限制,它就会一直拉下去。正确姿势是用-t限制输出时长:
ffmpeg -v error \ -i "https://cdn.example.com/live/index.m3u8" \ -t 60 -c copy clip60s.mp4-t 60表示只输出前 60 秒内容,时间到后 ffmpeg 自然结束。注意它要放在-i之后,作为输出选项生效。另外,直播列表的#EXT-X-MEDIA-SEQUENCE会随着时间推移增大,所以拉流得到的内容总是“从当前时间开始”,而不是从头开始。想要完整回放,得在直播结束后重新抓取带#EXT-X-ENDLIST的回放列表,这时的地址通常和直播地址不同。
4.3 密钥请求同样会被拦:本地起一个 HTTP 服务给 ffmpeg 喂 key
加密 m3u8 还有一个隐蔽坑:ts 切片能正常请求,但下载到一半 ffmpeg 报错,日志里提到 key 或 decrypt。这是因为很多 CDN 对/key.bin这类密钥接口单独做了 Referer 校验,而部分在线工具或旧版本 ffmpeg 请求密钥时没有带上播放页来源。
这种情况先按 3.2 的做法把 key 下载到本地,然后修改 m3u8 里的 URI。改完以后,如果原始 m3u8 里的 ts 是绝对地址,它们仍然直连 CDN 下载,本地服务器只负责把 key 喂给 ffmpeg,不会有带宽损失。
需要确认的一点是:本地的key.bin必须和 m3u8 中加密片段完全对应。有些平台会做密钥轮换,不同时间段生成不同 key,下载旧索引配新 key,解密出来的就是花屏。
4.4 相对路径拼错导致 404:手动把片段地址补全再下载
直接拿浏览器地址栏里的 m3u8 链接去下载,有时列表能解析出来,但每个相对路径的切片都 404。常见原因是 m3u8 里写的是seg_00001.ts或../1080p/seg.ts,而下载工具没有把index.m3u8所在目录当作基路径去拼接。
手动补齐的思路是:取 m3u8 地址里去掉文件名之后的部分作为 base,再给所有不以#开头的行加上前缀。我一般用 sed 处理:
base="https://cdn.example.com/movies/chapter_07/1080p/" sed "s|^\([^#][^\r]*\)$|${base}\1|" original.m3u8 > full.m3u8 ffmpeg -v error -i full.m3u8 -c copy output.mp4正则的意思是把每一行符合条件的整行替换成“base + 原行”,[^#]直接跳过#EXTM3U这些标签行。这里特意用|做 sed 分隔符,是因为 m3u8 里到处是#,用#当分隔符会把标签行也拆坏,这是我之前翻车过的地方。
要注意 base 末尾的斜杠不能漏,否则会拼成path/index.m3u8seg_00001.ts。如果切片路径已经带了子目录,比如segments/seg_1.ts,直接拼 base 也能得到正确完整地址。
5. 避坑清单:m3u8 在线下载最常翻车的五个点及排查习惯
下面这几条只讲技术排障,适用范围是你有权离线保存的视频内容。这些坑里有的是工具设计问题,有的是网络环境问题,按“现象 → 原因 → 解决”的方式记录如下。
5.1 下载完是几 KB 小文件:先确认手里拿的是索引还是页面
现象:下载工具显示“完成”,结果本地上是一个只有几 KB 的 mp4,打开就报错。
原因:地址实际返回的不是视频流,而是防盗链页或 JSON 包装数据。在线下载工具经常把“网络请求完成”误判为“下载成功”,尤其当响应头里没有明确的 MIME 类型时。另一个常见来源是手里拿的是主播放列表,直接把它当切片列表保存了。
解决:回到 2.3 的 curl 验证,第一行必须是#EXTM3U。如果响应体是 HTML,补 Referer 或 Cookie 再试;如果是主播放列表,就再进入子列表地址下载。这个动作每次都做,能省下大量重复失败的时间。
5.2 有画面没声音:音频流和视频流走的是两条 m3u8
现象:视频播放完全正常,画面清晰,但把声音拉到最大也听不到任何内容。
原因:部分 HLS 平台的音频轨道不混在视频切片里,而是在主播放列表里通过#EXT-X-MEDIA:TYPE=AUDIO单独声明,视频层只有画面。如果抓包时手动取了内层视频子列表,自然会下出无声视频。
解决:下载时尽量用主播放列表地址,而不是手工挑出来的内层子列表。已经下完的无声文件,先用 ffprobe 确认轨道:
ffprobe -v error -show_entries stream=codec_type \ -of default=noprint_wrappers=1 output.mp4输出里只有video没有audio,就说明下错了层级。可以回 Network 面板重新找完整 master 列表,或者用 ffmpeg 下载时加-map 0把所有轨道都保留:
ffmpeg -v error -i "master.m3u8" -map 0 -c copy output.mp45.3 解密的画面中途花屏:注意 EXT-X-KEY 的出现位置和 IV 规则
现象:前几个切片正常,播放到某个时间点之后出现绿块、花屏甚至直接中断。
原因:要么是下载器把整段同一个密钥套用到轮换后的切片上,要么是它忽略了 IV。HLS 加密中,如果 m3u8 没有明确给出 IV,默认 IV 使用切片的媒体序号生成,每个切片都不一样。工具只解密第一个 key、后面全用同一个参数,自然在中途崩坏。
解决:先用grep -c "EXT-X-KEY"看密钥出现次数。多次出现说明存在密钥轮换,要用能完整解析每个EXT-X-KEY的下载器或命令。本地手工处理时,每个加密区段都必须配对对应的 key,不能拿到一个 key 就从头用到尾。
5.4 把链接收藏起来第二天再下:签名地址早就过期
现象:用浏览器在线播放没问题,第二天用下载工具跑同一地址,返回 403 或空列表。
原因:很多 CDN 对 m3u8 和 ts 地址会附加短时签名参数,比如auth_key、token、expires。签名失效后,地址仍存在,但服务器拒绝响应。这是后验签名的常见机制,不是工具坏。
解决:拿到地址后立刻下载,不要存档链接。需要长期保留的内容,下载合并成 mp4 之后再归档。这个操作顺序是最重要的习惯:先下载,后整理,不要反过来。
5.5 使用在线页面工具抓不到任何请求:播放器可能根本不走标准 HLS
现象:视频能正常播放,但页面工具看不到任何 m3u8 请求,网络面板里也搜不到m3u8关键字。
原因:部分播放器先通过接口拿到 m3u8 内容,然后用blob:地址播放;或者播放器本身用了非标准封装,自行分段请求。前者在 Network 里能看到一个 blob 类型的媒休请求,但看不到真实索引;后者则需要抓接口而不是抓切片。
解决:先在 Network 里搜playlistUrl、videoUrl、src这类字段名,看接口响应里是否有 m3u8 地址。如果接口数据也被加密,页面工具这条路就走不通。此时可以判断该站点不适合用 m3u8 在线下载工具处理,换用播放器内缓存目录或寻找官方离线渠道,比硬破解更省时也更合规。
6. 进阶:固定一个二十秒的前置检查和一个下载后的验证命令
把偶发成功变成稳定流程,靠的是把检查动作固定成脚本。我现在拿到任何一个 m3u8 地址,都会先跑这个脚本:
#!/usr/bin/env bash # check_hls.sh 用法: bash check_hls.sh "m3u8地址" url="$1" tmp="/tmp/check_hls_$$.m3u8" curl -sL -A "Mozilla/5.0" -o "$tmp" "$url" first=$(head -n 1 "$tmp") if [[ "$first" != "#EXTM3U" ]]; then echo "不是标准 m3u8,返回内容可能被防盗链或接口包装" exit 1 fi echo "切片数量: $(grep -c '#EXTINF' "$tmp")" echo "加密标记: $(grep -c '#EXT-X-KEY' "$tmp")" grep -q '#EXT-X-ENDLIST' "$tmp" \ && echo "列表状态: 已完结" \ || echo "列表状态: 直播或未完结,需要限时下载" grep -m1 '#EXT-X-KEY' "$tmp" || true-sL表示安静地跟随重定向,$$生成临时文件名避免多人共用同一路径冲突。脚本输出四样东西:第一行是否合法、切片总量、加密次数、是否还有结束标记。据此决定下载命令要不要带 Referer、要不要处理密钥、要不要加-t限时。
下载完成后的验证同样固定。我一般只跑一条 ffprobe 命令:
ffprobe -v error \ -show_entries stream=codec_type,codec_name \ -show_entries format=duration,format_name \ -of default=noprint_wrappers=1 output.mp4看输出里有没有同时出现video和audio,duration 是否接近 m3u8 中所有#EXTINF时长的总和。相差 1 到 2 秒是正常的,差很多说明有切片丢失或直播被中途截断。
这个习惯是我带项目时从一次直播回放下载中总结出来的:当时连下三次都无声,才发现列表里视频和音频是分离的,之后 ffmpeg 命令加了-map 0就好了。现在固定流程是“先 curl 看第一行,再 grep 看密钥,跑完 ffprobe 看轨道”,三步做完才归档,基本没有再为格式问题返工过。希望帮到你。
本文还有配套的精品资源,点击获取