BiliBili视频下载-原理与实现Python+FFmpeg

server/2025/3/1 15:42:55/

脚本地址:

项目地址: Gazer

BiliGrab.py

提要

适用于: 登录状态下, 非大会员视频下载.

自动解析任意 B 站非大会员 / 付费视频的视频 & 音频请求链接并下载, 需要添加 Cookie 保证视频清晰度. 使用 FFmpeg 命令无损合并视频和音频.

使用方法

  1. 克隆或下载项目代码.
  2. 安装依赖: pip install requests, 或者克隆项目代码后 pip install -r requirements.txt
  • 脚本顶部: 指定常量 FOLDER_PATH, 保存视频和音频文件夹路径
  • 主函数处: 填写你的 cookie
  • 主函数处: 指定 url, 要下载的视频 URL
  • 主函数处: 指定 video_file_nameaudio_file_name, 要保存的视频和音频文件名

代码结构

  1. get_url 从 HTML 中的 window.__playinfo__ 提取解析视频的 2 个请求 URL
  2. download_video 接收 get_url 的返回链接, 下载视频/音频文件

抓包

登录状态下, 随机打开哔哩哔哩的一个视频, 打开 devtool, 播放一会视频, 让请求一一列出. 然后在 Network 面板的过滤器中, 输入 m4s, 过滤出与视频相关的请求. 点 Size 从大到小排列.

在这里插入图片描述

m4s 过滤可以同时过滤出音频和视频, Type 都是 application/octet-stream. 这证明了音频和视频数据都是以 m4s 格式存储的, 并且使用了相同的 MIME 类型.

请求为 ...100026... 的是视频请求网址, 请求为 ...30280... 的是音频请求网址.

点开请求可以看到, Status Code: 206 Partial Content, 表示服务器成功处理了部分 GET 请求, 而且说明视频内容是一部分一部分传输的.

复制视频/音频的请求 URL, 分别使用 requests get 一下, 能够成功下载完整视频和音频.

代码示例 (下载单个 m4s 片段):

python">import requestsvideo_url = "Request_URL"  # 替换成你找到的请求链接headers = {"Referer": "https://www.bilibili.com/","User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
}response = requests.get(video_url, headers=headers, stream=True)if response.status_code == 200 or response.status_code == 206:with open("video_part.m4s", "wb") as f:for chunk in response.iter_content(chunk_size=8192):if chunk:f.write(chunk)print("下载成功!")
else:print("下载失败!")print(response.status_code)

自动解析视频和音频的请求 URL

现在这个脚本只能自己通过 devtool 来查看请求的 URL 然后复制到脚本再下载, 那么如何做到我们只需要输入 B 站视频的地址, 即可自动获取它的 2 个请求 (视频和音频) 的 URL 呢?

分析 B 站视频页面的 HTML 结构:

  • 找到包含视频和音频 URL 的 HTML 元素或 JavaScript 变量.
  • 通常, 视频和音频的 URL 会以某种形式嵌入在网页中, 例如:
    • <video> 标签的 src 属性中(但 B 站通常不是这样).
    • 在 JavaScript 变量中(例如一个名为 window.__playinfo__ 的变量, 这是 B 站常用的).
    • 在 JSON 数据中(例如一个包含视频信息的 JSON 对象).
  1. play_info 的提取:

    • 原先使用了 soup.select_one('head > script:nth-child(58)') 来定位包含 play_info<script> 标签. 这种方法依赖于 <script> 标签在 HTML 中的具体位置(第 58 个子元素). 如果 B 站的网页结构发生变化, 这个选择器可能会失效.
    • 更可靠的方法是使用正则表达式 window\.__playinfo__\s*=\s*({.*?"video":.*?"audio":.*?"session":.*?})\s* 来提取 window.__playinfo__ 变量的值.
      • 一些有用的网站
        • https://regex101.com/ 检查正则表达式能否正确匹配到所有的数据
        • https://jsonformatter.org/json-pretty-print 美化 HTML 中的单行 json 格式
        • https://jsonlint.com/ 验证 json 格式是否正确

正则表达式详解

window\.__playinfo__\s*=\s*({.*?"video":.*?"audio":.*?"session":.*?})\s*

这个正则表达式从 HTML 源代码中提取 B 站视频页面的 window.__playinfo__ 变量的值. window.__playinfo__ 是一个 JavaScript 变量, 它包含了视频和音频的播放信息(例如 URL、清晰度、编码等), 通常以 JSON 格式存储.

