最近学习各种知识,已经从 Youtube 迁移到了 Bilibili 。之前一直以为B站是个二次元的世界。虽然现在也是,但是各种学习资源简直太多了。最主要的原因就是没有广告!有些视频真的很抢手,但是偶尔会有版权问题,会被B站下架,所以B站的收藏夹偶尔会出现视频丢失的情况。为了避免这种情况的发生,决定把优秀的资源下载到本地保存。所以又要请出我们的神器Python了。

利用 Python 的 Requests 库,基本上网络上的资源没什么不能下载的。即使有,那么加上 Selenium 也不成问题。

一、利用you-get下载哔哩哔哩播放列表

you-get 是一个开源程序,专门用来下载各种视频的,从名字上来看,就知道起初应该只是为了下载 Youtube 视频的,之后随着各种 contributer 的加入,发展成了一个可以下载各种视频网站的工具。可以在这里查看中文说明。

1. 安装 you-get

安装 you-get 之前,还得安装 you-get 的各种依赖才可以进行使用。
- python
- FFmpeg

2. 使用 you-get

使用方法也很简单,直接一句话代码下载即可。

you-get --playlist <url>

PS:关于 you-get 不知道为什么,有时候总是出现没速度的情况,而且关于文件名问题,没办法自定义。Forece 下载了几P,然后就不断的出现超时错误。于是就放弃了这个强大的工具。

二、利用 Python 下载B站播放列表视频

关于 Python 爬虫就不多说了,这里也写不下,大家自己去B站学吧。这里只是讲一下分析过程。

1. 分析网页

通过分析视频列表页,发现基本上所有信息都在 HTML 里边了。

1. 视频、音频地址

通过分析,发现视频、音频被拆分为两部分。而这段代码都在

<script>window.__playinfo__=

这段代码之后,进行 JSON 格式化之后大概是这样式的,通过分析,拿到当前播放视频的 Video URL 还有 Audio URL。

2. 合并文件

因为视频和音频是分离的,所以需要用到 FFmpeg 来将两个文件进行合并。记得需要将 FFmpeg 加入环境变量先。合并命令是:

ffmpeg -i video.mp4 -i audio.mp4 -c copy final.mp4

3. 视频列表信息

知道怎么下载单个视频后,就需要对整个列表进行分析。很幸运的是,在搜索相关资料的时候,发现了一个 API,

https://api.bilibili.com/x/player/pagelist?aid=xxxxxx

通过访问这个网址,就可以拿到一个 JSON 文件,里边就是播放列表里边的所有信息,不过需要传入一个 aid 值。 aid 值也很容易找到,在原本的 HTML 中就可以利用正则提取到。之后我们就通过遍历这个 JSON 文件,然后就能下载所有的视频了。

其实视频页面的 HTML 也能拿到这个列表,不过利用正则提取稍微麻烦了点。所以不如直接利用 API 获取信息。

4. 完整代码

import re
import requests
import json
import os

headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36',
           'cookie': ""  # YOUR COOKIES
           }

def download_single_video(url, name):
    res = requests.get(url, headers=headers)
    video_pattern = '__playinfo__=(.*?)</script><script>'
    playlist_info = json.loads(re.findall(video_pattern, res.text)[0])
    video_url = playlist_info['data']['dash']['video'][0]['baseUrl']
    audio_url = playlist_info['data']['dash']['audio'][0]['baseUrl']
    save_file(video_url, 'video')
    save_file(audio_url, 'audio')
    merge(name)
    print('{} 下载完毕'.format(name))

def save_file(url, type):
    download_content = requests.get(url, headers=headers).content
    with open('{}.mp4'.format(type), 'wb') as output:
        output.write(download_content)

def merge(name):
    command = 'ffmpeg -i video.mp4 -i audio.mp4 -c copy "{}".mp4'.format(name)
    os.system(command)
    os.remove('video.mp4')
    os.remove('audio.mp4')

def get_list_info(url):
    aid_pattern = 'window.__INITIAL_STATE__={"aid":(\d*?),'
    res = requests.get(url, headers=headers)
    aid = re.findall(aid_pattern, res.text)[0]
    playlist_json_url = 'https://api.bilibili.com/x/player/pagelist?aid={}'.format(aid)
    json_info = json.loads(requests.get(playlist_json_url, headers=headers).content.decode('utf-8'))['data']
    return json_info


if __name__ == '__main__':
    base_url = 'https://www.bilibili.com/video/BV14J4114768'
    json_info = get_list_info(base_url)
    for i in json_info:
        p = i['page']
        name = 'P{} - {}'.format(p, i['part'])
        url = base_url + '?p={}'.format(p)
        download_single_video(url, name)