将B站视频搬运到本地进行观看

👋 前言

当你想听周董的歌的时候,你会发现到处都在收费,好在我们还有良心的bilibili用户提供了很多优质的视频,可以将视频下载到本地,建立自己喜欢的音乐集,随时播放。

💡 正文

1 如何进行下载

我们可以使用python的requests库获取你想下载视频页面的资源,通过二进制文件来保存到本地即可。

2 网站分析

2.1 资源分析

打开一个视频网站,通过F12打开控制台,选择元素获取到网页源码,通过分析找到视频音频等资源保存在一个script标签中,所以我们可以通过源码中资源获取到我们想要的内容,本文通过正则进行提取。
分析截图

2.2 地址分析

根据对地址的分析发现,我们访问网站的地址,如:https://www.bilibili.com/video/BV11p4y1b7ej/,只有路径中最后的BV开头的内容不一致,可以断定为BV11p4y1b7ej这个就是当前视频的地址,所以后续批量下载的时候,我们只需要修改这个值即可

3 规划技术

编程语言为python,这里使用的是3.7.3,需要用到第三方的库,其中moviepy用来进行音频视频合并的,loguru用来日志打印

pip install moviepy
pip install requests
pip install loguru

4 实现思路

4.1 数据封装

根据前面的2.2的分析,我们每次访问的时候,只需要修改请求中BV的值即可,所以单独把数据放在data.py文件中,使用字典进行保存

chn_dict = {
    'BV11p4y1b7ej': '周杰伦《一路向北》', 'BV1KE411b7by': '周杰伦《轨迹》', 'BV1nY4y1o7tv': '周杰伦《龙拳》',
    'BV1TY41167Tm': '周杰伦《你听得到》', 'BV1qD4y1U7fs': '周杰伦《七里香》', 'BV1sP411H7Ce': '周杰伦《黑色毛衣》',
    'BV1Xe4y187Gn': '周杰伦《乱舞春秋》', 'BV1iB4y1E7XE': '周杰伦《借口》', 'BV1KG411G7NK': '周杰伦《搁浅》'
}

其中chn_dict表示一个字典,字典中的键位请求地址后面的BV值,字典的值为下载资源后保存的文件名称

4.2 资源获取

新建一个名为download_by_dict.py文件,这里将具体实现download的操作。

4.2.1 导入用到的库
import json
import os
import re
import sys
import time
import requests
from moviepy.editor import VideoFileClip, AudioFileClip
from loguru import logger
from dataclasses import dataclass, field
4.2.2 定义一个下载类
@dataclass
class DownloadVideo:
    # 全局参数
    # 请求头
    headers = {
        "referer": "https://www.bilibili.com/",
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36"
    }
    # 标题
    title = None
    # 网页源码
    source = None

    # 初始化参数
    # 资源列表
    source_list: dict
    # 文件保存路径
    dir: str
    # 是否合并视频
    is_video: bool = False
    # 基础路径
    basic_url: str = field(init=False)

    def __post_init__(self) -> None:
        """
        初始化
        :return:
        """
        self.basic_url = 'https://www.bilibili.com/video/{}/'
        if not os.path.exists(self.dir):
            logger.info('创建目录:{}'.format(self.dir))
            os.makedirs(self.dir)
  • 首先这里使用@dataclass对类进行装饰,简化类的编写,类似java中lombok的作用,这里不过多赘述。
  • 初始化一些变量,其中:
    • titlesource是用来保存文件没和源码的全局变量
    • source_list:表示资源,就是data.py中定义的字典
    • dir:下载后保存的路径,初始化的时候对路径进行了判断,如果不存在就创建
    • is_video:判断是否进行音频视频合并,默认False(不合并,就下载音乐听听才会用)
    • basic_url:基础路径,路径中{}是后面格式化时使用的
4.2.3 获取网站资源
def get_source_code(self, url: str) -> str:
    """
    获取源码
    :param url:
    :return:
    """
    return requests.get(url=url, headers=self.headers).text

url为请求地址,通过循环data.py中的字典,使用键对基础路径进行format操作,即可获取完整路径