详细解释:

  1. window\.__playinfo__: 匹配字符串 “window.__playinfo__”.

    • window: 匹配字面上的 “window”.
    • \.: 匹配一个点号 “.”. 由于点号在正则表达式中有特殊含义(匹配任意字符), 所以需要使用反斜杠 \ 进行转义.
    • __: 匹配两个下划线 “_”.
    • playinfo: 匹配字面上的 “playinfo”
  2. \s*: 匹配零个或多个空白字符(空格、制表符、换行符等).

  3. =: 匹配等号 “=”.

  4. \s*: 匹配零个或多个空白字符.

  5. ({.*?}): 这是整个正则表达式的核心, 用于捕获 window.__playinfo__ 变量的值(JSON 对象).

    • (): 定义一个捕获组, 用于提取匹配到的内容.
    • {}: 匹配大括号, 表示 JSON 对象的开始和结束.
    • .*?: 匹配任意字符(除了换行符), *? 表示非贪婪模式, 尽可能少地匹配字符.
      • .*?"video":: 匹配任意字符直到 "video": 出现.
      • .*?"audio":: 匹配任意字符直到 "audio": 出现.
      • .*?"session":: 匹配任意字符直到"session"出现.
      • .*?: 匹配任意字符直到 }.
  6. \s*: 匹配在结尾大括号后的零个或多个空白字符.

在 Python 中使用(示例):

python">import re
import jsonhtml = """
<script>window.__playinfo__ = {"data": {"dash": {"video": [{"id": 30, "baseUrl": "video_url_360p"},{"id": 80, "baseUrl": "video_url_1080p"}],"audio": [{"id": 30280, "baseUrl": "audio_url"}],"session": "session_id"}}};// 其他 JavaScript 代码
</script>
"""match = re.search(r'window\.__playinfo__\s*=\s*({.*?"video":.*?"audio":.*?"session":.*?})\s*', html)if match:playinfo_json = match.group(1)  # 获取捕获组的内容(JSON 字符串)playinfo = json.loads(playinfo_json)  # 解析 JSON 字符串print(playinfo)

带上 Cookie 确保至少可以观看 1080P

目前位置脚本没有加上 Cookie, 如果在自动解析之后仍然不带上 Cookie, 只能下载到 360P 的视频. 下面来确认一下:

1. 未登录状态:

在未登录状态下, B 站只能播放 360P 视频.

2. 参数缺失:

请求 URL 缺少某些参数. 通过比较手动抓取的 URL 和脚本抓取的 URL, 可以发现一些差异.

  • 程序抓取的 URL:
https://xy60x163x162x138xy240eyf7ye000y660yy1bxy.mcdn.bilivideo.cn:4483/upgcxcode/99/38/1482513899/1482513899-1-100022.m4s?e=ig8euxZM2rNcNbdlhoNvNC8BqJIzNbfqXBvEqxTEto8BTrNvN0GvT90W5JZMkX_YN0MvXg8gNEV4NC8xNEV4N03eN0B5tZlqNxTEto8BTrNvNeZVuJ10Kj_g2UB02J0mN0B5tZlqNCNEto8BTrNvNC7MTX502C8f2jmMQJ6mqF2fka1mqx6gqj0eN0B599M=&uipk=5&nbs=1&deadline=1740721988&gen=playurlv2&os=mcdn&oi=2104662226&trid=00004af14e75c1fa489794b0c1129a26c41eu&mid=0&platform=pc&og=cos&upsig=d9494a1a248c855fea3d23418a267e22&uparams=e,uipk,nbs,deadline,gen,os,oi,trid,mid,platform,og&mcdnid=50015422&bvc=vod&nettype=0&orderid=0,3&buvid=23327D6A-D1D8-11E7-0808-4DEDFA843F7716556infoc&build=0&f=u_0_0&agrr=0&bw=19818&logo=A0020000
  • 手动抓取的 URL (高清):
