公司动态

Python爬虫实战:批量下载B站视频的完整解决方案

📅 2026/9/2 6:31:06
Python爬虫实战:批量下载B站视频的完整解决方案
简介这是一份面向Python初学者与爬虫入门者的实战练习资源聚焦B站小视频批量采集场景涵盖动态请求构造、User-Agent随机化、视频文件大小预估及下载进度实时反馈等核心爬虫技能点。压缩包共5个Python源文件总大小仅12KB结构清晰主程序bilibili.py统筹调度其余task_*.py分别实现动态接口解析、请求头伪装、文件尺寸获取与进度可视化等模块化功能便于分步学习与调试。目前已有272人下载学习适合用于理解HTTP请求机制、掌握requestsjson基础应用、训练网络异常处理与用户体验优化意识。所有代码注释完整、逻辑解耦可直接运行调试亦可作为Web数据采集课程的配套实验脚本或自学项目参考。1. 项目缘起从“看视频”到“存视频”的自动化需求作为一个经常在B站上找学习资料、技术教程或者收藏一些有趣创意的开发者我发现自己经常遇到一个痛点看到一系列优质的视频教程想离线保存下来慢慢看或者想批量分析视频的标题、播放量等数据但一个个手动下载不仅效率低下而且B站官方并没有提供便捷的批量下载工具。市面上的一些下载器要么功能受限要么操作复杂要么存在安全风险。于是一个念头就冒了出来为什么不自己写一个呢既能解决实际问题又能作为一次绝佳的Python网络爬虫实战练习。这个“网络爬虫-批量爬取B站视频-python练习源码.zip”项目就是基于这个最朴素的需求诞生的。它不是一个追求大而全的商业级工具而是一个聚焦于核心流程、代码清晰、便于学习和二次开发的练习项目。通过它你可以深入理解如何用Python模拟浏览器行为解析网页结构处理网络请求最终实现从B站UP主主页、系列合集或搜索列表中自动化地获取视频信息并下载视频文件。整个过程涉及HTTP协议、HTML解析、JSON数据处理、文件I/O、多线程/异步编程等多个核心知识点是检验和提升Python编程与网络数据处理能力的绝佳沙盒。2. 核心工具链选型与项目环境搭建在动手之前选择合适的工具是成功的一半。对于B站视频爬虫我们需要几个核心组件发送HTTP请求的库、解析HTML或JSON数据的库、以及处理视频流下载的库。下面是我经过多次实践后总结出的稳定组合。2.1 核心库依赖解析Requests这是Python社区进行HTTP请求的“事实标准”。它比Python内置的urllib库更加人性化接口简洁功能强大。我们将用它来获取B站网页的源代码、API接口返回的JSON数据等。它的会话Session功能对于维持登录状态如果需要和自动处理Cookies非常有用。BeautifulSoup4B站的大部分页面仍然是HTML格式我们需要从中提取视频的链接、标题、BV号等信息。BeautifulSoup4是一个强大的HTML/XML解析库它支持多种解析器如lxml, html.parser能够以非常直观的方式遍历和搜索文档树是信息提取的利器。lxml这是一个高性能的HTML/XML解析库。虽然BeautifulSoup4默认的html.parser也能用但在处理复杂或大型页面时lxml的速度和内存效率要高得多。我们将它作为BeautifulSoup的解析后端提升整体解析速度。Aiohttp / 并发处理库可选但推荐批量下载视频是一个典型的I/O密集型任务大部分时间都在等待网络响应。如果使用同步的Requests库顺序下载会非常慢。aiohttp是一个基于asyncio的异步HTTP客户端/服务器框架可以轻松实现成百上千个视频的同时下载极大提升效率。对于初学者也可以使用concurrent.futures模块中的ThreadPoolExecutor来实现线程池并发更为简单直观。FFmpeg外部工具非Python库B站的视频和音频流通常是分开的尤其是高清视频下载下来的是.m4s格式的分段文件。我们需要用FFmpeg这个强大的多媒体处理工具将视频流和音频流合并成一个完整的MP4文件。你需要确保FFmpeg的可执行文件路径已添加到系统的环境变量中或者在代码中指定其完整路径。2.2 环境配置步骤创建虚拟环境强烈建议使用虚拟环境来管理项目依赖避免污染全局Python环境。在项目根目录下打开终端命令行执行以下命令以Windows系统为例使用venvpython -m venv bilibili_spider_env # 激活虚拟环境 # Windows: bilibili_spider_env\Scripts\activate # macOS/Linux: source bilibili_spider_env/bin/activate激活后命令行提示符前会出现(bilibili_spider_env)字样。安装Python依赖库在激活的虚拟环境中使用pip安装所需库。创建一个requirements.txt文件是个好习惯。pip install requests beautifulsoup4 lxml # 如果需要异步下载安装aiohttp pip install aiohttp安装并配置FFmpeg下载前往FFmpeg官网下载对应操作系统的静态编译版本。配置解压后将其bin目录的路径例如C:\ffmpeg\bin添加到系统的环境变量PATH中。验证打开新的命令行窗口输入ffmpeg -version如果显示版本信息则配置成功。注意虚拟环境是Python项目开发的“标配”它能确保每个项目的依赖库版本独立避免版本冲突。FFmpeg是处理音视频合并的关键没有它你下载的只是一堆无法直接播放的片段文件。3. B站视频信息获取的两种核心路径要爬取视频首先得找到视频。B站视频的入口多种多样但归根结底我们需要获取到视频的唯一标识符——BV号如BV1xx411c7mh或老式的av号如av170001以及视频的清晰度、分P等信息。这里我主要介绍两种最常用、最稳定的信息获取路径。3.1 路径一解析网页HTML适用于UP主主页、分区列表对于像UP主个人主页、频道投稿列表、分区排行榜这类公开页面我们可以直接请求HTML页面然后用BeautifulSoup从中提取信息。以爬取某个UP主的所有视频为例构造请求URLB站UP主的投稿页面URL格式通常为https://space.bilibili.com/{mid}/video其中{mid}是用户的ID。我们需要先获取这个mid。发送请求与解析使用Requests获取页面HTML然后用BeautifulSoup定位视频列表区域。视频信息通常包含在li标签或带有特定class的div中。import requests from bs4 import BeautifulSoup headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..., Referer: https://www.bilibili.com/ } mid 12345678 # 示例UP主ID url fhttps://space.bilibili.com/{mid}/video resp requests.get(url, headersheaders) soup BeautifulSoup(resp.text, lxml) # 寻找视频列表项这里需要根据实际页面结构调整选择器 # 例如可能是 class 为 ‘small-item’ 的 a 标签 video_items soup.find_all(a, class_small-item) for item in video_items: # 提取BV号可能从href属性中解析 link item.get(href) if /video/BV in link: bvid link.split(/video/)[-1].split(?)[0] # 提取标题 title item.get(title) or item.find(div, class_title).text.strip() print(fBV号: {bvid}, 标题: {title})关键点B站的页面结构可能会更新因此class_等选择器需要你通过浏览器的开发者工具F12实时查看并调整。User-Agent和Referer头是模拟浏览器行为、避免被简单反爬的基础。3.2 路径二调用官方API接口更稳定、信息更全直接解析HTML虽然直观但容易受页面改版影响。更稳健的方式是寻找B站内部使用的API接口。通过浏览器开发者工具的“网络Network”选项卡我们可以发现当滚动加载UP主视频列表时浏览器会向一个特定的API地址发送请求返回结构清晰的JSON数据。API接口示例一个常见的获取UP主视频投稿列表的API是https://api.bilibili.com/x/space/arc/search?mid{mid}ps30tid0pn{page}keywordorderpubdatejsonpjsonpmid: UP主IDps: 每页数量 (page size)pn: 页码 (page number)orderpubdate: 按发布时间排序代码实现import requests import json def get_up_videos_by_api(mid, max_page5): video_list [] for page in range(1, max_page1): api_url fhttps://api.bilibili.com/x/space/arc/search?mid{mid}ps30pn{page}orderpubdate resp requests.get(api_url, headersheaders) data resp.json() if data[code] 0: # 请求成功 vlist data[data][list][vlist] for v in vlist: bvid v[bvid] title v[title] aid v[aid] # JSON里信息很全还有播放量、评论数等 video_list.append({bvid: bvid, title: title, aid: aid}) else: print(f第{page}页请求失败: {data.get(message)}) break return video_list为什么推荐API路径API返回的数据是结构化的JSON解析简单且稳定不易受前端页面样式变化的影响。它通常包含了视频的bvid、aid、标题、封面、播放量、时长等丰富信息是我们获取视频元数据的首选方法。4. 视频流地址解析与下载策略获取到视频的bvid或aid后下一步就是找到视频文件的真实下载地址。这是整个爬虫最核心也最具挑战性的部分因为B站对视频流进行了加密和分段处理。4.1 获取视频播放页信息与cid每个视频包括分P视频的每一P都有一个唯一的cid。我们需要先通过一个API根据bvid获取到视频的基本信息特别是cid。def get_video_cid(bvid): 根据BV号获取视频的cid分P列表 api_url fhttps://api.bilibili.com/x/web-interface/view?bvid{bvid} resp requests.get(api_url, headersheaders) data resp.json() if data[code] 0: # 返回所有分P的信息 pages data[data][pages] # 通常第一个就是主视频如果是多P视频pages是一个列表 cid_list [{cid: page[cid], page: page[page], part: page[part]} for page in pages] return cid_list else: print(f获取{bvid}的cid失败) return None4.2 解析含密钥的播放地址最关键的步骤有了cid和bvid或aid我们就可以请求获取视频的播放流信息。这个接口返回的JSON数据中包含了不同清晰度如1080P, 720P, 480P对应的视频流(video)和音频流(audio)的URL地址。但是这些URL是经过加密的包含了token、auth等参数并且有过期时间。def get_play_url(bvid, cid, quality80): 获取视频播放地址 quality: 清晰度标识80代表1080P64代表720P32代表480P api_url fhttps://api.bilibili.com/x/player/playurl?bvid{bvid}cid{cid}qn{quality}fnval16fourk1 # fnval16 表示获取dash格式流音视频分离这是高清视频的格式 # fnval1 表示获取flv格式流音视频合并但可能无法获取最高清晰度 resp requests.get(api_url, headersheaders) data resp.json() if data[code] 0: dash data[data][dash] video_url dash[video][0][baseUrl] # 视频流地址 audio_url dash[audio][0][baseUrl] # 音频流地址 # 注意这些url包含了鉴权参数不能直接分享且会过期 return video_url, audio_url else: print(f获取播放地址失败: {data.get(message)}) return None, None重要提示从这个接口获取到的baseUrl是带有签名参数的直接用于下载是有效的但这些链接有效期很短通常几分钟到几小时。因此我们的策略应该是在解析出播放地址后立即开始下载任务不宜长时间存储这些URL。4.3 音视频流下载与FFmpeg合并下载下来的视频流(.m4s)和音频流(.m4s)是分开的我们需要用FFmpeg将它们合并。步骤分别下载使用Requests的流式下载模式将视频流和音频流保存为临时文件例如temp_video.m4s和temp_audio.m4s。def download_file(url, filename, headers): resp requests.get(url, headersheaders, streamTrue) with open(filename, wb) as f: for chunk in resp.iter_content(chunk_size1024*1024): # 1MB chunks if chunk: f.write(chunk)调用FFmpeg合并使用Python的subprocess模块调用系统命令执行FFmpeg合并。import subprocess import os def merge_media(video_path, audio_path, output_path): # 构建ffmpeg命令 cmd [ ffmpeg, -i, video_path, -i, audio_path, -c:v, copy, # 视频流直接复制不重新编码速度快 -c:a, copy, # 音频流直接复制 -y, # 覆盖已存在文件 output_path ] try: subprocess.run(cmd, checkTrue, capture_outputTrue) print(f合并成功: {output_path}) # 合并后删除临时文件 os.remove(video_path) os.remove(audio_path) except subprocess.CalledProcessError as e: print(f合并失败: {e.stderr.decode()})-c:v copy和-c:a copy参数是关键它们指示FFmpeg直接流复制而不进行耗时的重新编码能在几秒钟内完成合并保证效率。5. 工程化实践构建健壮的批量爬虫框架掌握了核心的单视频下载流程后我们需要将其工程化构建一个能够处理批量任务、具备错误恢复能力、并考虑效率的爬虫框架。5.1 任务队列与并发控制我们不能简单地用一个for循环顺序下载几百个视频那会耗费大量时间。我们需要引入并发。方案一使用concurrent.futures.ThreadPoolExecutor线程池这是最简单易懂的并发方案适合I/O密集型任务。from concurrent.futures import ThreadPoolExecutor, as_completed def batch_download_video_info_list(video_info_list, max_workers5): 批量下载视频信息列表 failed_tasks [] with ThreadPoolExecutor(max_workersmax_workers) as executor: # 提交任务到线程池 future_to_video {executor.submit(download_single_video, info): info for info in video_info_list} for future in as_completed(future_to_video): video_info future_to_video[future] try: result future.result() if not result: failed_tasks.append(video_info) except Exception as e: print(f任务{video_info[title]}发生异常: {e}) failed_tasks.append(video_info) print(f批量下载完成。失败任务数: {len(failed_tasks)}) return failed_tasks关键参数max_workers并非越大越好。B站服务器有反爬机制过高的并发请求会被限制或封禁IP。建议设置在3-10之间并可以在请求间加入随机延时time.sleep(random.uniform(1, 3))来模拟人类操作。方案二使用aiohttp进行异步IO更高性能对于大规模批量任务异步IO比多线程效率更高资源消耗更少。但代码复杂度也更高。import aiohttp import asyncio async def async_download_single(session, video_info, semaphore): async with semaphore: # 用信号量控制并发度 # 使用异步session进行所有HTTP请求 # ... 异步版本的 get_play_url, download_file 等 pass async def main_async(video_list): connector aiohttp.TCPConnector(limit10) # 控制总连接数 timeout aiohttp.ClientTimeout(total300) async with aiohttp.ClientSession(connectorconnector, timeouttimeout, headersheaders) as session: semaphore asyncio.Semaphore(5) # 控制最大并发任务数 tasks [async_download_single(session, info, semaphore) for info in video_list] await asyncio.gather(*tasks, return_exceptionsTrue)5.2 错误处理与重试机制网络请求充满不确定性必须要有完善的错误处理。连接超时/读取超时在Requests中设置timeout参数并为aiohttp配置ClientTimeout。HTTP错误状态码如403 404 429检查响应对象的status_code对于429请求过多可以等待一段时间后重试。JSON解析错误使用try...except json.JSONDecodeError捕获。文件写入错误检查磁盘空间和文件权限。FFmpeg合并错误检查临时文件是否完整下载FFmpeg路径是否正确。实现一个带重试的请求函数import time def request_with_retry(url, max_retries3, **kwargs): for i in range(max_retries): try: resp requests.get(url, timeout10, **kwargs) resp.raise_for_status() # 如果状态码不是200抛出HTTPError异常 return resp except (requests.exceptions.Timeout, requests.exceptions.HTTPError) as e: if i max_retries - 1: raise e # 最后一次重试失败抛出异常 wait_time 2 ** i # 指数退避 print(f请求{url}失败{e}。第{i1}次重试等待{wait_time}秒...) time.sleep(wait_time) return None5.3 配置化与日志记录一个好的项目应该将易变的参数如请求头、并发数、下载路径、清晰度选择提取到配置文件如config.yaml或config.ini中。同时完善的日志系统对于调试和监控运行状态至关重要。import logging import yaml # 需要安装PyYAML # 读取配置 with open(config.yaml, r, encodingutf-8) as f: config yaml.safe_load(f) DOWNLOAD_PATH config[download][path] MAX_WORKERS config[concurrency][max_workers] USER_AGENT config[headers][user_agent] # 配置日志 logging.basicConfig( levellogging.INFO, format%(asctime)s - %(name)s - %(levelname)s - %(message)s, handlers[ logging.FileHandler(spider.log, encodingutf-8), logging.StreamHandler() ] ) logger logging.getLogger(__name__) # 在代码中使用logger代替print logger.info(f开始下载任务保存路径: {DOWNLOAD_PATH}) try: # ... 业务逻辑 except Exception as e: logger.error(f下载过程中发生错误: {e}, exc_infoTrue)6. 进阶挑战应对B站的反爬机制与风控随着你爬取频率和数量的增加很可能会触发B站的反爬机制。常见的现象包括返回错误数据、要求验证码、甚至暂时封禁IP。这里分享一些应对策略。6.1 请求头Headers的精细化伪装仅仅一个User-Agent是不够的。你需要让你的请求看起来更像一个真实的浏览器。使用浏览器开发者工具复制一次正常访问时的所有请求头。headers { 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, Referer: https://www.bilibili.com/, # 关键表明请求来源 Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Accept-Encoding: gzip, deflate, br, Connection: keep-alive, Upgrade-Insecure-Requests: 1, Sec-Fetch-Dest: document, Sec-Fetch-Mode: navigate, Sec-Fetch-Site: same-origin, Sec-Fetch-User: ?1, Cache-Control: max-age0, }特别注意Referer头对于获取播放地址的API至关重要很多情况下缺失或不正确的Referer会导致403错误。6.2 会话Session与Cookies管理使用requests.Session()可以自动管理Cookies保持登录状态如果你需要爬取需要登录才能看的视频。你可以先手动在浏览器登录B站然后通过开发者工具将Cookie字符串复制出来加载到Session中。session requests.Session() cookie_str 你的Cookie字符串 # 从浏览器复制 cookies {item.split()[0]: item.split()[1] for item in cookie_str.split(; )} session.cookies.update(cookies) session.headers.update(headers) # 之后使用session进行请求警告请妥善保管你的Cookies不要泄露或上传到公开仓库。它等同于你的登录凭证。6.3 请求频率控制与IP代理这是最有效的反反爬策略之一。频率控制在请求之间加入随机延时模拟人类阅读和点击的间隔。import random time.sleep(random.uniform(1.5, 4.0)) # 随机等待1.5到4秒使用代理IP当单个IP被限制时可以使用代理IP池来轮换请求。你可以购买付费代理服务或者寻找一些免费的代理IP但免费代理通常不稳定。在Requests中使用代理很简单proxies { http: http://your-proxy-ip:port, https: http://your-proxy-ip:port, } resp requests.get(url, headersheaders, proxiesproxies)6.4 处理“风控”验证有时B站会返回一个包含“验证”或“安全拦截”的页面。这种情况下简单的请求头伪装可能不够。这可能意味着你需要降低爬取速度。确保Cookies有效。考虑模拟更完整的浏览器环境这涉及到更复杂的工具如Selenium或Playwright它们可以驱动真实浏览器但资源消耗大速度慢一般作为最后手段。一个基本原则对目标网站保持友好。你的爬虫不应该影响B站服务器的正常运行。控制速度、处理异常、尊重robots.txt虽然B站的相关规则可能比较严格是每个爬虫开发者应有的素养。7. 项目源码结构设计与使用指南一个清晰的项目结构不仅方便自己维护也便于他人理解和复用。以下是我为这个练习项目设计的目录结构建议bilibili-video-downloader/ ├── config.yaml # 配置文件 ├── requirements.txt # 依赖库列表 ├── main.py # 主程序入口 ├── core/ # 核心模块 │ ├── __init__.py │ ├── bilibili_api.py # 封装所有B站API请求函数 │ ├── downloader.py # 下载与合并逻辑 │ └── parser.py # 页面/数据解析函数 ├── utils/ # 工具模块 │ ├── __init__.py │ ├── logger.py # 日志配置 │ ├── retry.py # 重试装饰器 │ └── proxy.py # 代理IP管理 ├── tasks/ # 任务定义 │ ├── up_task.py # 下载UP主所有视频任务 │ └── series_task.py # 下载系列合集任务 ├── data/ # 数据目录 │ ├── video_list.json # 临时存储的视频列表 │ └── failed_tasks.log # 失败任务记录 └── downloads/ # 视频下载保存目录按UP主/系列分类使用指南克隆或解压源码将python练习源码.zip解压到本地。配置环境按照第2部分安装Python依赖和FFmpeg。修改配置编辑config.yaml设置你的下载路径、并发数、请求头特别是User-Agent等。如果需要下载会员视频在此处配置你的Cookies请务必谨慎。运行主程序运行main.py它可能会提供一个简单的命令行交互界面让你输入UP主的ID、视频合集链接或BV号列表。查看结果与日志下载的视频会保存在downloads/目录下运行日志会输出到控制台并记录在spider.log文件中。失败的任务会记录在data/failed_tasks.log方便后续重试。这个项目源码的价值在于它提供了一个完整、可运行、可修改的范本。你可以根据自己的需求轻松地修改它来爬取其他信息如弹幕、评论或者集成到更大的数据分析流程中。记住最好的学习方式就是“运行它修改它打破它再修复它”。本文还有配套的精品资源点击获取