4.2.4 获取音频视频资源
def get_video_or_audio_content(self, type: str) -> bytes:
    """
    获取视频音频资源
    :param type: 类型(audio音频,video视频)
    :return:
    """
    play_info = re.findall('<script>window.__playinfo__=(.*?)</script>', self.source)[0]
    json_data = json.loads(play_info)
    source_url = json_data['data']['dash'][type][0]['baseUrl']
    # pprint.pprint(source_url)
    content = requests.get(url=source_url, headers=self.headers).content
    return content

根据2.1分析的,通过页面源码使用正则获取视频音频的资源

其中该方法需要传入参数,根据不通的参数来决定下载音频还是视频

self.source是页面源码

4.3 资源下载

4.3.1 下载音频视频
def download_audio(self) -> None:
    """
    下载音频
    :return:
    """
    audio = self.get_video_or_audio_content('audio')
    # print(f"开始下载<{self.title}>音频")
    with open(f'{self.dir}\\{self.title}.mp3', mode='wb') as f:
        f.write(audio)
    logger.info(f"{self.title}音频下载完毕!")

def download_video(self) -> None:
    """
    下载视频
    :return:
    """
    video = self.get_video_or_audio_content('video')
    # print(f"开始下载<{self.title}>视频")
    with open(f'{self.dir}\\{self.title}.mp4', mode='wb') as f:
        f.write(video)
    logger.info(f"{self.title}视频下载完毕!")

下载的文件,根据视频和音频分为mp4和mp3

4.3.2 合并音频视频
def merge(self) -> None:
    """
    合并视频音频
    :return:
    """
    logger.info(f"开始合并{self.title}的视频音频!")
    start = time.time()
    video = VideoFileClip(f'{self.dir}\\{self.title}.mp4')
    audio = AudioFileClip(f'{self.dir}\\{self.title}.mp3')
    final_clip = video.set_audio(audio)
    final_clip.write_videofile(f'{self.dir}\\{self.title}output.mp4')
    os.remove(f'{self.dir}\\{self.title}.mp3')
    os.remove(f'{self.dir}\\{self.title}.mp4')
    logger.info(f"合并{self.title}的视频音频完毕,<耗时: {round(time.time() - start, 4)}秒>")

该方法就是通过已经下载后的音频视频,来进行合并,对文件的拓展名都在方法中写死,可以不用去管

4.3.3 完整下载过程
def download(self) -> None:
    """
    下载完整视频:分别下载视频音频,然后合并
    :return:
    """
    for k, v in self.source_list.items():
        # 获取源码
        self.source = self.get_source_code(self.basic_url.format(k))
        # 获取标题
        self.title = v + '-' + k + '-' + str(int(time.time() * 1000))
        if not self.is_video:
            self.download_audio()
            continue
        self.download_audio()
        self.download_video()
        self.merge()

主函数进行下载操作:

  • 遍历字典获取当前视频的源码,标题
  • 判断是否合并,如果初始化类的时候,使用默认参数则不合并,就只会下载音频
  • 如果初始化指定参数is_video为True,则下载音频视频并且合并
4.3.4 开始下载
if __name__ == '__main__':
    from data import chn_dict1

    source_dict = chn_dict1
    dir = r'D:\video'
    start = time.time()
    dv = DownloadVideo(source_dict, dir, is_video=True)
    dv.download()
    logger.info(f"全部下载完毕,共耗时: {round(time.time() - start, 5)}秒")

4.4 结果展示

视频音频合并

因为下载视频音频后还需要合并,所以导致时间用的比较久,可以使用多线程来节省时间。如果对视频没有要求,只需要下载音频听歌的话,那么在初始化类的时候is_video使用默认参数False即可,只下载音频速度是很快的。

PS:本文只做技术分享交流,禁止用于商用,私自使用该技术对BiliBili如造成损失,自行承担后果。如对BiliBili造成影响,请联系我删除该文章。联系邮箱442331998@qq.com。

🎉 欢迎关注我的公众号

微信公众号

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值