https://xy124x225x28x109xy.mcdn.bilivideo.cn:8082/v1/resource/1482513899-1-100026.m4s?agrr=1&build=0&buvid=75265G3Y-C2S1-66P3-8396-2FVCDS983Y0925738infoc&bvc=vod&bw=88882&deadline=1740702720&e=ig8euxZM2rNcNbdlhoNvNC8BqJIzNbfqXBvEqxTEto8BTrNvN0GvT90W5JZMkX_YN0MvXg8gNEV4NC8xNEV4N03eN0B5tZlqNxTEto8BTrNvNeZVuJ10Kj_g2UB02J0mN0B5tZlqNCNEto8BTrNvNC7MTX502C8f2jmMQJ6mqF2fka1mqx6gqj0eN0B599M%3D&f=u_0_0&gen=playurlv2&logo=A0020000&mcdnid=50016483&mid=73467246&nbs=1&nettype=0&og=cos&oi=2104662226&orderid=0%2C3&os=mcdn&platform=pc&sign=a9b5fe&traceid=tranVDKskSntAK_0_e_N&uipk=5&uparams=e%2Cuipk%2Cnbs%2Cdeadline%2Cgen%2Cos%2Coi%2Ctrid%2Cmid%2Cplatform%2Cog&upsig=caca7e9a425e2dbc2c491a81e9acda17

关键参数差异:

  • agrr=1: 高清 URL 包含 agrr=1, 而程序抓取的 URL 包含 agrr=0. 这可能表示 “Adaptive Group Representation Rate”(自适应组表示率), agrr=1 可能表示启用自适应码率, 允许选择更高清的视频流.
  • buvid: 高清 URL 包含 buvid=75265G3Y-C2S1-66P3-8396-2FVCDS983Y0925738infoc, 而程序抓取的 URL 包含 buvid=(为空). buvid 是 B 站用来识别用户的设备 ID.
  • mid: 高清 URL 包含 mid=73467246(你的用户 ID), 而程序抓取的 URL 包含 mid=0.
  • traceidsign: 高清 URL 包含 traceidsign, 而程序抓取的 URL 不包含. 这些参数可能是 B 站用来验证请求的合法性.

解决方案:

添加 Cookie:

  • 从浏览器中获取 B 站 Cookie(在登录状态下).

  • 将 Cookie 添加到 requests 请求头中.

    python">headers = {"Referer": "https://www.bilibili.com/","User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36","Cookie": "YOUR_COOKIE"  # 替换为你的 B 站 Cookie
    }
    

使用 FFmpeg 无损合并视频和音频

下载完音频后, 我们可以尝试将视频和音频合并起来. 这需要用到一些视频编辑工具, 例如 ffmpeg.

FFmpeg:

  • ffmpeg 是一个非常强大的命令行工具, 可以用来处理各种视频和音频文件.
  • 先安装 ffmpeg, 然后才能在命令行中使用它. 可以在 ffmpeg 的官网找到安装教程.

使用 FFmpeg 合并视频和音频

下载 FFmpeg
  1. Windows 11, 64 位环境下, 下载包含 win64 的版本.

其中 gpllgpl 的区别:

  • GPL (GNU General Public License): GPL 许可的 FFmpeg 版本包含了所有功能, 包括一些受专利保护的编解码器, 例如 x264, x265 等等.
  • LGPL (GNU Lesser General Public License): LGPL 许可的 FFmpeg 版本功能相对较少, 它不包含那些受专利保护的编解码器.

**在实际使用中, GPL 版本的功能更强大, 支持的格式更多, 所以我们一般选择 GPL 版本. **

sharedstatic 的区别:

  • Shared: Shared 版本表示动态链接库版本, 它的体积比较小, 运行时需要依赖系统中的一些 DLL 文件.
  • Static: Static 版本表示静态链接库版本, 它的体积比较大, 它将所有依赖的库都打包到了一个可执行文件中, 不需要依赖系统中的 DLL 文件, 可以独立运行.

第一次使用 FFmpeg 下载 Static 版本更方便, 不需要配置环境变量等操作.

比如ffmpeg-n5.1-latest-win64-gpl-5.1.zip 或者 ffmpeg-master-latest-win64-gpl.zip

这两个版本都是 64 位 Windows 系统下的 GPL 许可的静态链接库版本, 功能强大且使用方便. 随便下载哪个都行, master 是最新的开发版本, n5.1-latest 是最新的 5.1 的稳定版本. 对于我们目前的需求, 其实哪个都行.

  1. 将 FFmpeg 的 bin 目录 C:\ffmpeg\bin 添加到系统环境变量中:

添加完环境变量后, 就可以在命令行中直接使用 FFmpeg 命令了. 可以打开命令行, 输入 ffmpeg -version, 如果能够看到 FFmpeg 的版本信息, 就说明配置成功了.

FFmpeg 命令
ffmpeg -i video_part.m4s -i audio.mp4 -c:v copy -c:a copy output.mp4

命令说明:

  • ffmpeg: 调用 FFmpeg 程序.
  • -i video_part.m4s: 指定视频输入文件.
  • -i audio.mp4: 指定音频输入文件.
  • -c:v copy: 指定视频编码器为 copy, 表示直接复制视频流, 不进行重新编码.
  • -c:a copy: 指定音频编码器为 copy, 表示直接复制音频流, 不进行重新编码.
  • output.mp4: 指定输出文件名.

命令说明:**

  • ffmpeg: 调用 FFmpeg 程序.
  • -i video_part.m4s: 指定视频输入文件.
  • -i audio.mp4: 指定音频输入文件.
  • -c:v copy: 指定视频编码器为 copy, 表示直接复制视频流, 不进行重新编码.
  • -c:a copy: 指定音频编码器为 copy, 表示直接复制音频流, 不进行重新编码.
  • output.mp4: 指定输出文件名.

这个命令会将 video_part.m4s 和 audio.m4s 合并成一个名为 output.mp4 的文件, 而且是无损合并.


http://www.ppmy.cn/server/171574.html

相关文章

Ubuntu+deepseek+Dify本地部署

1.deepseek本地部署 在Ollama官网下载 需要魔法下载 curl -fsSL https://ollama.com/install.sh | sh 在官网找到需要下载的deepseek模型版本 复制命令到终端 ollama run deepseek-r1:7b 停止ollama服务 sudo systemctl stop ollama # sudo systemctl stop ollama.servi…

论软件设计模式及其应用-软考

软件设计模式(Software Design Pattern)是一套被反复使用的、多数人知晓的代码设计经验的总结。使用设计模式是为了重用代码以提高编码效率、增加代代码可理解性、保证代码的可靠性。软件设计模式是软件开发中的最佳实践之一,它经常被开发人员在面向对象软件开发过程中所采用…

【大模型】大模型推理能力深度剖析:从通用模型到专业优化

大模型推理能力深度剖析&#xff1a;从通用模型到专业优化 大模型推理能力深度剖析&#xff1a;从通用模型到专业优化一、通用语言模型与推理模型的区别&#xff08;一&#xff09;通用语言模型&#xff1a;多任务的“万金油”&#xff08;二&#xff09;推理模型&#xff1a;复…

1.2.3 使用Spring Initializr方式构建Spring Boot项目

本实战概述介绍了如何使用Spring Initializr创建Spring Boot项目&#xff0c;并进行基本配置。首先&#xff0c;通过Spring Initializr生成项目骨架&#xff0c;然后创建控制器HelloController&#xff0c;定义处理GET请求的方法hello&#xff0c;返回HTML字符串。接着&#xf…

git 强推

1、查看git版本 git --version 如果你已经安装了 Git&#xff0c;可以检查是否安装成功&#xff1a; 打开命令提示符&#xff08;CMD&#xff09;或 PowerShell。输入 git --version&#xff0c;如果安装成功&#xff0c;应该会显示 Git 的版本信息。 2、强推 git push or…

算法-二叉树篇14-从中序与后序遍历序列构造二叉树

从中序与后序遍历序列构造二叉树 力扣题目链接 题目描述 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 解题思路 这道题很有难度&#xff0…

Day11 洛谷题第一阶段总结

给大家看一下上面的是我上一个阶段所写的&#xff0c;因为我要准备校赛&#xff0c;蓝桥杯&#xff0c;所以我写的这些题目&#xff0c;我打算在4.12号之前写完60道题&#xff0c;最近这几天其实我心情不是很美丽&#xff0c;因为我觉得真的好辛苦啊&#xff0c; 主要还得是因…

蓝桥杯(握手问题)

小蓝组织了一场算法交流会议&#xff0c;总共有 50 人参加了本次会议。在会议上&#xff0c;大家进行了握手交流。按照惯例他们每个人都要与除自己以外的其他所有人进行一次握手 (且仅有一次)。但有 7 个人&#xff0c;这 7 人彼此之间没有进行握手 (但这 7 人与除这 7 人以外的…