python抓取、解析、下載小電影……

老實說是不是因為標(biāo)題才點進來的
?雖然我這里沒有小電影,但是今天的內(nèi)容也是實打?qū)嵉母韶洠?/span>這叫授人以魚不如授人以漁,有了這技術(shù),小電影不也是分分鐘的事嘛!
前言
一到周末就想搞點有意思的事,比如之前分享的arduino開發(fā),比如上周分享的博客爬蟲,今天我又想搞點有意思的事,所以就有了今天的內(nèi)容——python爬取m3u8視頻資源。不過需要在這里需要著重說明的是,技術(shù)無罪,切勿用技術(shù)搞違法犯罪的事,不然日子真的就越來越有判頭了
。
知識擴展
在開始抓取m3u8視頻(小電影)之前,我們先了解下m3u8的相關(guān)知識,了解的更多,可以讓我們少走彎路。
m3u8是什么?
在此之前,我僅僅知道m3u8是一種網(wǎng)絡(luò)串流,在平時娛樂時候會找一些m3u8的資源,看看直播啥的,直到今天要分享m3u8的相關(guān)內(nèi)容,才真正開始搜集m3u8的相關(guān)知識點。關(guān)于m3u8連百度百科都沒有說明,搜到知乎一篇內(nèi)容(【全網(wǎng)最全】m3u8到底是什么格式?一篇文章搞定m3u8下載),下面的原理圖也是參照的這篇內(nèi)容:

從上面的原理圖中我們可以得到以下知識點:
首先 m3u8并非是視頻格式,而是視頻文件的索引。ts文件才是我們真正播放的視頻資源。ts是日本高清攝像機拍攝下進行的封裝格式,全稱為MPEG2-TS。ts即"Transport Stream"的縮寫。MPEG2-TS格式的特點就是要求從視頻流的任一片段開始都是可以獨立解碼的。
m3u8通常分兩種格式,一種是單碼率(固定分辨率),一種是多碼率(包含多種分別率)。下面就是一個單碼率的m3u8文件的內(nèi)容:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:1619459525
#EXT-X-PROGRAM-DATE-TIME:2021-10-23T05:39:42Z
#EXTINF:10.0,
1634967582-1-1619459525.hls.ts
#EXT-X-PROGRAM-DATE-TIME:2021-10-23T05:39:52Z
#EXTINF:10.0,
1634967592-1-1619459526.hls.ts
#EXT-X-PROGRAM-DATE-TIME:2021-10-23T05:40:02Z
#EXTINF:10.0,
1634967602-1-1619459527.hls.ts
這個就是一個多碼率的m3u8文件,從多碼率的文件格式可以看出來,多碼率中包括了多個單碼率是m3u8文鏈接:
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=500000,RESOLUTION=480x270
500kb/hls/index.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=300000,RESOLUTION=360x202
300kb/hls/index.m3u8
m3u8文件指令
另外我在另外一篇博客發(fā)現(xiàn)m3u8其實是m3u文件的擴展(參考文檔2),這可能就是為什么沒有找到m3u8相關(guān)詞條的原因吧,同時在m3u的詞條中發(fā)現(xiàn)了M#U文件的指令描述(每個字段的含義):
#EXTM3U?//必需,表示一個擴展的m3u文件
#EXT-X-VERSION:3?//hls的協(xié)議版本號,暗示媒體流的兼容性
#EXT-X-MEDIA-SEQUENCE:xx?//首個分段的sequence?number
#EXT-X-ALLOW-CACHE:NO?//是否緩存
#EXT-X-TARGETDURATION:5?//每個視頻分段最大的時長(單位秒)
#EXT-X-DISCONTINUITY?//表示換編碼
#EXTINF:?//每個切片的時長
另外關(guān)于m3u的文件指令是有國際標(biāo)準(zhǔn)的,感興趣的小伙伴可以去看下:
http://tools.ietf.org/html/draft-pantos-http-live-streaming-06
好了,關(guān)于m3u8的相關(guān)內(nèi)容,我們就說這么多,感興趣的小伙伴可以自己繼續(xù)探索,下面我們看下如何用python抓取、解析和下載m3u8文件索引中的視頻文。
抓取、解析、下載
首先我們先要拿到目標(biāo)視頻資源的索引文件,也就是m3u8文件。一般稍微懂點web開發(fā)的小伙伴,應(yīng)該都知道瀏覽器抓包吧,F12打開瀏覽器控制臺,然后選擇Network,刷新下頁面,在左側(cè)資源區(qū)找到index.m3u8文件。
通常會有兩個m3u8,第一個是獲取視頻碼率列表的,也就是多碼率m3u8,這個文件我們是沒辦法直接解析的,我們要找的是包含ts視頻資源的m3u8文件。這里我隨便在網(wǎng)上搜了一個葫蘆娃的視頻,然后通過控制臺拿到m3u8文件地址:

https://vod1.bdzybf1.com/20200819/wMgIH6RN/1000kb/hls/index.m3u8
下面我們就用python解析下載這個視頻文件。
解析m3u8文件
解析m3u8文件最核心的地方是分析m3u8的文件結(jié)構(gòu),然后根據(jù)其文件內(nèi)容寫出對應(yīng)的解析邏輯。這里我推薦直接用requests庫模擬調(diào)用,然后分析響應(yīng)結(jié)果,因為用文本工具之類的查看m3u8文件的話,換行符\n、制表符\t這些看起來不夠直觀,但是requests就不會有這個問題,因為我們解析的就是requests的響應(yīng)結(jié)果。
這里的以我們上面m3u8文件為例響應(yīng)結(jié)果如下:

可以清晰地看出,這個m3u8文件是通過換行符\n分割的,有部分m3u8文件中會出現(xiàn)制表符和換行符組合的情況,所以具體情況具體分析。
另外,我從響應(yīng)內(nèi)容中發(fā)現(xiàn),這個視頻資源是進行了AES-128加密的,所以后面在下載視頻資源的時候要解密。
解析ts視頻地址
因為python代碼都很簡單,代碼量也不多,所以就不展開講了,看代碼注釋應(yīng)該可以看懂。這個方法主要是為了獲取ts視頻文件的地址。
import?requests
def?getTsFileUrlList(m3u8Url):
????#?組裝請求頭
????headers?=?{
????????????'User-Agent':?'Mozilla/5.0?(Windows?NT?10.0;?Win64;?x64)?AppleWebKit/537.36?(KHTML,?like?Gecko)?Chrome/75.0.3770.100?Safari/537.36'
????????????}
????#?請求?m3u8文件,并拿到文件內(nèi)容
????ts_rs?=?requests.get(url?=?m3u8Url,?headers=headers).text
????print(ts_rs)
????#?換行符分割文件內(nèi)容
????list_content?=?ts_rs.split('\n');
????print('list_content:{}'.format(list_content))
????player_list?=?[]
????#?循環(huán)分割結(jié)果
????for?line?in?list_content:
??????#?以下拼接方式可能會根據(jù)自己的需求進行改動
??????if?'#EXTINF'?in?line:
????????continue;
??????#?僅記錄?ts文件地址??
??????elif?line.endswith('.ts'):
????????player_list.append(line)
????print('數(shù)據(jù)列表組裝完成-size:?{}'.format(len(player_list)));
????return?player_list;
運行上面這個方法的話,最終會獲取到m3u8索引文件中所有的ts視頻地址,但是由于我們剛才已經(jīng)從m3u8文件中發(fā)現(xiàn)了,視頻資源是加密的,所以在下載的時候我們需要解密。
下載文件
這里的方法是下載并解密視頻資源,這里我加了一個解密操作,因為注釋夠清晰,所以我也不過多贅述了
def?fileDownloadWithdecrypt(fileSavePath,?player_list):
????#?創(chuàng)建文件夾
????if?not?os.path.exists(fileSavePath):
????????os.mkdir(fileSavePath);
????#?循環(huán)?ts?視頻資源列表
????for?index,?url?in?enumerate(player_list):
????????#?發(fā)送下載請求
????????ts_video?=?requests.get(url?=?url,?headers=headers)
????????#?保存文件
????????with?open('{}/{}.ts'.format(fileSavePath,?str(index?+?1)),?'wb')?as?file:
????????????#?視頻資源解密
????????????context?=?decrypt(ts_video.content);
????????????#?文件寫入
????????????file.write(context)
????????????print('正在寫入第{}個文件'.format(index?+?1))
????print('下載完成');
下面是解密代碼
from?Crypto.Cipher?import?AES???#?用于AES解碼
def?decrypt(context):
????#?加密的key
????key?=??b'2cd1da2aedacaec8';
????#?解密
????cryptor?=?AES.new(key,?AES.MODE_CBC,?key);
????decrypt_content?=?cryptor.decrypt(context);
????return?decrypt_content;
這里用的是pycryptodome庫,安裝方式如下:
?pip3?install?pycryptodome
關(guān)于密文,在m3u8文件里面已經(jīng)有了,直接下載就可以拿到,這里我就不通過代碼拿了:

這里下載視頻會比較費時間,為了提高下效率可以用多線程,但是由于時間的關(guān)系就不演示了。
合并視頻
合并視頻就更簡單了,就是講前面我們保存的視頻合并成一個完整的視頻,合并完之后的格式是mp4。
def?fileMerge(filePath):
????#?查詢出文件中的ts文件
????c?=?os.listdir(filePath)
????#?打開視頻保存文件
????with?open('%s.mp4'?%?filePath,?'wb+')?as?f:
??????#?循環(huán)
??????for?i?in?range(len(c)):
????????#?打開?ts?視頻文件
????????x?=?open('{}/{}.ts'.format(filePath,?str(i?+?1)),?'rb').read()
????????f.write(x)
????print('合并完成')
我看了下,短短的一集葫蘆娃,總共被分割成272個ts文件(好像比這個多,我沒下載完就把網(wǎng)斷了
):

272個ts文件合并成一個視頻文件:

好了,到這里我們python爬取m3u8視頻資源的實例就結(jié)束了,今天的示例還算比較完美,目標(biāo)也比較完美的達成了。奈斯!
完整代碼如下:
import?requests
import?os
from?Crypto.Cipher?import?AES???#?用于AES解碼
headers?=?{
????????????'User-Agent':?'Mozilla/5.0?(Windows?NT?10.0;?Win64;?x64)?AppleWebKit/537.36?(KHTML,?like?Gecko)?Chrome/75.0.3770.100?Safari/537.36'
????????????}
def?getTsFileUrlList(m3u8Url):
????ts_rs?=?requests.get(url?=?m3u8Url,?headers=headers).text
????print(ts_rs)
????list_content?=?ts_rs.split('\n');
????print('list_content:{}'.format(list_content))
????player_list?=?[]
????index?=?1;
????for?line?in?list_content:
??????#?以下拼接方式可能會根據(jù)自己的需求進行改動
??????if?'#EXTINF'?in?line:
????????continue;
??????elif?line.endswith('.ts'):
????????player_list.append(line)
????print('數(shù)據(jù)列表組裝完成-size:?{}'.format(len(player_list)));
????return?player_list;????
????
def?fileDownloadWithdecrypt(fileSavePath,?player_list):
????if?not?os.path.exists(fileSavePath):
????????os.mkdir(fileSavePath);
????for?index,?url?in?enumerate(player_list):
????????ts_video?=?requests.get(url?=?url,?headers=headers)
????????with?open('{}/{}.ts'.format(fileSavePath,?str(index?+?1)),?'wb')?as?file:
????????????context?=?decrypt(ts_video.content);
????????????file.write(context)
????????????print('正在寫入第{}個文件'.format(index?+?1))
????print('下載完成');
????
????
def?fileMerge(filePath):
????c?=?os.listdir(filePath)
????with?open('%s.mp4'?%?filePath,?'wb+')?as?f:
??????for?i?in?range(len(c)):
????????x?=?open('{}/{}.ts'.format(filePath,?str(i?+?1)),?'rb').read()
????????f.write(x)
????print('合并完成')
????
????
def?decrypt(context):
????key?=??b'2cd1da2aedacaec8';
????cryptor?=?AES.new(key,?AES.MODE_CBC,?key);
????decrypt_content?=?cryptor.decrypt(context);
????return?decrypt_content;
????
????
if?__name__?==?'__main__':
????m3u8Url?=?'https://vod1.bdzybf1.com/20200819/wMgIH6RN/1000kb/hls/index.m3u8';
????videoList?=?getTsFileUrlList(m3u8Url);
????print(videoList)
????savePath?=?"./test"
????fileDownloadWithdecrypt(savePath,?videoList);
????fileMerge(savePath);
運行上面的代碼,你就可以得到一集完整的葫蘆娃
當(dāng)然,如果你能找到資源,用上面這段代碼爬取小電影也是可以的
總結(jié)
今天的視頻爬蟲很簡單,可以說非常簡單,核心技術(shù)點也不多,主要涉及如下幾點:
request請求字符串解析(響應(yīng)結(jié)果解析) 文件操作 ASE解密
只需要稍微有一點python基礎(chǔ),就可以做出來,所以這里我也沒什么好總結(jié)的了。
最后,免費為python做一個無償廣告。我一直覺得python是一門不錯的語言,特別是作為腳本使用的時候,真的是太方便了,今天它也依然沒有讓我失望。
其實嚴(yán)格來說,我學(xué)python,但是之前一直詬病于它的縮進語法,所以也就一直沒入門,直到做了一段時間java web開發(fā)之后,回頭再看下python,覺得好簡單,于是就又愉快地使用它了,不過用它寫腳本真的太爽了,短短幾行代碼,搞定java一個繁瑣的項目,而且用起來也很輕便。
最近一年多的時間,我用它處理過數(shù)據(jù)、用它跑數(shù)據(jù)庫統(tǒng)計過數(shù)據(jù),然后日程工作中我可以用它生成文件目錄、爬取資料,也感覺我對它越來越有好感,所以在這里強烈安利各位小伙伴都來學(xué)下python,特別是那些非開發(fā)崗位的小伙伴,python簡直是統(tǒng)計數(shù)據(jù)的利器,雖然不像廣告吹的那么秀,但是技多不壓身呀,而且它真的是可以極大提供我們的效率。
最后的最后,再強調(diào)下,技術(shù)無罪,切勿用技術(shù)搞違法犯罪的事,不然日子真的就越來越有判頭了!
技術(shù)無罪,切勿用技術(shù)搞違法犯罪的事,不然日子真的就越來越有判頭了!
技術(shù)無罪,切勿用技術(shù)搞違法犯罪的事,不然日子真的就越來越有判頭了!
重要的事情說三遍?。?!
另外,今天忙著搞爬蟲了,設(shè)計模式的類圖還沒來得及補,明天要加下油了。好了,鐵子們,晚安吧
參考內(nèi)容
[1] ? ?https://zhuanlan.zhihu.com/p/346683119
[2] ? ?https://www.cnblogs.com/shakin/p/3870439.html
