Python 批量下載BiliBili視頻 打包成軟件
一、項目概述
1.項目背景
有一天,我突然想找點事做,想起一直想學但是沒有學的C語言,就決定來學一下。可是怎么學呢?看書的話太無聊,報班學呢又快吃土了沒錢,不如去B站看看?果然,關(guān)鍵字C語言搜索,出現(xiàn)了很多C語言的講課視頻:
B站https://www.bilibili.com/是一個很神奇的地方,簡直就是一個無所不有的寶庫,幾乎可以滿足你一切的需求和視覺欲。不管你是想看動畫、番劇 ,還是游戲、鬼畜 ,亦或科技和各類教學視頻 ,只要你能想到的,基本上都可以在B站找到。對于程序猿或即將成為程序猿的人來說,B站上的編程學習資源是學不完的,可是B站沒有提供下載的功能,如果想保存下載在需要的時候看,那就是一個麻煩了。我也遇到了這個問題,于是研究怎么可以實現(xiàn)一鍵下載視頻,最終用Python這門神奇的語言實現(xiàn)了。
2.環(huán)境配置
這次項目不需要太多的環(huán)境配置,最主要的是有ffmpeg(一套可以用來記錄、轉(zhuǎn)換數(shù)字音頻、視頻,并能將其轉(zhuǎn)化為流的開源計算機程序)并設(shè)置環(huán)境變量就可以了。ffmpeg主要是用于將下載下來的視頻和音頻進行合并形成完整的視頻。
下載ffmpeg
可點擊https://download.csdn.net/download/CUFEECR/12234789或進入官網(wǎng)http://ffmpeg.org/download.html進行下載,并解壓到你想保存的目錄。
設(shè)置環(huán)境變量
復(fù)制ffmpeg的bin路徑,如 xxx\ffmpeg-20190921-ba24b24-win64-shared\bin此電腦右鍵點擊屬性,進入控制面板\系統(tǒng)和安全\系統(tǒng) 點擊高級系統(tǒng)設(shè)置→進入系統(tǒng)屬性彈窗→點擊環(huán)境變量→進入環(huán)境變量彈窗→選擇系統(tǒng)變量下的Path→點擊編輯點擊→進入編輯環(huán)境變量彈窗 點擊新建→粘貼之前復(fù)制的bin路徑 點擊確定,逐步保存退出 動態(tài)操作示例如下: 
除了ffmpeg,還需要安裝pyinstaller庫用于程序打包。可用以下命令進行安裝:
pip?install?pyinstaller
如果遇到安裝失敗或下載速度較慢,可換源:
pip?install?pyinstaller?-i?https://pypi.doubanio.com/simple/
二、項目實施
1.導(dǎo)入需要的庫
import?json
import?os
import?re
import?shutil
import?ssl
import?time
import?requests
from?concurrent.futures?import?ThreadPoolExecutor
from?lxml?import?etree
導(dǎo)入的庫包括用于爬取和解析網(wǎng)頁的庫,還包括創(chuàng)建線程池的庫和進行其他處理的庫,大多數(shù)都是Python自帶的,如有未安裝的庫,可使用pip install xxx命令進行安裝。
2.設(shè)置請求參數(shù)
##?設(shè)置請求頭等參數(shù),防止被反爬
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'
}
設(shè)置請求頭等參數(shù),減少被反爬的可能。
3.基本處理
def?re_video_info(text,?pattern):
????'''利用正則表達式匹配出視頻信息并轉(zhuǎn)化成json'''
????match?=?re.search(pattern,?text)
????return?json.loads(match.group(1))
def?create_folder(aid):
????'''創(chuàng)建文件夾'''
????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)
????????????pass
????????elif?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庫請求得到請求后的網(wǎng)頁,屬于文本,通過正則表達式提取得到關(guān)于將要下載的視頻的有用信息,便于后一步處理。 文件處理 將下載視頻完成后的相關(guān)文件進行處理,包括刪除生成的臨時的音視頻分離的文件和移動最終視頻文件到指定文件夾。
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?=?0
????with?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?=?0
????with?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,?index
def?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?=?0
????with?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?=?0
????with?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("視頻下載結(jié)束:%s"?%?video_name)
????video_audio_merge_single(video_name)
這部分包括系列視頻的批量下載和單個視頻的下載,兩者的大體實現(xiàn)原理近似,但是由于兩個函數(shù)的參數(shù)有差別,因此分別實現(xiàn)。在具體的實現(xiàn)中,首先更新請求頭,請求視頻鏈接并保存視頻(無聲音),再請求音頻鏈接并保存音頻,在這個過程中得到相應(yīng)的視頻和音頻文件的大小。
5.視頻和音頻合并成完整的視頻
def?video_audio_merge_batch(result):
????'''使用ffmpeg批量視頻音頻合并'''
????video_name?=?result.result()[0]
????index?=?result.result()[1]
????import?subprocess
????video_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視頻下載結(jié)束:%s"?%?(index,?video_name.split('/')[2]))
def?video_audio_merge_single(video_name):
????'''使用ffmpeg單個視頻音頻合并'''
????print("視頻合成開始:%s"?%?video_name)
????import?subprocess
????command?=?'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("視頻合成結(jié)束:%s"?%?video_name)
這個過程也是批量和單個分開,大致原理差不多,都是調(diào)用subprogress模塊生成子進程,Popen類來執(zhí)行shell命令,由于已經(jīng)將ffmpeg加入環(huán)境變量,所以shell命令可以直接調(diào)用ffmpeg來合并音視頻。
6.3種下載方式的分別實現(xiàn)
def?batch_download():
????'''使用多線程批量下載視頻'''
????##?提示輸入需要下載的系列視頻對應(yīng)的id
????aid?=?input('請輸入要下載的視頻id(舉例:鏈接https://www.bilibili.com/video/av91748877?p=1中id為91748877),默認為91748877\t')
????if?aid:
????????pass
????else:
????????aid?=?'91748877'
????##?提示選擇清晰度
????quality?=?input('請選擇清晰度(1代表高清,2代表清晰,3代表流暢),默認高清\t')
????if?quality?==?'2':
????????pass
????elif?quality?==?'3':
????????pass
????else:
????????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)
????##?創(chuàng)建臨時文件夾
????create_folder('video')
????create_folder('video_final')
????##?定義一個線程池,大小為3
????pool?=?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)
????????##?解析出視頻詳情的json
????????video_info_temp?=?re_video_info(res.text,?'__playinfo__=(.*?)
亚洲无码理论
|
91人人人人
|
国产高清色情
|
日韩欧美性爱视频
|
久久夜色精品国产
|
