Python下載嗶哩嗶哩學習視頻
0 概要
涵蓋技術:嗶哩嗶哩反爬,解析接口,視頻,音頻分片下載,視頻音頻合成
一 目標地址
嗶哩嗶哩上Egon大神的python快速入門視頻,咱們要全部爬取下來
https://www.bilibili.com/video/av73342471?p=1
二 安裝ffmpeg
2.1mac下安裝
只要把項目中的該文件配置到環境變量即可
2.2 windows下安裝
將項目目錄下的該文件解壓,把bin目錄配置到環境變量即可
三 破解分析
3.1分析出該地址的接口為
https://api.bilibili.com/x/player/pagelist?aid=73342471
請求返還結果為:
3.2 分析出每個視頻地址為:
每一個視頻地址,通過p=數字來區分如第2個視頻為:https://www.bilibili.com/video/av73342471?p=2
我們向每個地址發送請求,拿回頁面,內部含有json數據
通過正則解析出想要的數據:
playinfo = my_match(res.text, '__playinfo__=(.*?)</script><script>')
數據格式是這樣的,里面有分辨率,不同分辨率的視頻地址,音頻地址
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中放着分開的視頻和音頻
四 全部代碼
#導入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)
u3m8
m3u8格式鏈接在瀏覽器上打開,沒有插件的情況下你會得到長得跟下面差不多的一個文本列表
以.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)