爬取B站视频并对音视频混流


爬虫本体

代码仅供学习交流使用,请不要大量爬取,也不可将之用于商业用途,如需转载视频请征求视频作者同意,所产生的法律纠纷与本人无关。

用到的包

#如果缺失请在cmd下使用pip install xxx进行安装
import requests
import re,os
import time

两个请求头

#用于抓取搜索结果页的视频入口
first_headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36'
    }
#用于配置视频真实地址并下载
second_headers = {
    "Accept-Encoding":"identify",
    "Accept":"*/*",
    "Connection":"keep-alive",
    "Accept-Language":"cross-site",
    "Sec-Fetch-Mode":"cors",
    "Origin":"https://www.bilibili.com",
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36'
    }

构建搜索页地址

keyword = input('请输入你要爬取的关键词:')
#检查文件夹是否存在,不存在则创建
if not os.path.exists(keyword):
    os.mkdir(keyword)
    #切换到目标路径
    os.chdir('./' + keyword)
else:
    os.chdir('./' + keyword)
URLS = ['https://search.bilibili.com/all?keyword={}&page={}'.format(keyword,i) for i in range(1,11)]

主要代码

def run():
    #搜索页的循环
    for URL in URLS:
        try:
            #获取目标页的网页数据
            res = requests.get(url = URL,headers = first_headers)
            res.encoding = res.apparent_encoding
            html = res.text
            #正则匹配视频入口和标题
            pattern_url = re.compile('href="//(www.bilibili.com.*?search)"\stitle')
            pattern_title = re.compile('search"\stitle="(.*?)"')
            url_list = re.findall(pattern_url, html)
            title_list = re.findall(pattern_title, html)
            #循环爬取多个视频
            for i in range(len(url_list)):
                #清除标题中的不规则字符
                title_list[i] = title_list[i].replace('\\', '')
                #创建用于保存视频与音频的文件夹
                if not os.path.exists(title_list[i]):
                    os.mkdir(title_list[i])
                #已经下载过的则pass
                else:
                    continue
                #配置完整的视频地址
                http_url = 'https://' + url_list[i]
                #抓取视频地址返回的数据
                video_html = requests.get(url = http_url,headers = first_headers).content.decode('utf-8')
                try:
                    #匹配视频流Url
                    m4s_30080 = re.findall(r'''"baseUrl":"(.*?)"''',video_html,re.S)[0]
                except Exception as e:
                    print(e)
                #匹配音频流Url
                mp3_30216 = re.findall(r'''"baseUrl":"(.*?)"''',video_html,re.S)[-2]
                #将second_headers请求头配置完整
                Referer_key = url_list[i]
                #暂时将Range设置为0-5
                Range_Key = 'bytes=0-5'
                second_headers['Referer'] = 'https://' + Referer_key
                second_headers['Range'] = Range_Key
                #请求视频流地址
                video_bytes = requests.get(m4s_30080,headers = second_headers).headers['Content-Range']
                #请求音频流地址
                audio_bytes = requests.get(mp3_30216,headers = second_headers).headers['Content-Range']
                #匹配完整音视频的真实Range并更新请求头
                video_total = re.findall(r"/(.*)?",video_bytes,re.S)[0]
                audio_total = re.findall(r"/(.*)?",audio_bytes,re.S)[0]
                second_headers['Range'] = video_total
                stream = True
                #用于判断视频大小并以该字节作为数据流大小
                chunk_size = 1024
                #将匹配到的Range转为int型数据
                video_content_size = int(video_total)
                audio_content_size = int(audio_total)
                print('开始下载:' + title_list[i])
                #输出音视频的大小总和
                print("文件大小:" + str(round(float((video_content_size + audio_content_size) / chunk_size / 1024), 4)) + "[MB]")
                #记录开始时间
                start = time.time()
                #请求完整视频地址
                m4s_bytes = requests.get(m4s_30080,headers = second_headers,stream = stream)
                #返回值不为416则代表有数据,一般为200
                if m4s_bytes.status_code != 416:
                    #下载视频并命名为1.mp4
                    with open(title_list[i] + '/' + '1.mp4','wb') as f:
                        for data in m4s_bytes.iter_content(chunk_size=chunk_size):
                            f.write(data)
                            f.flush()
                        f.close()
                #更新请求头用于下载音频
                second_headers['Range'] = audio_total
                #请求完整音频地址
                mp3_bytes = requests.get(mp3_30216, headers = second_headers, stream = stream)
                #下载音频并命名为1.mp3
                with open(title_list[i] + '/' + '1.mp3','wb') as t:
                    t.write(mp3_bytes.content)
                    t.close()
                #记录结束时间
                end = time.time()
                #输出耗时
                print('下载完成,总耗时:' + str(end - start) + '秒\n')
                #爬累了歇几秒...
                time.sleep(3)
                #如果只爬单个视频作为测试就把break取消注释
                #break
        #用于捕捉并忽略错误
        except Exception as e:
            print(e)
            pass
        #如果爬多页就把break注释掉
        break

if __name__ == '__main__':
    run()

音视频混流

18年以后B站将视频与音频分割开,无法直接获取完整文件 这里介绍两种方法对音频与视频进行混流 1、格式工厂,去软件商店或者百度搜索下载即可 2、使用的Python强大的音视频处理库moviepy写一个脚本

用到的包

#仍然使用pip install xxx进行安装,可能也需要安装ffmpeg模块
from moviepy.editor import VideoFileClip,AudioFileClip
import os,time

切换目录

#os模块取关键词目录下的所有文件夹
paths = os.listdir()[1]
#切换到关键词目录
os.chdir('./' + paths)

循环处理

def run():
    for path in os.listdir():
        try:
            #定义混流后的文件名
            filename = 'merge_file'
            #文件的保存路径
            filepath = path + '/' + filename
            #检测是否已处理过,如果是则忽略
            if os.path.exists(filepath + '.mp4'):
                continue
            #预处理的视频文件
            video_file = path + '/' + '1.mp4'
            #预处理的音频文件
            audio_file = path + '/' + '1.mp3'
            #加载音视频
            video = VideoFileClip(video_file)
            audio = AudioFileClip(audio_file)
            #将音视频混流
            new_video = video.set_audio(audio)
            #保存混流后的视频,并禁用缓存
            new_video.to_videofile(filepath + '.mp4',remove_temp = True)
            #释放已加载的视频
            video.close()
            new_video.close()
            #看情况是否需要休息几秒...
            time.sleep(3)
        except:
            #如果前述未关闭已加载的文件就遇到错误,则重新执行
            try:
                video.close()
                new_video.close()
            #如果遇到错误则忽略
            except:
                pass
            #输出遇到错误的视频并继续混流下一个
            print(path + '\nerror')
            pass

if __name__ == '__main__':
    run()