03-06-09-Python爬取嗶哩嗶哩學習視頻


Python下載嗶哩嗶哩學習視頻

0 概要

涵蓋技術:嗶哩嗶哩反爬,解析接口,視頻,音頻分片下載,視頻音頻合成

一 目標地址

嗶哩嗶哩上Egon大神的python快速入門視頻,咱們要全部爬取下來

https://www.bilibili.com/video/av73342471?p=1

image-20191209173423502

二 安裝ffmpeg

2.1mac下安裝

只要把項目中的該文件配置到環境變量即可

image-20191209173650193

2.2 windows下安裝

將項目目錄下的該文件解壓,把bin目錄配置到環境變量即可

image-20191209173731196

三 破解分析

3.1分析出該地址的接口為

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

請求返還結果為:

image-20191210012438389

3.2 分析出每個視頻地址為:

每一個視頻地址,通過p=數字來區分如第2個視頻為:https://www.bilibili.com/video/av73342471?p=2

我們向每個地址發送請求,拿回頁面,內部含有json數據

通過正則解析出想要的數據:

playinfo = my_match(res.text, '__playinfo__=(.*?)</script><script>')

數據格式是這樣的,里面有分辨率,不同分辨率的視頻地址,音頻地址

image-20191210012649423

3.2分片下載視頻和音頻

def download_video(old_video_url,video_url,audio_url,video_name):
    headers.update({"Referer":old_video_url})
    print("開始下載視頻:%s"%video_name)
    video_content = requests.get(video_url, headers=headers)
    print('%s視頻大小:'%video_name, video_content.headers['content-length'])
    audio_content = requests.get(audio_url, headers=headers)
    print('%s音頻大小:'%video_name, audio_content.headers['content-length'])
    # 下載視頻開始
    received_video = 0
    with open('%s_video.mp4' % video_name, 'ab') as output:
        while int(video_content.headers['content-length']) > received_video:
            headers['Range'] = 'bytes=' + str(received_video) + '-'
            response = requests.get(video_url, headers=headers)
            output.write(response.content)
            received_video += len(response.content)
    # 下載視頻結束
    # 下載音頻開始
    audio_content = requests.get(audio_url, headers=headers)
    received_audio = 0
    with open('%s_audio.mp4' % video_name, 'ab') as output:
        while int(audio_content.headers['content-length']) > received_audio:
            # 視頻分片下載
            headers['Range'] = 'bytes=' + str(received_audio) + '-'
            response = requests.get(audio_url, headers=headers)
            output.write(response.content)
            received_audio += len(response.content)
    # 下載音頻結束
    return video_name

3.3 把視頻和音頻通過ffmpeg合並到一起即可

def make_all(result):
    # 視頻音頻合並
    video_name=result.result()
    import subprocess
    video_final = video_name.replace('video', 'video_final')
    ss = 'ffmpeg -i %s_video.mp4 -i %s_audio.mp4 -c copy %s.mp4' % (video_name, video_name, video_final)
    subprocess.Popen(ss, shell=True)
    #刪除視頻和音頻
    # os.remove('%s_video.mp4'%video_name)
    # os.remove('%s_audio.mp4'%video_name)
    print("視頻下載結束:%s" % video_name)

3.4 大功告成

video_final文件夾中放着下好的視頻,video中放着分開的視頻和音頻

image-20191210013246446

四 全部代碼

#導入requests模塊,模擬發送請求
import requests
#導入線程池模塊,創建30個線程同時做
from concurrent.futures import ThreadPoolExecutor
#導入ssl模塊,處理https請求失敗問題
import ssl
#導入json
import json
#導入re
import re
import os
#定義請求頭
headers="{'Accept': '*/*', 'Accept-Language': 'en-US,en;q=0.5', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36'}"
headers={
    'Accept':'*/*',
    'Accept-Language':'en-US,en;q=0.5',
    'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36'
}
#定義一個池,大小為30
pool = ThreadPoolExecutor(30)
# 生成證書上下文(unverified 就是不驗證https證書)
ssl._create_default_https_context = ssl._create_unverified_context
res_json=requests.get('https://api.bilibili.com/x/player/pagelist?aid=73342471').json()


#正則表達式,根據條件匹配出值
def my_match(text,pattern):
    match = re.search(pattern, text)
    # print(match.group(1))
    # print()
    return json.loads(match.group(1))
#下載並合並視頻的函數
def download_video(old_video_url,video_url,audio_url,video_name):
    headers.update({"Referer":old_video_url})
    print("開始下載視頻:%s"%video_name)
    video_content = requests.get(video_url, headers=headers)
    print('%s視頻大小:'%video_name, video_content.headers['content-length'])
    audio_content = requests.get(audio_url, headers=headers)
    print('%s音頻大小:'%video_name, audio_content.headers['content-length'])
    # 下載視頻開始
    received_video = 0
    with open('%s_video.mp4' % video_name, 'ab') as output:
        while int(video_content.headers['content-length']) > received_video:
            headers['Range'] = 'bytes=' + str(received_video) + '-'
            response = requests.get(video_url, headers=headers)
            output.write(response.content)
            received_video += len(response.content)
    # 下載視頻結束
    # 下載音頻開始
    audio_content = requests.get(audio_url, headers=headers)
    received_audio = 0
    with open('%s_audio.mp4' % video_name, 'ab') as output:
        while int(audio_content.headers['content-length']) > received_audio:
            # 視頻分片下載
            headers['Range'] = 'bytes=' + str(received_audio) + '-'
            response = requests.get(audio_url, headers=headers)
            output.write(response.content)
            received_audio += len(response.content)
    # 下載音頻結束
    return video_name


