Python 批量下载BiliBili视频 打包成软件
文章目錄
- 一、項目概述
- 1.項目背景
- 2.環境配置
- 下載ffmpeg
- 設置環境變量
- 二、項目實施
- 1.導入需要的庫
- 2.設置請求參數
- 3.基本處理
- 4.下載視頻
- 5.視頻和音頻合并成完整的視頻
- 6.3種下載方式的分別實現
- 7.主函數
- 三、項目分析和說明
- 1.結果測試
- 改進說明
- 2.軟件打包
- 3.改進分析
- 4.合法性說明
如有需要購買用于個人或公司使用的小伙伴可選擇百度云或者華為云服務器,點擊華為云服務器優惠鏈接或掃描下方二維碼即可享受采購季優惠價:
一、項目概述
1.項目背景
有一天,我突然想找點事做,想起一直想學但是沒有學的C語言,就決定來學一下。
可是怎么學呢?看書的話太無聊,報班學呢又快吃土了沒錢,不如去B站看看?
果然,關鍵字C語言搜索,出現了很多C語言的講課視頻:
B站https://www.bilibili.com/是一個很神奇的地方,簡直就是一個無所不有的寶庫,幾乎可以滿足你一切的需求和視覺欲。不管你是想看動畫、番劇 ,還是游戲、鬼畜 ,亦或科技和各類教學視頻 ,只要你能想到的,基本上都可以在B站找到。對于程序猿或即將成為程序猿的人來說,B站上的編程學習資源是學不完的,可是B站沒有提供下載的功能,如果想保存下載在需要的時候看,那就是一個麻煩了。我也遇到了這個問題,于是研究怎么可以實現一鍵下載視頻,最終用Python這門神奇的語言實現了。
2.環境配置
這次項目不需要太多的環境配置,最主要的是有ffmpeg(一套可以用來記錄、轉換數字音頻、視頻,并能將其轉化為流的開源計算機程序)并設置環境變量就可以了。ffmpeg主要是用于將下載下來的視頻和音頻進行合并形成完整的視頻。
下載ffmpeg
可點擊https://download.csdn.net/download/CUFEECR/12234789或進入官網http://ffmpeg.org/download.html進行下載,并解壓到你想保存的目錄。
設置環境變量
- 復制ffmpeg的bin路徑,如xxx\ffmpeg-20190921-ba24b24-win64-shared\bin
- 此電腦右鍵點擊屬性,進入控制面板\系統和安全\系統
- 點擊高級系統設置→進入系統屬性彈窗→點擊環境變量→進入環境變量彈窗→選擇系統變量下的Path→點擊編輯點擊→進入編輯環境變量彈窗
- 點擊新建→粘貼之前復制的bin路徑
- 點擊確定,逐步保存退出
動態操作示例如下:
除了ffmpeg,還需要安裝pyinstaller庫用于程序打包。
可用以下命令進行安裝:
如果遇到安裝失敗或下載速度較慢,可換源:
pip install pyinstaller -i https://pypi.doubanio.com/simple/二、項目實施
1.導入需要的庫
import json import os import re import shutil import ssl import time import requests from concurrent.futures import ThreadPoolExecutor from lxml import etree導入的庫包括用于爬取和解析網頁的庫,還包括創建線程池的庫和進行其他處理的庫,大多數都是Python自帶的,如有未安裝的庫,可使用pip install xxx命令進行安裝。
2.設置請求參數
# 設置請求頭等參數,防止被反爬 headers = {'Accept': '*/*','Accept-Language': 'en-US,en;q=0.5','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36' } params = {'from': 'search','seid': '9698329271136034665' }設置請求頭等參數,減少被反爬的可能。
3.基本處理
def re_video_info(text, pattern):'''利用正則表達式匹配出視頻信息并轉化成json'''match = re.search(pattern, text)return json.loads(match.group(1))def create_folder(aid):'''創建文件夾'''if not os.path.exists(aid):os.mkdir(aid)def remove_move_file(aid):'''刪除和移動文件'''file_list = os.listdir('./')for file in file_list:# 移除臨時文件if file.endswith('_video.mp4'):os.remove(file)passelif file.endswith('_audio.mp4'):os.remove(file)pass# 保存最終的視頻文件elif file.endswith('.mp4'):if os.path.exists(aid + '/' + file):os.remove(aid + '/' + file)shutil.move(file, aid)主要包括兩方面的基本處理,為正式爬取下載做準備:
- 利用正則表達式提取信息
通過requests庫請求得到請求后的網頁,屬于文本,通過正則表達式提取得到關于將要下載的視頻的有用信息,便于后一步處理。 - 文件處理
將下載視頻完成后的相關文件進行處理,包括刪除生成的臨時的音視頻分離的文件和移動最終視頻文件到指定文件夾。
4.下載視頻
def download_video_batch(referer_url, video_url, audio_url, video_name, index):'''批量下載系列視頻'''# 更新請求頭headers.update({"Referer": referer_url})# 獲取文件名short_name = video_name.split('/')[2]print("%d.\t視頻下載開始:%s" % (index, short_name))# 下載并保存視頻video_content = requests.get(video_url, headers=headers)print('%d.\t%s\t視頻大小:' % (index, short_name),round(int(video_content.headers.get('content-length', 0)) / 1024 / 1024, 2), '\tMB')received_video = 0with open('%s_video.mp4' % video_name, 'ab') as output:headers['Range'] = 'bytes=' + str(received_video) + '-'response = requests.get(video_url, headers=headers)output.write(response.content)# 下載并保存音頻audio_content = requests.get(audio_url, headers=headers)print('%d.\t%s\t音頻大小:' % (index, short_name),round(int(audio_content.headers.get('content-length', 0)) / 1024 / 1024, 2), '\tMB')received_audio = 0with open('%s_audio.mp4' % video_name, 'ab') as output: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, indexdef download_video_single(referer_url, video_url, audio_url, video_name):'''單個視頻下載'''# 更新請求頭headers.update({"Referer": referer_url})print("視頻下載開始:%s" % video_name)# 下載并保存視頻video_content = requests.get(video_url, headers=headers)print('%s\t視頻大小:' % video_name, round(int(video_content.headers.get('content-length', 0)) / 1024 / 1024, 2), '\tMB')received_video = 0with open('%s_video.mp4' % video_name, 'ab') as output:headers['Range'] = 'bytes=' + str(received_video) + '-'response = requests.get(video_url, headers=headers)output.write(response.content)# 下載并保存音頻audio_content = requests.get(audio_url, headers=headers)print('%s\t音頻大小:' % video_name, round(int(audio_content.headers.get('content-length', 0)) / 1024 / 1024, 2), '\tMB')received_audio = 0with open('%s_audio.mp4' % video_name, 'ab') as output:headers['Range'] = 'bytes=' + str(received_audio) + '-'response = requests.get(audio_url, headers=headers)output.write(response.content)received_audio += len(response.content)print("視頻下載結束:%s" % video_name)video_audio_merge_single(video_name)這部分包括系列視頻的批量下載和單個視頻的下載,兩者的大體實現原理近似,但是由于兩個函數的參數有差別,因此分別實現。
在具體的實現中,首先更新請求頭,請求視頻鏈接并保存視頻(無聲音),再請求音頻鏈接并保存音頻,在這個過程中得到相應的視頻和音頻文件的大小。
5.視頻和音頻合并成完整的視頻
def video_audio_merge_batch(result):'''使用ffmpeg批量視頻音頻合并'''video_name = result.result()[0]index = result.result()[1]import subprocessvideo_final = video_name.replace('video', 'video_final')command = 'ffmpeg -i "%s_video.mp4" -i "%s_audio.mp4" -c copy "%s.mp4" -y -loglevel quiet' % (video_name, video_name, video_final)subprocess.Popen(command, shell=True)print("%d.\t視頻下載結束:%s" % (index, video_name.split('/')[2]))def video_audio_merge_single(video_name):'''使用ffmpeg單個視頻音頻合并'''print("視頻合成開始:%s" % video_name)import subprocesscommand = 'ffmpeg -i "%s_video.mp4" -i "%s_audio.mp4" -c copy "%s.mp4" -y -loglevel quiet' % (video_name, video_name, video_name)subprocess.Popen(command, shell=True)print("視頻合成結束:%s" % video_name)這個過程也是批量和單個分開,大致原理差不多,都是調用subprogress模塊生成子進程,Popen類來執行shell命令,由于已經將ffmpeg加入環境變量,所以shell命令可以直接調用ffmpeg來合并音視頻。
6.3種下載方式的分別實現
def batch_download():'''使用多線程批量下載視頻'''# 提示輸入需要下載的系列視頻對應的idaid = input('請輸入要下載的視頻id(舉例:鏈接https://www.bilibili.com/video/av91748877?p=1中id為91748877),默認為91748877\t')if aid:passelse:aid = '91748877'# 提示選擇清晰度quality = input('請選擇清晰度(1代表高清,2代表清晰,3代表流暢),默認高清\t')if quality == '2':passelif quality == '3':passelse:quality = '1'acc_quality = int(quality) - 1# ssl模塊,處理https請求失敗問題,生成證書上下文ssl._create_default_https_context = ssl._create_unverified_context# 獲取視頻主題url = 'https://www.bilibili.com/video/av{}?p=1'.format(aid)html = etree.HTML(requests.get(url, params=params, headers=headers).text)title = html.xpath('//*[@id="viewbox_report"]/h1/span/text()')[0]print('您即將下載的視頻系列是:', title)# 創建臨時文件夾create_folder('video')create_folder('video_final')# 定義一個線程池,大小為3pool = ThreadPoolExecutor(3)# 通過api獲取視頻信息res_json = requests.get('https://api.bilibili.com/x/player/pagelist?aid={}'.format(aid)).json()video_name_list = res_json['data']print('共下載視頻{}個'.format(len(video_name_list)))for i, video_content in enumerate(video_name_list):video_name = ('./video/' + video_content['part']).replace(" ", "-")origin_video_url = 'https://www.bilibili.com/video/av{}'.format(aid) + '?p=%d' % (i + 1)# 請求視頻,獲取信息res = requests.get(origin_video_url, headers=headers)# 解析出視頻詳情的jsonvideo_info_temp = re_video_info(res.text, '__playinfo__=(.*?)</script><script>')video_info = {}# 獲取視頻品質quality = video_info_temp['data']['accept_description'][acc_quality]# 獲取視頻時長video_info['duration'] = video_info_temp['data']['dash']['duration']# 獲取視頻鏈接video_url = video_info_temp['data']['dash']['video'][acc_quality]['baseUrl']# 獲取音頻鏈接audio_url = video_info_temp['data']['dash']['audio'][acc_quality]['baseUrl']# 計算視頻時長video_time = int(video_info.get('duration', 0))video_minute = video_time // 60video_second = video_time % 60print('{}.\t當前視頻清晰度為{},時長{}分{}秒'.format(i + 1, quality, video_minute, video_second))# 將任務加入線程池,并在任務完成后回調完成視頻音頻合并pool.submit(download_video_batch, origin_video_url, video_url, audio_url, video_name, i + 1).add_done_callback(video_audio_merge_batch)pool.shutdown(wait=True)time.sleep(5)# 整理視頻信息if os.path.exists(title):shutil.rmtree(title)os.rename('video_final', title)try:shutil.rmtree('video')except:shutil.rmtree('video')def multiple_download():'''批量下載多個獨立視頻'''# 提示輸入所有aidaid_str = input('請輸入要下載的所有視頻id,id之間用空格分開\n舉例:有5個鏈接https://www.bilibili.com/video/av89592082、https://www.bilibili.com/video/av68716174、https://www.bilibili.com/video/av87216317、\nhttps://www.bilibili.com/video/av83200644和https://www.bilibili.com/video/av88252843,則輸入89592082 68716174 87216317 83200644 88252843\n默認為89592082 68716174 87216317 83200644 88252843\t')if aid_str:passelse:aid_str = '89592082 68716174 87216317 83200644 88252843'if os.path.exists(aid_str):shutil.rmtree(aid_str)aids = aid_str.split(' ')# 提示選擇視頻質量quality = input('請選擇清晰度(1代表高清,2代表清晰,3代表流暢),默認高清\t')if quality == '2':passelif quality == '3':passelse:quality = '1'acc_quality = int(quality) - 1# 創建文件夾create_folder(aid_str)# 創建線程池,執行多任務pool = ThreadPoolExecutor(3)for aid in aids:# 將任務加入線程池pool.submit(single_download, aid, acc_quality)pool.shutdown(wait=True)time.sleep(5)# 刪除臨時文件,移動文件remove_move_file(aid_str)def single_download(aid, acc_quality):'''單個視頻實現下載'''# 請求視頻鏈接,獲取信息origin_video_url = 'https://www.bilibili.com/video/av' + aidres = requests.get(origin_video_url, headers=headers)html = etree.HTML(res.text)title = html.xpath('//*[@id="viewbox_report"]/h1/span/text()')[0]print('您當前正在下載:', title)video_info_temp = re_video_info(res.text, '__playinfo__=(.*?)</script><script>')video_info = {}# 獲取視頻質量quality = video_info_temp['data']['accept_description'][acc_quality]# 獲取視頻時長video_info['duration'] = video_info_temp['data']['dash']['duration']# 獲取視頻鏈接video_url = video_info_temp['data']['dash']['video'][acc_quality]['baseUrl']# 獲取音頻鏈接audio_url = video_info_temp['data']['dash']['audio'][acc_quality]['baseUrl']# 計算視頻時長video_time = int(video_info.get('duration', 0))video_minute = video_time // 60video_second = video_time % 60print('當前視頻清晰度為{},時長{}分{}秒'.format(quality, video_minute, video_second))# 調用函數下載保存視頻download_video_single(origin_video_url, video_url, audio_url, title)def single_input():'''單個文件下載,獲取參數'''# 獲取視頻aidaid = input('請輸入要下載的視頻id(舉例:鏈接https://www.bilibili.com/video/av89592082中id為89592082),默認為89592082\t')if aid:passelse:aid = '89592082'# 提示選擇視頻質量quality = input('請選擇清晰度(1代表高清,2代表清晰,3代表流暢),默認高清\t')if quality == '2':passelif quality == '3':passelse:quality = '1'acc_quality = int(quality) - 1# 調用函數進行下載single_download(aid, acc_quality)在一般情形下,下載的需求包含3種情況:
- 單個視頻的下載
只有一個視頻,沒有和它屬于同一個系列的其他視頻,如下圖
此時,除了右下方的相關推薦中的視頻,沒有其他視頻,右上方只有彈幕列表、沒有視頻列表。為了代碼的復用,將單個視頻下載時提示用戶輸入需求的代碼單獨提取出來作為single_input(),下載的函數另外作為single_download(aid, acc_quality)函數實現,在該函數中:
通過視頻鏈接如https://www.bilibili.com/video/av89592082解析網頁,得到相應的字符串并轉化成json,如下:
字符串json格式化可使用https://www.sojson.com/editor.html進行在線轉化。
獲取到視頻的標題、根據輸入確定的視頻質量、持續時長、視頻鏈接和音頻鏈接,并調用download_video_single()函數下載該視頻。 - 多個視頻的下載
這里,多個視頻之間是沒有關系的,多個視頻的下載實際上是先獲取到所有的aid,并進行循環,對每個視頻鏈接傳入參數調用單個視頻下載的函數即可。同時設立線程池,大小為3,既不會對資源有太大的要求,也能實現多任務、提高下載效率。 - 系列視頻的下載
此時,多個視頻屬于同一系列,如https://www.bilibili.com/video/av91748877是一個課程系列,如下
顯然,此時右上方有視頻列表,標明了有65個子視頻,每個視頻用p標識,如第2個視頻就是https://www.bilibili.com/video/av91748877?p=2。對于所有視頻,先獲取到視頻的相關信息,再加入進程池進行下載,并在任務結束之后回調函數video_audio_merge_batch()合并音視頻,并進行文件整理。
7.主函數
def main():'''主函數,提示用戶進行三種下載模式的選擇'''download_choice = input('請輸入您需要下載的類型:\n1代表下載單個視頻,2代表批量下載系列視頻,3代表批量下載多個不同視頻,默認下載單個視頻\t')# 批量下載系列視頻if download_choice == '2':batch_download()# 批量下載多個單個視頻elif download_choice == '3':multiple_download()# 下載單個視頻else:single_input()if __name__ == '__main__':'''調用主函數'''main()主函數中實現3種下載方式對應的函數的分別調用。
三、項目分析和說明
1.結果測試
對3種方式進行測試的效果如下:
3種下載情景的測試效果均較好,下載速度也能與一般的下載速度相媲美。
代碼可點擊https://download.csdn.net/download/CUFEECR/12243122或https://github.com/corleytd/Python_Crawling/blob/master/bilibili_downloader_1.py進行下載。
改進說明
B站網站也一直在變化,所以對于下載可能也會有一些變化,所以將改進的地方在下面列舉出來:
- 網址參數變化
舉例說明:
這段時間發現B站一個視頻系列的鏈接變成https://www.bilibili.com/video/BV1x7411M74h?p=65,即是無規律的字符串(可能是經過某種算法編碼或加密得到的),現在從鏈接中不能得到視頻(系列)的aid,這時候可以借助瀏覽器工具抓包查看數據來找到該視頻的aid,如下:
在左側尋找stat開頭的請求,后邊的參數即為aid,該請求api的完整鏈接為https://api.bilibili.com/x/web-interface/archive/stat?aid=91748877,所以可以直接在該鏈接中獲取aid,也可以查看該請求的具體內容,可以看到第一個數據就是aid,我們也可以看到隨機字符串就是bvid,可能是建立了aid和bvid的一一映射,找到aid就可以正常下載了。
2.軟件打包
在命令行中,使路徑位于代碼所在路徑運行
pyinstaller bilibili_downloader_1.py打印
136 INFO: PyInstaller: 3.6 137 INFO: Python: 3.7.4 138 INFO: Platform: Windows-10-10.0.18362-SP0 140 INFO: wrote xxxx\Bili_Video_Batch_Download\bilibili_downloader_1.spec 205 INFO: UPX is not available. 209 INFO: Extending PYTHONPATH with paths ['xxxx\\Bili_Video_Batch_Download','xxxx\\Bili_Video_Batch_Download'] 210 INFO: checking Analysis 211 INFO: Building Analysis because Analysis-00.toc is non existent 211 INFO: Initializing module dependency graph... 218 INFO: Caching module graph hooks... 247 INFO: Analyzing base_library.zip ... 5499 INFO: Caching module dependency graph... 5673 INFO: running Analysis Analysis-00.toc 5702 INFO: Adding Microsoft.Windows.Common-Controls to dependent assemblies of final executablerequired by xxx\python\python37\python.exe 6231 INFO: Analyzing xxxx\Bili_Video_Batch_Download\bilibili_downloader_1.py 7237 INFO: Processing pre-safe import module hook urllib3.packages.six.moves 10126 INFO: Processing pre-safe import module hook six.moves 14287 INFO: Processing module hooks... 14288 INFO: Loading module hook "hook-certifi.py"... 14296 INFO: Loading module hook "hook-cryptography.py"... 14936 INFO: Loading module hook "hook-encodings.py"... 15093 INFO: Loading module hook "hook-lxml.etree.py"... 15097 INFO: Loading module hook "hook-pydoc.py"... 15099 INFO: Loading module hook "hook-xml.py"... 15330 INFO: Looking for ctypes DLLs 15334 INFO: Analyzing run-time hooks ... 15339 INFO: Including run-time hook 'pyi_rth_multiprocessing.py' 15344 INFO: Including run-time hook 'pyi_rth_certifi.py' 15355 INFO: Looking for dynamic libraries 15736 INFO: Looking for eggs 15737 INFO: Using Python library xxx\python\python37\python37.dll 15757 INFO: Found binding redirects: [] 15776 INFO: Warnings written to xxxx\Bili_Video_Batch_Download\build\bilibili_downloader_1\war n-bilibili_downloader_1.txt 15942 INFO: Graph cross-reference written to xxxx\Bili_Video_Batch_Download\build\bilibili_dow nloader_1\xref-bilibili_downloader_1.html 15967 INFO: checking PYZ 15968 INFO: Building PYZ because PYZ-00.toc is non existent 15968 INFO: Building PYZ (ZlibArchive) xxxx\Bili_Video_Batch_Download\build\bilibili_downloade r_1\PYZ-00.pyz 16944 INFO: Building PYZ (ZlibArchive) xxxx\Bili_Video_Batch_Download\build\bilibili_downloade r_1\PYZ-00.pyz completed successfully. 16980 INFO: checking PKG 16981 INFO: Building PKG because PKG-00.toc is non existent 16981 INFO: Building PKG (CArchive) PKG-00.pkg 17030 INFO: Building PKG (CArchive) PKG-00.pkg completed successfully. 17034 INFO: Bootloader xxx\python\python37\lib\site-packages\PyInstaller\bootloader\Windows-64bit\run.exe 17034 INFO: checking EXE 17035 INFO: Building EXE because EXE-00.toc is non existent 17035 INFO: Building EXE from EXE-00.toc 17037 INFO: Appending archive to EXE xxxx\Bili_Video_Batch_Download\build\bilibili_downloader_ 1\bilibili_downloader_1.exe 17046 INFO: Building EXE from EXE-00.toc completed successfully. 17053 INFO: checking COLLECT 17053 INFO: Building COLLECT because COLLECT-00.toc is non existent 17055 INFO: Building COLLECT COLLECT-00.toc出現INFO: Building EXE from EXE-00.toc completed successfully. 即打包成功。
在當前路徑下找到dist或build目錄下的bilibili_downloader_1目錄下的bilibili_downloader_1.exe,即是打包后的軟件。
點擊打開即可進行選擇和輸入,開始下載相應視頻。
測試示例如下:
在bilibili_downloader_1.exe的同級目錄下可以看到下載保存的視頻。
3.改進分析
該項目是小編進行B站視頻下載的首次嘗試,難免有很多不足,在實現的過程中和后期的總結中,可以看出還存在一些問題:
- 還不能下載B站上的所有視頻,目前局限于各種普通視頻教程,不能下載直播視頻、大會員番劇等,可以在后期進一步優化;
- 代碼過于繁瑣,有不少功能類似的重復代碼,可以進一步簡化、提高代碼的復用性;
- 沒有采取適當的措施應對B站的反爬,可能會因為請求過多而無法正常下載。
可以在后期進行優化,使整個程序更加健壯。
4.合法性說明
- 本項目的出發點是方便地下載B站上的學習視頻,可以更好地學習各類教程,這對程序猿來說也是一種福利,但是絕不用與其他商業目的,所有讀者可以參考執行思路和程序代碼,但不能用于惡意和非法目的(惡意頻繁下載視頻、非法盈利等),如有違者請自行負責。
- 本項目在實施的過程中可能參考了其他大佬的實現思路,如有侵犯他人利益,請聯系更改或刪除。
- 本項目是B站視頻批量下載系列的第一篇,有很多尚待改進的地方,后期會繼續更新,歡迎各位讀者交流指正,以期不斷改進。
總結
以上是生活随笔為你收集整理的Python 批量下载BiliBili视频 打包成软件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: html表单input file,inp
- 下一篇: python连接sql数据库_pytho