B站视频下载:DASH音视频分离与ffmpeg合并实战

发布时间:2026/11/2 20:41:13
B站视频下载:DASH音视频分离与ffmpeg合并实战 1. 先把“下载”这件事的本质看透1.1 右键另存为拿不到文件问题出在哪很多人第一次尝试 B站视频下载动作都高度一致在播放画面上点右键指望蹦出一个“视频另存为”。结果要么菜单里根本没有这一项要么存下来的是一个几百字节的网页文件双击打开还是跳回浏览器。这不是操作姿势不对而是从根上搞错了对象。现代视频网站在浏览器里播放的内容并不是一个躺在 HTML 里的video srcxxx.mp4标签播放器只是一层外壳真正的画面数据是播放器在运行时向服务器动态请求、边下边播的。换句话说页面里压根没有“一个完整的视频文件”这个概念你右键另存存的只能是外壳。所以理解 B站视频下载的第一步是先把心理预期从“找一个文件”切换到“还原一次播放过程”。浏览器能播出来说明数据一定经过了你的网络我们要做的就是把这条数据通路上的原料截下来再按原来的方式拼回去。这个思路一旦建立后面所有的工具选择、参数配置、报错排查都会变得有迹可循而不是靠试。我见过太多人卡在这一步反复换工具却始终不明白为什么失败本质就是没分清“页面”和“流”是两码事。1.2 音视频分离传输是现代视频站的基本盘早期的视频网站确实是一个 FLV 或 MP4 文件从头拉到尾那种结构下载起来非常简单抓到一个地址就能整段拖下来。现在主流站点普遍采用 DASHDynamic Adaptive Streaming over HTTP基于 HTTP 的动态自适应流方案它的核心特征是画面和声音是两条完全独立的流各自切片、各自请求、各自缓存。播放器根据当前网速动态挑选合适清晰度的分片网速好就切到高码率网速差就降到低码率这样卡顿少、起播快。这个设计对观看体验是好事对下载来说就多了一道工序。你抓到的往往是两个地址一个video轨一个audio轨两者都是.m4s后缀的分片容器。如果只下了视频轨播放器打开后就是一段默片只下音频轨那就是一个纯音轨文件。很多人第一次操作时看到“下载成功但没声音”急得去换播放器、换解码器其实文件本身就是不完整的问题出在只拿了一半原料。提示判断一个站点的视频结构最快的办法是看抓到的媒体请求数量。如果视频和音频是两个不同地址、体积各自独立那基本可以确定是分离传输必须走合并流程。1.3 三种编码格式直接决定你的文件能不能播同样是 1080PB站视频下载下来可能是三种完全不同的编码AVC也就是 H.264、HEVCH.265、AV1。站方提供多套编码是为了兼顾不同设备的解码能力和带宽成本但对下载者来说这三者的兼容性差异非常大选错了会出现“我这儿能播、发到别人手机上黑屏”的尴尬。AVC 是兼容性最好的选择十几年前的设备都能硬解剪辑软件、播放器、手机、电视通吃代价是同画质下体积偏大。HEVC 压缩效率比 AVC 高约 30% 到 50%体积小很多但对老设备不友好部分安卓机和 Windows 需要额外装解码器才能播。AV1 是更新的开放格式压缩效率最高可惜生态支持还在爬坡很多剪辑软件导入直接报错。我的建议很明确如果你下载是为了存档、剪辑、二次加工或者发给别人看优先选 AVC如果纯粹是自己在一个确认支持 HEVC 的设备上看追求省空间那再考虑 HEVC。别为了省那 30% 的空间换来一堆兼容性麻烦这笔账不划算。编码类型站内常见标识兼容性同画质体积适用场景AVC / H.264codecid 7极好几乎通吃基准偏大剪辑、分发、长期存档HEVC / H.265codecid 12中等老设备吃力约小 30%~50%自用观看、移动端AV1codecid 13较差生态在建设中最小尝鲜、专业播放环境1.4 先把边界划清楚再谈技术这里必须说一句可能不太讨喜但很重要的话技术上能做到不等于任何场景都该做。我自己的原则是三条线——自己上传的内容随便下那是你自己的劳动成果备份、留档、换平台迁移都天经地义明确获得作者授权的内容可以下很多创作者会主动说明允许搬运或转载平台明确标注可下载的内容正常下。除此之外的内容技术上能解析出来但把它用于二次上传、剪辑搬运、商业用途那就是另一回事了跟工具本身无关。我之所以在开头就讲这个是因为它直接影响你的技术路线选择。如果只是备份自己的投稿你会更关心原画质、原始音频采样率、字幕轨道完整性如果是学习用途看一遍就删那其实根本不需要下载收藏夹和稍后再看够用了。目的不同参数的取舍完全不同别一上来就埋头搞技术先把“我要这个文件干什么”想清楚能省掉一大半无用功。2. 定位真实播放地址抓包这一关怎么过2.1 开发者工具的正确打开方式整个流程里最容易劝退新手的就是这一步但其实逻辑极简让浏览器自己把地址找出来你只负责看。打开视频页面按下 F12或者右键“检查”切到 Network网络面板然后做两件容易被忽略的小事——先点一下清空按钮把已有记录清掉再按 F5 刷新页面。不清空的话几百条请求混在一起你要找的那两条会被淹没。刷新之后在过滤器里依次尝试media、xhr、fetch。media分类下会直接列出浏览器正在请求的媒体资源通常能看到两条体积较大的记录类型标注为fetch或media域名往往和主站不同。这时候不要急着复制先把请求的完整信息看一遍Request URL、Request Headers、Response 三个标签页后面全要靠它们。特别是 Request Headers很多人失败就是因为漏了这里的某一项后面会专门讲。如果media过滤器什么都没抓到那就换思路去看xhr里的接口请求。播放器在起播前一定会先发一个“我要这个视频的播放地址”的请求接口名里通常带playurl这类字样响应是一大段 JSON。找到它后面的活儿就变成读 JSON 了比瞎猜可靠得多。2.2 从 playurl 响应里挑出你要的那几条流打开那个接口响应的 Preview 或 Response 标签你会看到一层层嵌套的 JSON。别被吓到真正要用的字段就那么几个data.dash.video是一个数组每个元素代表一条视频轨对应一种清晰度和一种编码data.dash.audio同理代表音频轨每条轨里的baseUrl是主地址backupUrl是一个数组形式的备用地址主地址抽风时可以顶上id字段对应清晰度档位常见的有 16360P、32480P、64720P、801080P、1121080P、1161080P60、1204Kcodecid对应编码7 是 AVC12 是 HEVC13 是 AV1bandwidth是这条流的码率单位是 bit/s这个数字后面估算体积要用到。挑选的优先级我一般这么排先按id锁定清晰度再按codecid筛编码最后看bandwidth是不是合理。有时候同一清晰度会返回多条 AVC 记录码率略有差异选高的那条。音频轨通常只有一两条选bandwidth最大的即可音频码率差距不大但高码率的听感确实更扎实。举个实际的估算例子。假设你选中的视频轨bandwidth是 2,000,000也就是 2 Mbps。换算成字节2,000,000 ÷ 8 250,000 B/s约等于 244 KB/s。一个 10 分钟的视频600 秒 × 244 KB ≈ 146 MB。音频轨假设 128 Kbps即 16 KB/s600 秒约 9.4 MB。合计大概 155 MB。提前算一遍你就能判断硬盘空间够不够、下载要等多久不至于下到一半才发现只剩 3G 空间。2.3 请求头里绕不开的那几项地址拿到了直接粘到浏览器新标签页里十有八九返回 403。这不是地址失效而是服务端在校验“你是不是那个正在播放的客户端”。浏览器自动带的请求头你用别的方式发请求时是不会自动带上的必须手动补齐。关键的就三个Referer告诉服务端你是从哪个页面来的缺了它通常直接被拒。User-Agent声明客户端类型建议直接用你浏览器里那一串原样复制别自己编。Cookie某些情况下需要带上登录态才拿得到高清晰度具体看你的账号权限注意别把 Cookie 到处粘贴那玩意等同于你的登录凭证。还有一个经常被忽略的点Range 头。分片请求通常是按字节区间拉的服务端返回 206 Partial Content。如果你手动请求时没带 Range服务端可能返回整段也可能直接拒绝具体看实现。稳妥做法是模拟分片请求或者干脆让下载工具自己处理。2.4 一个手写的请求示例把上面这些拼起来用命令行验证一下地址是否可用这是最省事的排错方式curl -I \ -H Referer: https://站点域名/video/视频ID \ -H User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ... \ -H Cookie: 从浏览器复制的完整值 \ https://流地址前缀/....../video.m4s-I只发 HEAD 请求看响应头就行。返回 200 或 206说明地址和请求头都对上了返回 403 是请求头缺东西返回 404 是地址本身有问题可能过期了。这里要提醒一句这类流地址通常带时效签名参数里有一串deadline或者类似的过期时间一般是几小时。抓到了就尽快下别放到第二天再动手否则只能重抓一遍白做工。3. 下载与合并把两段流还原成一个能播的文件3.1 分片下载与断点续传一个长视频的视频轨往往被切成几十甚至上百个分片每个分片几 MB。你从baseUrl拿到的可能只是其中一个分片的地址也可能是整段的入口具体看返回结构。如果发现地址里带index、segment这类字样或者下载下来只有几 MB、时长明显不对那就说明你拿到的是分片需要按顺序把后面的都拉下来。这时候手工一条条下就不现实了。稳妥的办法是用支持批量队列的下载方式把分片地址按序号排好依次拉取到同一个目录最后按顺序拼接。拼接的顺序极其重要一旦顺序错乱合并出来的文件会出现画面跳帧、时间轴错位而且这种错误是隐性的不从头上看根本发现不了。我的习惯是下载时就给文件命名带上三位数的序号前缀比如seg_001、seg_002拼的时候按文件名排序能避开这个坑。断点续传这块命令行工具基本都支持加个-C -参数即可。但要注意续传的前提是文件本身没被写坏。如果上一次中断是因为磁盘满或者网络异常导致文件写了一半续传会得到一个混合了新旧数据的坏文件。遇到续传后文件大小异常、播放卡在某个位置别犹豫删掉重下比排查半天省时间。3.2 ffmpeg 合并参数到底该怎么写合并这一步ffmpeg是绕不过去的工具免费、跨平台、命令清晰。最基本的合并命令长这样ffmpeg -i video.m4s -i audio.m4s -c copy -movflags faststart output.mp4这里有几个点值得说清楚因为每一个都对应一类常见失败。-i video.m4s -i audio.m4s的顺序决定了输出文件里第一个流是视频、第二个是音频。顺序反了不会报错但某些播放器会认错主轨道表现就是打开后默认播音频、画面不显示。养成先视频后音频的习惯能省掉一堆莫名其妙的兼容问题。-c copy是整条命令的灵魂意思是“直接复制流不重新编码”。为什么必须这样因为重新编码意味着解码再编码十分钟的视频可能要跑几分钟甚至更久画质还会有肉眼可见的损失除非你给足码率纯属吃力不讨好。-c copy几秒钟就完事画质和原始流一模一样这是唯一正确的选择。-movflags faststart的作用是把索引信息挪到文件头部。不加这个参数生成的 MP4 也能播但在网页上边下边播时会先黑屏一段时间因为播放器得等整个文件下完才能读到索引。加了之后几十 KB 的索引被前置起播几乎是瞬间的。这个参数对小体积文件感知不强但对几百 MB 的长视频差别非常明显。如果合并时报错Could not find codec parameters八成是你下的分片不完整或者混了不同清晰度的片段。这时候不要硬试回去检查每个分片的体积是否接近、是不是来自同一条baseUrl前缀。3.3 “改后缀就能播”这个误区网上流传一种说法把.m4s直接改成.mp4或者.m4a就能当普通文件用。这个说法只对了一半而且很容易把人带沟里。如果那个分片里已经包含了完整的视频流改后缀确实能让部分播放器认出来因为.m4s本质上就是 ISOBMFF 容器跟 MP4 是同一家族。但问题在于大多数分片是切片而非完整文件头部的 moov box 可能是缺失的改后缀打开要么黑屏要么只播几秒就卡住。更麻烦的是你没法从文件大小判断它到底完不完整——它可能只是少了一个索引体积看起来正常播放就是不行。所以我的态度很明确不要用改后缀的方式偷懒。视频轨和音频轨老老实实用ffmpeg合并这一段多花三十秒后面能省掉几小时的困惑。真要图省事至少用ffprobe先查一下文件里到底有几个流ffprobe -v error -show_entries streamindex,codec_type,codec_name -of csv output.mp4输出的结果里如果只有video没有audio那说明你只下了一半回头补音频轨。这个检查动作我建议格式化到流程里每合并一个文件就跑一次比事后发现问题强得多。3.4 字幕、封面、弹幕要不要一起拿很多人只盯着视频本体等下载完了才发现字幕没跟着来尤其是那种只有自动生成字幕、没有外挂字幕轨的视频事后补起来非常费劲。我的做法是字幕优先单独抓在接口响应里找subtitle相关的字段通常会给出srt或json格式的地址下载下来后用ffmpeg嵌进去或者单独存成.srt放旁边播放器会自动加载同名字幕。封面图相对简单页面源码里能直接找到图片地址顺手存一份日后整理素材库的时候好用。弹幕则是另一回事原始数据是 XML 格式得靠专门的转换工具转成 ASS 字幕才能正常渲染而且不同播放器对 ASS 的支持好坏不一我一般只在确实需要归档的时候才折腾这一步日常下载跳过。提示字幕文件和视频文件保持同名、放在同一目录绝大多数播放器都能自动挂载。这个小习惯的回报率极高比事后手动加载字幕省事得多。4. 常见问题排查速查4.1 403 与 412两个最劝退的报错403 表示“服务器理解了你的请求但拒绝执行”。在视频下载场景里九成是请求头没带全尤其是Referer和User-Agent。排查顺序建议是先把Referer补上重试不行再加User-Agent最后才怀疑Cookie。不要一上来就把所有头都塞进去那样即使成功了你也说不清是哪一项在起作用下次换场景还得重新试。412 表示“前置条件不满足”在这个语境下通常是签名参数过期或者校验失败。遇到 412先检查地址里的时间戳参数如果已经超过有效期唯一的解法就是回页面重新抓一遍。别想着改时间戳蒙混过关签名是配套计算的改了反而彻底失效。我自己的习惯是抓完地址立刻下载中间不切去做别的事就是为了绕开这个坑。报错码常见原因排查动作成功率403Referer / UA 缺失补齐请求头后重试高412签名过期、参数被改回页面重新抓取地址高404地址拼错、分片序号越界核对地址前缀与序号中206 但文件 0 字节Range 头异常改用工具自动处理中连接重置并发过高、被限速降低并发数、加间隔高4.2 有画面没声音或者音画不同步有画面没声音八成是漏了音频轨或者合并时-i只给了一个文件。回到第 3.2 节检查命令确认两个-i都在。还有一种隐蔽情况两条流的时长不一致合并后视频轨正常音频轨末尾被截断。这不是合并的锅而是你下的音频分片少了最后一段重新核对分片数量就能确认。音画不同步的成因更复杂一些。如果偏移量是从头到尾恒定的通常是因为两路流的起始时间戳不同合并时没有对齐。可以在ffmpeg里用-itsoffset做微调但更省事的办法是换一个下载时能保证两路流同时起点的工具从源头避免。如果偏移量随着播放时间逐渐变大那基本可以判定是某一路的帧率或采样率信息被写错了这种文件建议直接重下修的性价比太低。4.3 合并报错与文件损坏Invalid data found when processing input是最常见的一条。成因有三个分片没下全、多个清晰度的分片混在了一起、文件在传输中被截断。排查方法是逐个用ffprobe检查分片能读出流信息的才算完整。如果只有第一个分片正常、后面的都报错那说明你拿到的可能是索引文件而非真实数据回去看响应结构。还有一种情况是合并成功、文件也能播但拖到某个时间点就卡死。这通常是拼接顺序错了中间某一段被重复或跳过。解决办法是用-f concat配合一个列表文件按序合而不是简单地用通配符*.m4s——通配符展开是按字典序的seg_10会排在seg_2前面这个坑我踩过不止一次。4.4 我的几条实操心得第一每完成一个环节就验证一次。地址拿到先用curl -I探一下分片下完先ffprobe查一下合并完再播一遍开头和结尾。这样出问题时你知道是哪一步坏的不用从头复盘。第二目录结构按“视频ID / 清晰度 / 流类型”分好。听起来麻烦但当你同时处理三五个视频时混在一起的video.m4s会让你彻底失去判断力根本分不清哪个对应哪个。第三保留原始分片直到确认成品没问题再删。合并失败想重来时重下分片的时间成本远高于多占的那点硬盘空间。我一般确认成品能完整播放、时长对得上、音画同步之后才清理中间文件。5. 批量处理与工具选型的取舍5.1 现成工具和自写脚本边界在哪市面上的下载工具大致分两类。一类是图形界面的封装版粘贴地址就能下优点是零门槛缺点是遇到结构调整就容易失效而且你没法干预它的合并参数清晰度、编码基本不可控。另一类是开源的命令行工具功能强、参数全、更新快代价是要花点时间读文档。我的建议是分场景偶尔下几个用图形工具够了别为了省事去学命令行需要批量、需要指定编码、需要自动化归档那就上命令行。判断标准很简单——如果你开始重复同样的操作三次以上就该考虑脚本化了如果一个月才用一次学脚本的投入根本收不回来。5.2 一个批量脚本的骨架真要批量处理思路比代码重要。核心流程就四步拿到视频 ID 列表、逐个请求播放地址、按清晰度和编码筛选出目标流、下载并合并。下面是个骨架具体接口和字段名要按实际情况调整import subprocess, os, json, pathlib def fetch_stream_info(video_id): # 请求播放地址接口返回解析后的 JSON ... def pick_streams(info, prefer_codec7, prefer_qn80): videos info[data][dash][video] audios info[data][dash][audio] v next((x for x in videos if x[id] prefer_qn and x[codecid] prefer_codec), None) a max(audios, keylambda x: x[bandwidth]) return v, a def download(url, out_path, headers): cmd [curl, -L, -C, -, -o, out_path] for k, val in headers.items(): cmd [-H, f{k}: {val}] cmd.append(url) subprocess.run(cmd, checkTrue) def merge(v_path, a_path, out_path): subprocess.run([ ffmpeg, -y, -i, v_path, -i, a_path, -c, copy, -movflags, faststart, out_path ], checkTrue)这段代码里最关键的不是语法而是pick_streams里的筛选逻辑先保证能拿到目标编码再退而求其次。写成next(...)带默认值时一定要处理None因为高清晰度不一定对所有人开放账号权限不够时那个档位压根不在返回数组里直接取会抛异常整个批次全挂。稳妥写法是先按id过滤结果为空就降一档逐级回退。5.3 并发、限速与礼貌批量下载最容易犯的错是把并发开到很大觉得这样快。实际结果是触发风控轻则请求被拖慢重则整批失败。我的经验值是并发控制在 3 到 5 之间每个请求之间加个几百毫秒的间隔总体速度并不会慢多少因为瓶颈往往在你的带宽而不是服务端响应。限速这件事也值得提一句。家用宽带上行下行不对称下行跑满时其他设备会明显卡顿。给下载工具加个限速参数比如--limit-rate 5M把速度压在带宽的六七成家里其他人刷视频、开会都不受影响自己也少挨抱怨。这不是技术问题是使用习惯问题但确实影响体验。5.4 存储与归档的一点经验下得多了文件管理会变成新问题。我的做法是文件名统一成标题_清晰度_编码.mp4的格式标题里的特殊字符先替换掉避免不同系统下的路径问题。目录按年月分一个月一个文件夹找起来直观。另外建一个清单文件记录每个视频的原始地址、下载时间、文件大小和校验值将来文件损坏或者需要重下不用再回页面翻一遍。校验值这块用sha256sum算一下存进清单里几十 MB 的文件几秒钟就算完。听起来有点重但当你半年后发现某个文件播不了、又不确定是下载时就坏了还是硬盘出了问题时这个值能立刻给你答案。这一点在长期归档的场景里价值很高日常随便下下就不用折腾了。最后分享一个小技巧如果你经常需要同一批视频的不同清晰度别重复抓地址同一次响应里的多条流地址往往同时有效把需要的几条都存下来一次性下完比反复回页面抓取高效得多。地址过期时间一般够你下完一个中等长度的视频但别拖太久拖久了就得重来。踩过几次坑之后我越来越觉得B站视频下载这件事技术上没有多高深难的是把每一步的因果关系想明白为什么拿不到文件、为什么要合并、为什么报 403、为什么没声音。这些问题一旦从原理上通了换什么工具、换什么站套路都是同一套。工具会过时接口会变但“播放是流、流要合并、合并要不重编码”这三条短期内不会变。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询