def make_all(result):
    # 視頻音頻合並
    video_name=result.result()
    import subprocess
    video_final = video_name.replace('video', 'video_final')
    ss = 'ffmpeg -i %s_video.mp4 -i %s_audio.mp4 -c copy %s.mp4' % (video_name, video_name, video_final)
    subprocess.Popen(ss, shell=True)
    #刪除視頻和音頻
    # os.remove('%s_video.mp4'%video_name)
    # os.remove('%s_audio.mp4'%video_name)
    print("視頻下載結束:%s" % video_name)

def main_download():
    for i, video_content in enumerate(res_json['data']):
        video_name = ('./video/' + video_content['part']).replace(" ", "-")
        old_video_url = 'https://www.bilibili.com/video/av73342471' + '?p=%d' % (i + 1)
        # print('視頻地址為:',old_video_url)
        # print('視頻名字為:',video_name)
        # 加載上面拼湊的視頻地址
        res = requests.get(old_video_url, headers=headers)
        # 解析出當前頁面下所有視頻的json
        # initial_state = my_match(res.text, r'__INITIAL_STATE__=(.*?);\(function\(\)')
        # 解析出視頻詳情的json
        playinfo = my_match(res.text, '__playinfo__=(.*?)</script><script>')
        # 列表套字典,拼湊出視頻和音頻的字典(這里只下載1080p的視頻)
        video_info_list = []
        for i in range(4):
            video_info = {}
            video_info['quality'] = playinfo['data']['accept_description'][i]
            video_info['acc_quality'] = playinfo['data']['accept_quality'][i]
            video_info['video_url'] = playinfo['data']['dash']['video'][i]['baseUrl']
            video_info['audio_url'] = playinfo['data']['dash']['audio'][0]['baseUrl']
            video_info_list.append(video_info)
        # 1080p視頻的地址為,分片加載的
        video_url = video_info_list[0]['video_url']
        # 1080p視頻的音頻為,分片加載的
        audio_url = video_info_list[0]['audio_url']
        # download_cov(old_video_url,video_url,audio_url,video_name)
        pool.submit(download_video, old_video_url, video_url, audio_url, video_name).add_done_callback(make_all)
    pool.shutdown(wait=True)


if __name__ == '__main__':
    main_download()

五 寫在最后

文章涉及you-get和ffmpeg的使用,這里簡單一說,后續有時間詳細跟大家聊吧

ffmpeg

FFmpeg是一套可以用來記錄、轉換數字音頻、視頻,並能將其轉化為流的開源計算機程序

可以視頻格式轉換,提取音頻,合並視頻,視頻剪切,給視頻加logo。。。無所不能

由於各大視頻網站視頻都是以視頻流的形式,分片傳輸的,如嗶哩嗶哩,所以需要分片下載后合並視頻,嗶哩嗶哩用的m4a格式,優酷用的u3m8格式(有興趣的可以自行學習u3m8視頻格式,如何合並成mp4)

image-20191209174508728

u3m8

m3u8格式鏈接在瀏覽器上打開,沒有插件的情況下你會得到長得跟下面差不多的一個文本列表

image-20191209174757565

以.ts 結尾的那些就是視頻連接的實際播放地址,當然你還要拼上前面的前綴。

在瀏覽器上安裝過插件的情況,你可以直接在線預覽影片,但是如果你想下載到本地卻很麻煩,在瀏覽器上傳好看網絡請求你會發現一部60分鍾的影片可能被切成了幾百上千個片段,每個片段不到10秒,難道我們要下一千個片段,然后用ffmpeg拼起來

you-get

一個開源的下載分片視頻,通過ffmpeg合並的項目(python寫的),有興趣的可以研究一下源碼,地址為:https://github.com/soimort/you-get/

所以,我們可以直接使用you-get下載,代碼如下(非常簡單)

import requests
from you_get.common import any_download
from concurrent.futures import ThreadPoolExecutor

import ssl
pool = ThreadPoolExecutor(10)
# 生成證書上下文(unverified 就是不驗證https證書)
ssl._create_default_https_context = ssl._create_unverified_context
res_json=requests.get('https://api.bilibili.com/x/player/pagelist?aid=73342471').json()

def download(url):
    any_download(url, output_dir='video', merge='merge')

for i,video_content in enumerate(res_json['data']):
    video_name=video_content['part']
    video_url='https://www.bilibili.com/video/av73342471'+'?p=%d'%(i+1)
    print('視頻地址為:',video_url)
    print('視頻名字為:',video_name)
    pool.submit(download, video_url)

pool.shutdown(wait=True)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM