
简介面向Python爬虫初学者或需要批量获取B站短视频的开发者这份资源提供了一个轻量级下载脚本解决手动逐个保存视频的低效问题。程序基于requests模块通过循环遍历10页排行榜JSON信息自动提取视频标题与地址并下载到本地video目录下载时标题中的非法字符会被替换为空避免文件保存失败。同时每下载完一个视频会随机等待3-6秒有效控制请求频率、降低被反爬机制拦截的风险。控制台会实时打印下载进度便于观察运行状态。压缩包仅含1个py文件大小仅2KB代码结构简单没有多余依赖适合直接阅读与二次开发。目前已有495人学习浏览。读者通过此脚本可掌握requests文件下载、JSON解析、字符串清理、进度条打印以及time.sleep频率控制等核心技巧也可将其改造为多线程或支持指定链接的下载工具是爬虫入门与练手的实用参考。1. 需求分析为什么非要自己写B站下载工具先说清楚这个项目的来龙去脉。B站视频下载这件事网上一搜一大把现成工具但多数要么要收费、要么带水印、要么就是网页版下载插件下载高清视频还得充值会员。我自己遇到的实际场景是要把几个长视频教程下载到本地方便在没网的环境下用电脑看同时希望能看到下载进度不要一卡就是半天不知道哪里出了问题。所以这个项目被定义为B站视频下载爬虫含进度本质是用Python写一个爬虫通过调用B站网页端内部的视频流接口拿到视频的真实播放地址后分片下载到本地并在下载过程中实时输出进度信息。很多人一听到爬虫就以为很高深其实拆开来看就是三步找接口、发请求、存文件难点主要是三个拿到正确的视频流地址、请求头和防盗链规则对齐、以及下载过程中的异常处理。先说清楚适用范围。这个方案适合个人学习、离线备份、视频剪辑素材收集不要用于批量抓取他人原创内容做二次分发。写爬虫的人心里要有边界技术上能做的事不等于道德上应该做的事。B站视频和普通网页视频最大的不同在于B站的视频播放走的是DASH协议通俗说就是视频画面和音频是分开的两个文件流。所以下载的时候要把视频流和音频流都拿下来再通过ffmpeg合并。这个设计后面会详细讲也是新手最容易卡住的地方。我在第一次写这个脚本的时候就犯过这个错只下了视频流结果播放的时候没有声音折腾了半小时才意识到音频流是另一条URL。2. 技术选型为什么用requests而不是scrapy2.1 核心依赖清单整个项目用到的依赖非常少这也是我推荐新手从这类项目入手的原因。Python环境装好之后只需要三个第三方库pip install requests tqdm ffmpeg-pythonrequests负责HTTP请求tqdm用来显示进度条ffmpeg-python是调用ffmpeg的封装库负责把视频流和音频流合并。当然ffmpeg本身需要另外安装Windows用户去官网下载解压后配置环境变量Mac用户直接brew install ffmpegLinux用户apt install ffmpeg这一步不用多讲。2.2 为什么弃用scrapy很多教程一上来就推scrapy但这里我要泼个冷水。B站视频下载这个场景根本不需要scrapy理由有两点。第一scrapy是面向大规模分布式爬取的框架处理的是多个页面抓取和结构化数据提取这类问题而视频下载的核心是单文件流下载用scrapy反而绕远路第二scrapy的异步下载和中间件机制对新手来说太重调试起来体验很差一个小代理配置问题就能卡你半天。requests配合Session对象保持连接、带Cookie、手动控制超时写起来直观得多。对于视频下载这种直连静态资源的场景requests的iter_content方法是真正的利器。它允许你以字节块为单位流式读取响应体这样既能控制内存占用又能精确计算已下载的字节数从而算出进度百分比。相比之下如果直接resp.content一次性读入内存一个2GB的视频文件会直接把内存打爆这也是新手特别容易踩的坑。3. 核心思路B站视频流接口的完整解析3.1 从播放页拿到视频基本信息和cidB站每个视频页面都有一个BV号或者av号比如经典的BV1xx411c7mD这种格式。要拿到真实的视频流地址第一步是请求视频的playurl接口。这个接口需要传入几个关键参数bvidBV号、cid视频分P的ID、qn清晰度编码、fnval流格式标志DASH协议需要设为16。cid从哪来可以用另一个接口或者从播放页HTML源码中的window.__playinfo__里直接提取。强烈建议直接从__playinfo__里取因为B站把当前视频的preload信息直接内嵌在HTML里了省去一次API调用。用requests请求播放页时必须带上User-Agent否则会被B站风控直接拦下来。B站现在对无UA请求的处理是返回一个验证页你解析出来什么东西都是空的。import requests import re import json session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 }) def get_video_meta(bvid): url fhttps://www.bilibili.com/video/{bvid} resp session.get(url, timeout10) # 提取内嵌的窗口初始化数据 match re.search(rwindow\.__playinfo__(.*?)/script, resp.text) if not match: raise RuntimeError(未找到playinfo可能被风控) data json.loads(match.group(1)) video_info data.get(data, {}) return video_info3.2 解析视频流和音频流地址拿到的data结构里有一个dash字段里面包含video和audio两个列表每个列表里又有多个不同清晰度的流。video列表里bandwidth值越高代表码率越高清晰度越好audio列表则对应不同码率的音频轨。还有一点要注意URL字段前面可能有防盗链前缀比如https://upos-sz-mirrorcos.bilivideo.com这类地址如果直接用requests请求会被403必须带上Referer头指向视频播放页。def get_dash_urls(video_meta, bvid): dash video_meta[dash] # 选最高清晰度的视频流 best_video max(dash[video], keylambda x: x[bandwidth]) best_audio max(dash[audio], keylambda x: x[bandwidth]) return best_video[baseUrl], best_audio[baseUrl]这个max表达式看着简单但实际帮了大忙。B站返回的视频流可能有多个弹幕里甚至有8K选项靠bandwidth选最高的不会错。音频流同理。选完地址后下载时Session需要带上Referer和Origin头否则B站的CDN会认为你是盗链。在调试中发现一个细节不同的视频和音频baseUrl可能指向不同CDN的域名有时候某个域名访问慢可以把baseUrl里的域名替换成备用节点。B站返回的字段里还有backupUrl是备用地址可以留作降级方案。4. 实操过程完整实现带进度条的下载脚本4.1 分片下载的设计B站视频流是支持Range请求的也就是说HTTP协议允许你请求文件的某一部分字节。这是一个非常关键的特性它让断点续传和多线程下载变成可能。但在自己的脚本里我并不建议一上来就做多线程分片下载原因后面说。简单可靠的方案是用requests的stream模式配合iter_content按块读取写入文件。每读取一个块就累计大小用tqdm更新进度条。from tqdm import tqdm def download_file(session, url, filepath, desc): resp session.get(url, streamTrue, timeout30) total_size int(resp.headers.get(content-length, 0)) with open(filepath, wb) as f, tqdm( totaltotal_size, unitB, unit_scaleTrue, descdesc ) as pbar: for chunk in resp.iter_content(chunk_size1024 * 1024): if chunk: f.write(chunk) pbar.update(len(chunk))这段代码的精髓在于unit_scaleTrue它会自动把字节数转成MB、GB显示比如78MB或者1.2GB真实下载时体验非常直观。chunk_size设置为1MB意思是每下载1MB更新一次进度条频率合适不会因为频繁刷新影响IO速度。进度条还有一个用途判断网络是否卡死。如果进度条长时间不动大概率是连接断了或者触发了B站的限速策略。在调试的时候我会在下载循环里加一个时间判断超过30秒没有新数据就主动中断并重试。4.2 音频视频合并ffmpeg一行命令搞定下载完成后当前目录下会有video.mp4和audio.m4a两个文件。如果直接打开video.mp4画面清晰但没有声音。这时需要调用ffmpeg做封装合并注意这里不是转码只是把两个流封装进同一个MP4容器里速度很快对画质无损。import subprocess def merge_av(video_path, audio_path, output_path): cmd [ ffmpeg, -y, -i, video_path, -i, audio_path, -c, copy, output_path ] subprocess.run(cmd, checkTrue)-c copy的意思是不重新编码直接复制流的编码数据所以整个过程基本上是秒级完成。如果FFmpeg报错多半是视频流和音频流的编码格式不兼容这时候需要去掉-c copy改用libx264和aac重编码但一般B站的流不会走到这一步。合并完之后删除临时文件整个下载流程就闭环了。我给这个脚本加了个最终清理的逻辑无论是正常结束还是中途异常都会把残存的video.mp4和audio.m4a删掉避免下次运行同名文件冲突。4.3 完整调用流程把上边的逻辑串起来主函数就是这样def main(bvid): # 1. 获取视频元数据 meta get_video_meta(bvid) video_url, audio_url get_dash_urls(meta, bvid) # 2. 下载视频流和音频流 download_file(session, video_url, video.mp4, 视频流下载中) download_file(session, audio_url, audio.m4a, 音频流下载中) # 3. 合并成最终文件 merge_av(video.mp4, audio.m4a, f{bvid}.mp4) # 4. 清理临时文件 os.remove(video.mp4) os.remove(audio.m4a) print(f下载完成{bvid}.mp4)这个流程非常清晰每一步都有明确输入输出。你把bvid传进去最终拿到一个带画面和声音的mp4文件中间所有进度都显示在控制台上能随时知道卡在哪一步。5. 常见问题与排查技巧实录5.1 403 Forbidden防盗链问题这是我遇到的最多的问题几乎每个新手都会栽在这。症状是用浏览器打开视频地址能播放但requests去请求返回403。原因是B站CDN风控会检查请求头里的Referer字段要求必须来自bilibili.com否则拒绝返回内容。解决办法就是在Session里设置固定的Referersession.headers.update({ Referer: https://www.bilibili.com/ })还有一个容易忽略的点是Origin头。有些情况下CDN会同时检查Origin得带上https://www.bilibili.com才保险。这类请求头问题排查时可以先用浏览器的开发者工具复制出实际播放时的完整请求头和代码里的请求头逐字段比对一般能立刻找到差异。5.2 下载中断进度卡在99%平时下载都能跑通但在网络不稳定的情况下进度条可能在99%卡很久甚至直接抛ConnectionError。原因很好理解TCP连接被重置或者响应超时。B站大文件下载时如果一段TTL内服务器没有收到客户端的数据请求会自动断开。解决思路有两个。第一是把iter_content的chunk_size调到512KB减小每个块的传输时间降低被断开的概率。第二是增加重试机制捕获requests.exceptions.RequestException让代码自动从断点处继续下载。def download_with_retry(session, url, filepath, retries5): for i in range(retries): try: download_file(session, url, filepath, 下载中) return except requests.exceptions.RequestException as e: print(f第{i1}次尝试失败{e}) # 删除不完整的文件再重试 if os.path.exists(filepath): os.remove(filepath) raise RuntimeError(多次重试仍失败)这里注意断点续传最麻烦的是处理已写入的部分文件。为了简化代码我选择失败后删除整个文件从头再下代价是浪费之前已经下载的内容但胜在代码可靠。如果你想要真正的断点续传就得在打开文件时用ab模式同时把Range头设置为已下载的字节数感兴趣可以自己研究。5.3 仅有画面没有声音这个问题本质上是B站DASH策略导致的。我现在再强调一遍视频画面和音频是分开的。所以不管你是用浏览器插件还是自己写脚本只要只拿到一条流必然缺一半。合并时注意输出的容器格式MP4最通用MKV也可以。有时候下载的是4K高码率视频ffmpeg处理会慢一些等个十几秒是正常的。5.4 风控和登录问题B站对未登录用户的视频流接口有一定的限流策略。同一个IP在短时间内大量请求视频接口会触发风控返回-412状态码。解决办法是降低请求频率并且在Session里带上你登录B站后获取的Cookie。Cookie直接从浏览器开发者工具里复制有效期大约一个月。另外B站现在部分充电专属视频和4K高码率视频确实需要登录才能获取到对应的流地址这是平台的权限体系决定的脚本只是忠诚地反映了接口能力。6. 实操心得这个项目还能怎么玩整个项目从构思到完成大概花了我一个下午的时间。核心代码不到100行难度确实不高但做完之后你对HTTP协议、请求头、流式下载、音视频封装这些知识点的理解会扎实很多。我在调试的过程中最大的体会是写爬虫不光是调包最重要的环节是找接口、逆参数的过程B站的playurl接口文档并没有对外开源完全靠抓包和观察HTML源码摸索出来的。如果你想把这种能力迁移到其他平台核心技能是一样的打开开发者工具的Network面板过滤出媒体类型的请求观察它的URL规律和请求头参数然后模拟出来。抖音、小红书的视频下载思路大同小异只是加密参数和风控强度有区别难度直线上升。B站这个项目的价值在于它的接口相对干净防护适中是新手练手的最佳样本。另外打个安全提醒下载的视频如果用于个人学习、素材收藏完全没毛病但如果要剪辑后发布到公开平台就必须注意版权问题。B站的视频大多数是UP主的心血未经授权二次转载不仅违反平台规则还可能涉及侵权。大家技术上学会了更要用在正道上。本文还有配套的精品资源点击获取