python爬取bilibili数据_如何使用Python爬取bilibili视频(详细教程)
Python爬取bilibili視頻
摘要
為了解決PC端的bilibili無法下載視頻的問題,使用python語言可以實現一個能夠爬取bilibili某個視頻資源(不包括會員視頻)的程序。采用整個視頻下載與分片拼接視頻兩種思路實現程序,比較兩種方式的下載效率,最終采用分片下載視頻再拼接成為一個視頻的方式實現了bilibili視頻(不包括會員視頻)的下載。實現bilibili視頻下載,可以用于離線觀看或者收集視頻素材用于剪輯,具有一定實際的用途。
引言
由于bilibili只能通過手機app下載視頻緩存,電腦打開需要將緩存從手機將文件傳到電腦,不方便。
本文從如何借助chorme瀏覽器的開發者工具與搜索引擎了解bilibili請求視頻資源的方式、如何使用python的各種庫模擬瀏覽器發送請求去獲取bilibili服務器的視頻資源、運行實現的python爬蟲程序并下載一個視頻三個步驟介紹bilibili視頻爬蟲的實現。
系統結構
使用到的工具
IntelliJ IDEA 2018.2.4 x64(集成開發環境)
Python3.8(編程語言)
requests庫(發送http請求)
lxml庫(xpath解析)
json庫(解析json數據)
ffmpeg(合并音頻和視頻)
實現功能的原理
通過輸入視頻編號再拼接成為url,通過用python的request庫使用url模擬瀏覽器請求訪問視頻頁面。使用lxml庫與json庫從返回的響應信息中提取到視頻資源的鏈接,再去模擬瀏覽器請求獲取音頻和視頻資源,再將獲得的音頻和視頻資源合并保存到本地。
實現代碼
1.了解url結構
使用chorme瀏覽器,先到bilibili首頁隨便點開一個視頻如圖3.1所示。圖3.1
進入視頻頁面后,點進第二p,開始分析該頁面的請求的結構如圖3.2所示。圖3.2
可以看到請求由url和一個參數p拼接而成。
2.編寫輸入程序
訪問頁面需要的url結構為https://www.bilibili.com/video/BV號?p=,我們下載視頻的話可以輸入視頻p數的分類來下載多個分p視頻,總結以上需要輸入程序的信息就包括視頻BV號、起始p、結束p這三個信息,編寫代碼如下。
if __name__ == '__main__':
# 輸入bilibili視頻的BV號
bv = input('視頻BV號:')
url='https://www.bilibili.com/video/'+bv
# 選擇視頻從第幾p開始到第幾p結束
startPart=input('起始P:')
endPart = input('終止P;')
print("url:",url)
print("startPart:",startPart)
print("endPart:",endPart)
輸出結果如圖3.3所示。圖3.3
3.解析網頁,找到下載視頻的鏈接
在chorme瀏覽器按下F12打開開發者工具,查找頁面元素,找到在head標簽的第3個script標簽里面存有視頻播放信息,劃紅線的baseUrl既是視頻資源鏈接如圖3.4所示。圖3.4
根據網上搜索了解到bilibili2018年后的視頻分為音頻與視頻,但是在此標簽里面沒有找到與音頻有關的鍵,這里我直接拷貝標簽文本,放到文本編輯器notepad++中,查找“audio”發現了音頻鍵值對,如圖3.5所示。圖5
其中“baseUrl”后面的內容就是音頻資源鏈接,如圖3.6所示。3.6
這里我需要先通過發送請求獲取網頁二進制文本信息,然后再通過解析文本獲取需要的鏈接。
增加函數getBiliBiliVideo,使用到的庫有json、os、requests、etree,代碼如下。
import json
import requests
from lxml import etree
# 防止因https證書問題報錯
requests.packages.urllib3.disable_warnings()
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3970.5 Safari/537.36',
'Refer'
'er': 'https://www.bilibili.com/'
}
'''
獲取bilibili視頻的主要函數
@param url 視頻頁面url 結構為:url?參數
@param p 視頻p數
@param bv 視頻bv數
'''
def getBiliBiliVideo(url,p,bv):
session = requests.session()
res = session.get(url=url,headers=headers,verify=False)
_element = etree.HTML(res.content)
# 獲取window.__playinfo__的json對象,[20:]表示截取'window.__playinfo__='后面的json字符串
videoPlayInfo = str(_element.xpath('//head/script[3]/text()')[0].encode('utf-8').decode('utf-8'))[20:]
videoJson = json.loads(videoPlayInfo)
# 獲取視頻鏈接和音頻鏈接
try:
# 2018年以后的b站視頻由.audio和.video組成 flag=0表示分為音頻與視頻
videoURL = videoJson['data']['dash']['video'][0]['baseUrl']
audioURl = videoJson['data']['dash']['audio'][0]['baseUrl']
flag=0
except Exception:
# 2018年以前的b站視頻音頻視頻結合在一起,后綴為.flv flag=1表示只有視頻
videoURL = videoJson['data']['durl'][0]['url']
flag=1
print("videoURL:",videoURL)
print("audioURl:",audioURl)
print("flag:",flag)
if __name__ == '__main__':
getBiliBiliVideo("https://www.bilibili.com/video/BV1MJ411b7F6?p=2",2,"BV1MJ411b7F6")
輸出結果圖3.7所示。
輸出結果圖3.7所示。3.7
成功獲取鏈接。
4.下載視頻與音頻
再去查看視頻資源的網絡請求,發現一共有兩種請求方式,一種是GET,如圖3.8所示。3.8
另一種是OPTION,如圖3.9所示。3.9
所以視頻資源可能需要先發送OPTION請求,獲取服務器許可后再請求資源,許可的保持時間較長,所以只發一次Option請求就可以了,如圖3.10所示。3.10
這里有兩種下載視頻的方式:
第一種是利用416報錯碼分片下載,留意在請求頭劃紅線的這兩個參數,如圖3.11所示。3.11
Referer用于寫明來源,Range用于規定分片的字節大小以及范圍,每下載一定字節的資源,就修改Range,直到最后一次字節數大于剩下的資源時,服務器返回416報錯,再重新將Range設置為’Range’: ‘bytes=上一次開始的索引-’。把最后剩下的資源下載下來。每獲得一個視頻的分片就給他拼接到文件最后,最后得到完整文件。
第二種是不加入Range參數,直接下載整個的音頻與視頻,只需要注釋掉添加請求頭Range參數的語句即可。
增加函數為fileDownload,增加導入的庫有json、os、requests、etree、os,代碼如下。
import json
import os
import requests
from lxml import etree
# 防止因https證書問題報錯
requests.packages.urllib3.disable_warnings()
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3970.5 Safari/537.36',
'Refer'
'er': 'https://www.bilibili.com/'
}
'''獲取bilibili視頻的主要函數@param url 視頻頁面url 結構為:url?參數@param p 視頻p數@param bv 視頻bv數'''
def getBiliBiliVideo(url,p,bv):
session = requests.session()
res = session.get(url=url,headers=headers,verify=False)
_element = etree.HTML(res.content)
# 獲取window.__playinfo__的json對象,[20:]表示截取'window.__playinfo__='后面的json字符串
videoPlayInfo = str(_element.xpath('//head/script[3]/text()')[0].encode('utf-8').decode('utf-8'))[20:]
videoJson = json.loads(videoPlayInfo)
# 獲取視頻鏈接和音頻鏈接
try:
# 2018年以后的b站視頻由.audio和.video組成 flag=0表示分為音頻與視頻
videoURL = videoJson['data']['dash']['video'][0]['baseUrl']
audioURl = videoJson['data']['dash']['audio'][0]['baseUrl']
flag=0
except Exception:
# 2018年以前的b站視頻音頻視頻結合在一起,后綴為.flv flag=1表示只有視頻
videoURL = videoJson['data']['durl'][0]['url']
flag=1
# 指定文件生成目錄,如果不存在則創建目錄
dirname = ("E:/result").encode("utf-8").decode("utf-8")
if not os.path.exists(dirname):
os.makedirs(dirname)
print('文件夾創建成功!')
# 獲取每一集的名稱
name = bv + "-" + str(p)
# 下載視頻和音頻
print('正在下載 "'+name+'" 的視頻····')
fileDownload(homeurl=url,url=videoURL, name='E:/result/'+name + '_Video.mp4', session=session)
if flag == 0:
print('正在下載 "'+name+'" 的音頻····')
fileDownload(homeurl=url,url=audioURl, name='E:/result/'+name+ '_Audio.mp3', session=session)
print(' "'+name+'" 下載完成!')
'''使用session保持會話下載文件@param homeurl 訪問來源@param url 音頻或視頻資源的鏈接@param name 下載后生成的文件名@session 用于保持會話'''
def fileDownload(homeurl,url, name, session=requests.session()):
# 添加請求頭鍵值對,寫上 refered:請求來源
headers.update({'Referer': homeurl})
# 發送option請求服務器分配資源
session.options(url=url, headers=headers,verify=False)
# 指定每次下載1M的數據
begin = 0
end = 1024*512 - 1
flag = 0
while True:
# 添加請求頭鍵值對,寫上 range:請求字節范圍
headers.update({'Range': 'bytes=' + str(begin) + '-' + str(end)})
# 獲取視頻分片
res = session.get(url=url, headers=headers,verify=False)
if res.status_code != 416:
# 響應碼不為為416時有數據
begin = end + 1
end = end + 1024*512
else:
headers.update({'Range': str(end + 1) + '-'})
res = session.get(url=url, headers=headers,verify=False)
flag=1
with open(name.encode("utf-8").decode("utf-8"), 'ab') as fp:
fp.write(res.content)
fp.flush()
# data=data+res.content
if flag==1:
fp.close()
break
if __name__ == '__main__':
getBiliBiliVideo("https://www.bilibili.com/video/BV1MJ411b7F6?p=2",2,"BV1MJ411b7F6")
輸出結果如圖3.12所示。3.12
通過第一種分片下載視頻得到音頻與視頻如圖3.13所示。3.13
而通過第二種直接下載視頻得到的視頻出現異常, 如圖3.14所示,中途還是停止了下載,不考慮這個方法。3.14
5.合并視頻與音頻
對于bilibili2018年以后的視頻需要再多一步音頻與視頻合并的操作,這里通過ffmpeg來實現這個功能。3.15
下載zip包到自定義文件夾下解壓,找到bin目錄,如圖3.16所示。3.16
將bin的完整目錄添加到系統環境變量Path中,如圖3.17所示。3.17
Win+R運行cmd指令輸入ffmpeg -version查看到如圖3.18所示則配置成功。3.18
編寫函數命名為combineVideoAudio,導入庫subprocess、os,使用subprocess.call()執行ffmpeg命令,然后使用os.remove()將原本的音頻和視頻刪除,代碼如下。
import subprocess
import os
'''
用于合并音頻與視頻
@param videopath 視頻路徑
@param audiopath 音頻路徑
@param outpath 生成合并視頻的路徑
'''
def combineVideoAudio(videopath,audiopath,outpath):
subprocess.call(("D:/python/ffmpeg-3.4.2-win64-static/bin/ffmpeg -i " + videopath + " -i " + audiopath + " -c copy "+ outpath).encode("utf-8").decode("utf-8"),shell=True)
os.remove(videopath)
os.remove(audiopath)
if __name__ == '__main__':
combineVideoAudio("E:/result/BV1MJ411b7F6-2_Video.mp4","E:/result/BV1MJ411b7F6-2_Audio.mp3","E:/result/BV1MJ411b7F6-2_output.mp4")
合并第4步下載的音頻和視頻,截取執行結果如圖3.19與3.20所示。3.193.20
能夠正常觀看視頻,如圖3.21所示。3.21
6.最終代碼
import json
import os
import subprocess
import requests
from lxml import etree
# 防止因https證書問題報錯
requests.packages.urllib3.disable_warnings()
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3970.5 Safari/537.36',
'Refer'
'er': 'https://www.bilibili.com/'
}
'''
獲取bilibili視頻的主要函數
@param url 視頻頁面url 結構為:url?參數
@param p 視頻p數
@param bv 視頻bv數
'''
def getBiliBiliVideo(url,p,bv):
session = requests.session()
res = session.get(url=url,headers=headers,verify=False)
_element = etree.HTML(res.content)
# 獲取window.__playinfo__的json對象,[20:]表示截取'window.__playinfo__='后面的json字符串
videoPlayInfo = str(_element.xpath('//head/script[3]/text()')[0].encode('utf-8').decode('utf-8'))[20:]
videoJson = json.loads(videoPlayInfo)
# 獲取視頻鏈接和音頻鏈接
try:
# 2018年以后的b站視頻由.audio和.video組成
videoURL = videoJson['data']['dash']['video'][0]['baseUrl']
audioURl = videoJson['data']['dash']['audio'][0]['baseUrl']
flag=0
except Exception:
# 2018年以前的b站視頻音頻視頻結合在一起,后綴為.flv
videoURL = videoJson['data']['durl'][0]['url']
flag=1
# 指定文件生成目錄,如果不存在則創建目錄
dirname = ("E:/result").encode("utf-8").decode("utf-8")
if not os.path.exists(dirname):
os.makedirs(dirname)
print('文件夾創建成功!')
# 獲取每一集的名稱
name = bv + "-" + str(p)
# 下載視頻和音頻
print('正在下載 "'+name+'" 的視頻····')
fileDownload(homeurl=url,url=videoURL, name='E:/result/'+name + '_Video.mp4', session=session)
if flag == 0:
print('正在下載 "'+name+'" 的音頻····')
fileDownload(homeurl=url,url=audioURl, name='E:/result/'+name+ '_Audio.mp3', session=session)
print('正在組合 "'+name+'" 的視頻和音頻····')
combineVideoAudio('E:/result/' + name + '_Video.mp4','E:/result/' + name + '_Audio.mp3','E:/result/' + name + '_output.mp4')
print(' "'+name+'" 下載完成!')
'''
使用session保持會話下載文件
@param homeurl 訪問來源
@param url 音頻或視頻資源的鏈接
@param name 下載后生成的文件名
@session 用于保持會話
'''
def fileDownload(homeurl,url, name, session=requests.session()):
# 添加請求頭鍵值對,寫上 refered:請求來源
headers.update({'Referer': homeurl})
# 發送option請求服務器分配資源
session.options(url=url, headers=headers,verify=False)
# 指定每次下載1M的數據
begin = 0
end = 1024*512 - 1
flag = 0
while True:
# 添加請求頭鍵值對,寫上 range:請求字節范圍
headers.update({'Range': 'bytes=' + str(begin) + '-' + str(end)})
# 獲取視頻分片
res = session.get(url=url, headers=headers,verify=False)
if res.status_code != 416:
# 響應碼不為為416時有數據
begin = end + 1
end = end + 1024*512
else:
headers.update({'Range': str(end + 1) + '-'})
res = session.get(url=url, headers=headers,verify=False)
flag=1
with open(name.encode("utf-8").decode("utf-8"), 'ab') as fp:
fp.write(res.content)
fp.flush()
# data=data+res.content
if flag==1:
fp.close()
break
'''
用于合并音頻與視頻
@param videopath 視頻路徑
@param audiopath 音頻路徑
@param outpath 生成合并視頻的路徑
'''
def combineVideoAudio(videopath,audiopath,outpath):
subprocess.call(("D:/python/ffmpeg-3.4.2-win64-static/bin/ffmpeg -i " + videopath + " -i " + audiopath + " -c copy "+ outpath).encode("utf-8").decode("utf-8"),shell=True)
os.remove(videopath)
os.remove(audiopath)
if __name__ == '__main__':
# 輸入bilibili視頻的BV號
bv = input('視頻BV號:')
url='https://www.bilibili.com/video/'+bv
# 選擇視頻從第幾p開始到第幾p結束
startPart=input('起始P:')
endPart = input('終止P;')
for p in range(int(startPart),int(endPart) + 1):
getBiliBiliVideo(url + '?p=' + str(p),p,bv)
總結
目前實現的爬取視頻功能已經可以滿足我離線觀看視頻,不用借助手機就可下載到視頻素材的需求。當然,這個程序還存在一些可以后續添加的功能,比如輸入多個BV號再逐個下載;實現循環輸入,下載完一個視頻后繼續輸入號,繼續下載;顯示彈幕等,這些功能根據以后的需求再進行添加。
如果你喜歡這期的Python 教程,請持續關注我,如果對你有幫助,麻煩點個關注加收藏,
領取學習資料和進交流群可以關注微信公共號:
總結
以上是生活随笔為你收集整理的python爬取bilibili数据_如何使用Python爬取bilibili视频(详细教程)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手机共享热点USB
- 下一篇: M OP N数值运算问题