简介这份资源是一套基于Python的B站视频批量下载与处理工具整合了yt-dlp和ffmpeg两大库面向需要下载高清视频、提取弹幕或进行多格式转换的B站用户尤其适合视频学习、素材整理与内容分析场景。工具支持AV号、BV号和URL三种输入方式可自动批量下载并解析高画质视频还能抓取弹幕数据便于后续做评论趋势或热度分析同时提供MP4、MKV等多格式输出兼顾不同设备播放需求。压缩包共7个文件包含1个Python主脚本、2个文本说明、1个Word文档、1个License、1个gitignore及1个Markdown文件整体仅40KB轻量小巧便于快速部署和修改。目前已有326人学习下载适合有一定Python基础、希望自动化处理B站视频的初学者或进阶用户。1. 从一条命令说起这个工具到底在解决 B 站用户的什么痛点在 B 站下载视频很多人第一反应是找在线解析站但解析站不是限速就是失效更别说批量下载和弹幕存档。这个项目标题其实把 B 站自动化下载的完整链路说清楚了用 Python 做调度用 yt-dlp 解析并拉流用 ffmpeg 做后处理一次解决批量下载、弹幕获取、多格式支持和高清解析。我第一次跑通时最意外的不是下载速度而是意识到 B 站的高清视频其实是音频和视频分两条流传输的合并这一步绕不开 ffmpeg。这篇文章适合想自建下载工具的人做剪辑、离线存课、批量备份或者想把弹幕一起留档。2. B 站视频的下载机制与工具链选型为什么非 yt-dlp 和 ffmpeg 不可2.1 高清视频的真相音画分离与分片传输B 站从多清晰度时代开始就采用 DASH 动态流——视频和音频拆成两条独立的流视频轨又按清晰度拆成多个分片播放器拿到播放凭证后逐片请求。所以你在网页里看到的 1080P本质上是1080P 视频轨 对应音频轨的组合。这个结构带来两个直接结果。其一直接拿浏览器里的网络请求去下载一个 mp4 链接往往只有画面没有声音或者只有声音和低清画面其二想要最高清晰度必须同时拿到两条流再合并。任何声称直接解析出完整 mp4的方案要么是绕过了 DASH 用兼容格式牺牲清晰度要么是后台悄悄帮你调用了 ffmpeg。这也是为什么工具链里 ffmpeg 是刚需而不是可选项。yt-dlp 只负责把音视频轨拉下来后半程的合并、转封装、字幕合成全靠 ffmpeg。我之前有一阵子图省事没装 ffmpeg结果所有批量任务都在最后一步失败纯属给自己挖坑。2.2 yt-dlp 的价值签名、防盗链和格式协商下载器层面核心工作不是请求 URL 拿文件而是先拿到播放凭证。B 站的播放接口做了一套签名机制和防盗链校验而且对请求频率非常敏感频率一高直接返回 412 状态码。yt-dlp 把这些脏活全部封装了这也是我最终放弃手写 requests 方案的原因。它对 B 站的解析流程大致是把 BV 号/AV 号/URL 归一化成视频 ID → 请求预览接口拿清晰度清单 → 按用户给的格式规则选出最佳音视频轨 → 带正确的请求头分片下载。格式协商这部分是可编程的这也是后面所有批量、多格式操作的基础。拿它和浏览器直接下载、在线解析站对比yt-dlp 的优势不是单文件下载快而是可预测、可脚本化、可重试。解析站今天能用明天可能就挂自己维护脚本才能长期用而且出问题你知道去哪看日志。2.3 ffmpeg 在后处理里的具体分工ffmpeg 在整套流程里承担四件事音视频合并、格式转换、字幕烧录和封装、音频提取。以封装 mp4 为例yt-dlp 默认把视频轨和音频轨逐段下到临时目录再由 ffmpeg 封装合并合并失败通常不是 ffmpeg 坏了而是音频轨下载不完整。选型上有一个常见误区觉得 yt-dlp 输出的临时文件可以直接给播放器播放。实际上合并前的文件是分离的音视频流只有经过 ffmpeg 合并才是完整文件。理解了这层关系后面调试也会容易很多。角色职责不可替代的理由Python调度、批量、解析输入把机械操作变成可重复的流程yt-dlp解析接口、签名、下载持续维护签名和防爬逻辑ffmpeg合并、转换、封装音视频后处理的事实标准3. 最小复现搭好环境并用一条命令跑通单视频下载3.1 环境准备Python、yt-dlp 与 ffmpeg 的安装与路径先准备一个 Python 3.9 以上的环境然后安装 yt-dlp。python -m pip install -U yt-dlp # 验证安装 yt-dlp --version参数说明-U表示升级到最新版本yt-dlp 迭代很快老版本解析 B 站接口经常失效--version确认命令可用也方便后面排查问题时核对版本。ffmpeg 建议按系统级方式安装# macOS brew install ffmpeg # Debian/Ubuntu sudo apt install ffmpeg # Windows 可以用自带包管理器安装或手动下载解压后把 bin 目录加入 PATH判断是否装好直接跑ffmpeg -version。我在 Windows 上踩过这个坑装完 yt-dlp 直接跑报ffmpeg not found因为在 Windows 上 yt-dlp 需要在 PATH 里能找到 ffmpeg.exe光有文件不配路径等于没装。注意yt-dlp 不会帮你安装 ffmpeg它只负责调用。合并步骤报错时第一先确认 ffmpeg 命令本身可用。3.2 第一条下载命令从 URL 到完整视频yt-dlp https://www.bilibili.com/video/BVxxxxxxxxx -f bv*ba/b -o %(title)s.%(ext)s --merge-output-format mp4 --embed-metadata这个命令的核心是-f bv*ba/bbv*是带完整帧率的视频轨ba是唯一音频轨加号表示两者合并斜杠/表示如果合并失败就退化到单文件流。-o指定文件命名为标题加扩展名--merge-output-format mp4把封装格式锁定为 mp4--embed-metadata把视频标题、UP 主信息写进文件元数据。为什么不直接-f best因为 B 站很多视频的best命中的是 480P 单文件流拿不到真正的最高清晰度。想确认当前视频有哪些可选的清晰度先列出格式清单yt-dlp -F URL输出里能看到 format_id、分辨率、编码格式。记下想要的格式 id再写进-f这是高清解析排错的第一步。比如列表里有个137是 1080P 视频轨、140是音频轨就可以写-f 137140。3.3 指定清晰度与格式优先级规则格式选择是这套工具里最值得花时间调的部分不同场景对应不同写法场景-f 写法说明最高画质bv*ba/b高清视频轨加音频轨限制 1080Pbv*[height1080]ba按高度过滤视频轨只要 mp4bv*ba/b合并后封装成 mp4只要音频ba配合--extract-audio转码bv*[height1080]这种写法是 yt-dlp 的格式过滤语法height字段代表视频高度想要 720P 就把数字改成 720。过滤条件加得越细越不容易在批量下载时拿到超预期的大文件。B 站的高清流1080P 及以上有些需要登录 cookie 才会在格式列表里返回否则-F只看得到低清晰度。这属于权限问题我在第 5 章展开讲。4. 批量下载与多来源归一化AV 号、BV 号与 URL 的统一处理4.1 先用 Python 造一个批量调度脚本单条命令跑通之后批量只是把命令套进循环。import subprocess import sys URLS_FILE urls.txt def run_download(url: str) - None: cmd [ yt-dlp, url, -f, bv*ba/b, --merge-output-format, mp4, -o, %(title).80s [%(id)s].%(ext)s, --retries, 5, --continue, --no-overwrites, --embed-metadata, ] proc subprocess.run(cmd, capture_outputTrue, textTrue) if proc.returncode ! 0: print(f[失败] {url}\n{proc.stderr[-800:]}) else: print(f[完成] {url}) def main(): with open(URLS_FILE, r, encodingutf-8) as f: lines [line.strip() for line in f if line.strip()] for line in lines: run_download(line) if __name__ __main__: main()逻辑说明逐行读取urls.txt每行一个输入subprocess.run调 yt-dlp 并捕获输出失败时打印 stderr 最后 800 个字符方便定位是权限问题、格式问题还是网络问题。参数说明--continue开启断点续传--no-overwrites表示已存在的文件不重复下载--retries 5让单文件失败时重试最多 5 次。-o里加了%(id)s文件名带视频 ID不同 UP 主视频重名时不会互相覆盖。批量场景里最容易忽略的是输入格式。urls.txt 每行只放一个原始输入不要用分号或逗号分隔也不要带多余空格。4.2 AV 号、BV 号与 URL 的归一化用户给的输入不一定是完整 URL可能是裸的 BV 号也可能是一段带分 P 参数的链接。写一个归一化函数import re def normalize_bilibili_input(raw: str) - str: raw raw.strip() if raw.startswith((http://, https://)): return raw if re.match(r^(BV[0-9A-Za-z]|av\d)$, raw, re.IGNORECASE): return fhttps://www.bilibili.com/video/{raw} raise ValueError(f无法识别的输入: {raw})逻辑说明带协议的直接返回原样BV 号或 av 号开头的小写也统一转成标准视频链接。批量脚本里先过这个函数再交给下载器能少传错格式。B 站现在主推 BV 号av 号是老编号两种在 yt-dlp 里都能直接识别但 Python 层归一化可以让日志更干净。另外要注意URL 里带?p2这种分 P 参数不要去掉那是多 P 视频的定位参数去掉会默认下第一 P。4.3 多格式支持mp4、mkv、音频提取与封装差异多格式支持的思路是三层分离-f决定拿哪些源流--merge-output-format决定封装容器--recode-video才决定要不要重新编码。# 封装为 mkv适合多轨和字幕外挂 yt-dlp URL -f bv*ba --merge-output-format mkv # 只抽音频转成 m4a yt-dlp URL -f ba -o %(title)s.m4a # 强制重新编码成标准 mp4 yt-dlp URL -f bv*ba --recode-video mp4说明mkv 容器对多音轨、多字幕轨支持更好mp4 兼容性更强。--recode-video是真正做视频重新编码耗时且吃 CPU日常使用应该优先用--merge-output-format做流拷贝封装只有遇到播放器不认的编码时才考虑重编码。B 站普通视频没有外挂字幕流官方的 CC 字幕和弹幕都不是直接嵌在媒体流里的需要单独获取。弹幕获取的具体做法在第 6 章。4.4 断点续传与已下载清单批量下载跑一半断网或脚本崩溃是常态archive 文件能避免重头再来yt-dlp URL --download-archive archive.txt说明archive.txt会记录每个已下载视频的 ID第二次跑同一个清单时自动跳过已完成的条目。跑几百个视频的存量备份时这个参数能省大量时间和流量而且脚本重启不会造成重复下载。配合 Python 脚本使用时把--download-archive加进 cmd 列表即可。要注意 archive 文件是所有任务共享一本账还是每个任务独立一本取决于你的使用场景我一般按批量任务主题各建一个方便统计哪些还没下完。5. B 站批量下载避坑四个高频问题的排查方法5.1 下载中途卡住或返回 412现象单文件下到一半进度条不动或者直接报HTTP Error 412: Precondition Failed。原因412 是 B 站风控的典型状态码。请求频率太快或者同一 IP 同时并发下载多个视频会被判定为脚本访问。视频分片请求会被短暂封禁过一段时间自动恢复。解决一是降并发和限速二是把浏览器 cookie 传给 yt-dlp。yt-dlp URL --cookies-from-browser chrome --limit-rate 5M --sleep-interval 3说明--cookies-from-browser chrome读取本地浏览器的登录 cookie用真实登录态绕过匿名风控--limit-rate 5M限制单文件下载速度--sleep-interval 3在任务之间加 3 秒延迟。批量脚本里我一般把并发控制在 2 以内再配合随机延迟基本能避开 412。5.2 下载完成只有画面没有声音现象下载产物是一个无声 mp4或者播放时音画不同步。原因DASH 流下只拉了视频轨音频轨漏掉了。常见于-f只写了bv*或者bestvideo而没有加音频轨又或者 ffmpeg 没装导致合并步骤被跳过。解决改用-f bv*ba/b并确认 ffmpeg 可用。如果文件已经下载完发现音轨缺失可以用 ffmpeg 手动合并ffmpeg -i video.m4s -i audio.m4s -c copy output.mp4说明-c copy是流拷贝不重新编码速度快。如果两个流时长不一致需要加-shortest参数不过大多数情况是网络丢片导致音频轨不完整重新下载音频轨比强行对齐更可靠。5.3 高清视频提示需要登录或返回 403现象-F列出的格式最高只有 480P指定 1080P 时报 403 Forbidden。原因B 站对高清流做了登录态校验部分 UP 主也设置了仅登录可看甚至还有仅会员可看的内容。解决导出浏览器 cookies 后加入下载命令。yt-dlp URL --cookies-from-browser chrome注意--cookies-from-browser依赖浏览器本身的存储格式如果浏览器有独立加密存储导致读取失败可以手动导出 cookies 文件再换成--cookies cookies.txt。这个参数只是把请求身份换成你的账号下载行为本身不受影响。5.4 文件名过长或包含非法字符导致保存失败现象Windows 下报文件创建失败或者文件名乱码。原因标题里包含: * ? |等 Windows 非法字符或者标题超过 255 字符。B 站有些视频标题会带各种符号批量下载时一个文件失败往往会让整批任务中断。解决yt-dlp URL -o %(title).80s [%(id)s].%(ext)s --restrict-filenames说明%(title).80s把标题截断到 80 字符--restrict-filenames把空格和特殊符号替换为下划线。批量任务我建议永远加这两个参数不然遇到一个标题全是特殊符号的视频整批任务就断了。6. 弹幕获取与合成把 XML 弹幕变成可播放的字幕6.1 从视频信息里拿 cid 并请求弹幕 XML弹幕不是媒体流的一部分需要单独请求弹幕接口。第一步先用 yt-dlp 拿视频的元数据yt-dlp --dump-json URL输出的 JSON 里有id和cid字段cid是弹幕接口需要的编号。拿到 cid 后请求弹幕接口import requests cid 视频的cid url fhttps://comment.bilibili.com/{cid}.xml headers {Referer: https://www.bilibili.com} r requests.get(url, headersheaders) r.encoding utf-8 with open(f{cid}.xml, w, encodingutf-8) as f: f.write(r.text)说明弹幕接口现在有 protobuf 新格式老 xml 接口仍然可用但弹幕量特别大的视频单次请求只会返回最新的一部分弹幕。日常存档场景 xml 够用要做完整弹幕备份需要按时间分段请求这里不展开。6.2 XML 弹幕转换 ASS 并合并进视频B 站弹幕 XML 里每条弹幕在d标签的 p 属性携带时间、字号、颜色等字段。简单转成 ASS 字幕import re def fmt_time(seconds: float) - str: h int(seconds // 3600) m int(seconds % 3600 // 60) s seconds % 60 return f{h}:{m:02d}:{s:05.2f} def danmaku_xml_to_ass(xml_text: str) - str: header [Script Info] PlayResX: 1920 PlayResY: 1080 [V4 Styles] Format: Name, Fontname, Fontsize, PrimaryColour, OutlineColour, BackColour, Bold, Outline, Shadow, Alignment, MarginL, MarginR, MarginV Style: Default, Microsoft YaHei, 48, H00FFFFFF, H00000000, H80000000, 0, 2, 1, 2, 30, 30, 40 [Events] Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text events [] for d in re.finditer(rd p([^])(.*?)/d, xml_text, re.S): fields, text d.group(1).split(,), d.group(2) appear_time float(fields[0]) start fmt_time(appear_time) end fmt_time(appear_time 4.0) events.append(fDialogue: 0,{start},{end},Default,,0,0,0,,{text}) return header \n.join(events)逻辑说明p 属性的第 1 个字段是弹幕出现时间单位秒弹幕默认停留 4 秒所以结束时间加 4fmt_time把秒转成 ASS 要求的时间格式。转换后文本里如果有 XML 转义符需要先做反转义再写入。ASS 生成后用 ffmpeg 烧录进视频ffmpeg -i video.mp4 -vf assdanmaku.ass -c:v libx264 -c:a copy output.mp4说明assdanmaku.ass是字幕滤镜-c:v libx264重新编码视频轨-c:a copy音频直接拷贝。烧录后弹幕成为画面一部分发到其他平台也能看到不想要烧录也可以把 ass 封装进 mkv 容器播放器里自己开关字幕。个人习惯是批量任务里优先下载原始视频留档弹幕单独存 XML 一份再按需生成烧录版本——这样视频本体和弹幕都能独立归档。希望帮到你。本文还有配套的精品资源点击获取