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


前言

一到周末就想搞點有意思的事,比如之前分享的arduino開發,比如上周分享的博客爬蟲,今天我又想搞點有意思的事,所以就有了今天的內容——python爬取m3u8視頻資源。不過需要在這里需要着重說明的是,技術無罪,切勿用技術搞違法犯罪的事,不然日子真的就越來越有判頭了。

知識擴展

在開始抓取m3u8視頻之前,我們先了解下m3u8的相關知識,了解的更多,可以讓我們少走彎路。

m3u8是什么?

在此之前,我僅僅知道m3u8是一種網絡串流,在平時娛樂時候會找一些m3u8的資源,看看直播啥的,直到今天要分享m3u8的相關內容,才真正開始搜集m3u8的相關知識點。關於m3u8連百度百科都沒有說明,搜到知乎一篇內容(【全網最全】m3u8到底是什么格式?一篇文章搞定m3u8下載),下面的原理圖也是參照的這篇內容:

從上面的原理圖中我們可以得到以下知識點:

  • 首先m3u8並非是視頻格式,而是視頻文件的索引。
  • ts文件才是我們真正播放的視頻資源。ts是日本高清攝像機拍攝下進行的封裝格式,全稱為MPEG2-TSts即"Transport Stream"的縮寫。MPEG2-TS格式的特點就是要求從視頻流的任一片段開始都是可以獨立解碼的。

m3u8通常分兩種格式,一種是單碼率(固定分辨率),一種是多碼率(包含多種分別率)。下面就是一個單碼率的m3u8文件的內容:

#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文件指令

另外我在另外一篇博客發現m3u8其實是m3u文件的擴展(參考文檔2),這可能就是為什么沒有找到m3u8相關詞條的原因吧,同時在m3u的詞條中發現了M#U文件的指令描述(每個字段的含義):

#EXTM3U //必需,表示一個擴展的m3u文件

#EXT-X-VERSION:3 //hls的協議版本號,暗示媒體流的兼容性

#EXT-X-MEDIA-SEQUENCE:xx //首個分段的sequence number

#EXT-X-ALLOW-CACHE:NO //是否緩存

#EXT-X-TARGETDURATION:5 //每個視頻分段最大的時長(單位秒)

#EXT-X-DISCONTINUITY //表示換編碼

#EXTINF: //每個切片的時長

另外關於m3u的文件指令是有國際標准的,感興趣的小伙伴可以去看下:

http://tools.ietf.org/html/draft-pantos-http-live-streaming-06

好了,關於m3u8的相關內容,我們就說這么多,感興趣的小伙伴可以自己繼續探索,下面我們看下如何用python抓取、解析和下載m3u8文件索引中的視頻文。

抓取、解析、下載

首先我們先要拿到目標視頻資源的索引文件,也就是m3u8文件。一般稍微懂點web開發的小伙伴,應該都知道瀏覽器抓包吧,F12打開瀏覽器控制台,然后選擇Network,刷新下頁面,在左側資源區找到index.m3u8文件。

通常會有兩個m3u8,第一個是獲取視頻碼率列表的,也就是多碼率m3u8,這個文件我們是沒辦法直接解析的,我們要找的是包含ts視頻資源的m3u8文件。這里我隨便在網上搜了一個葫蘆娃的視頻,然后通過控制台拿到m3u8文件地址:

https://vod1.bdzybf1.com/20200819/wMgIH6RN/1000kb/hls/index.m3u8

下面我們就用python解析下載這個視頻文件。

解析m3u8文件

解析m3u8文件最核心的地方是分析m3u8的文件結構,然后根據其文件內容寫出對應的解析邏輯。這里我推薦直接用requests庫模擬調用,然后分析響應結果,因為用文本工具之類的查看m3u8文件的話,換行符\n、制表符\t這些看起來不夠直觀,但是requests就不會有這個問題,因為我們解析的就是requests的響應結果。

這里的以我們上面m3u8文件為例響應結果如下:

可以清晰地看出,這個m3u8文件是通過換行符\n分割的,有部分m3u8文件中會出現制表符和換行符組合的情況,所以具體情況具體分析。

另外,我從響應內容中發現,這個視頻資源是進行了AES-128加密的,所以后面在下載視頻資源的時候要解密。

解析ts視頻地址

因為python代碼都很簡單,代碼量也不多,所以就不展開講了,看代碼注釋應該可以看懂。這個方法主要是為了獲取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文件,並拿到文件內容
    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 = []
    # 循環分割結果
    for line in list_content:
      # 以下拼接方式可能會根據自己的需求進行改動
      if '#EXTINF' in line:
        continue;
      # 僅記錄 ts文件地址  
      elif line.endswith('.ts'):
        player_list.append(line)
    print('數據列表組裝完成-size: {}'.format(len(player_list)));
    return player_list;

運行上面這個方法的話,最終會獲取到m3u8索引文件中所有的ts視頻地址,但是由於我們剛才已經從m3u8文件中發現了,視頻資源是加密的,所以在下載的時候我們需要解密。

下載文件

這里的方法是下載並解密視頻資源,這里我加了一個解密操作,因為注釋夠清晰,所以我也不過多贅述了

def fileDownloadWithdecrypt(fileSavePath, player_list):
    # 創建文件夾
    if not os.path.exists(fileSavePath):
        os.mkdir(fileSavePath);
    # 循環 ts 視頻資源列表
    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('下載完成');

下面是解密代碼

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

關於密文,在m3u8文件里面已經有了,直接下載就可以拿到,這里我就不通過代碼拿了:

這里下載視頻會比較費時間,為了提高下效率可以用多線程,但是由於時間的關系就不演示了。

合並視頻

合並視頻就更簡單了,就是講前面我們保存的視頻合並成一個完整的視頻,合並完之后的格式是mp4

def fileMerge(filePath):
    # 查詢出文件中的ts文件
    c = os.listdir(filePath)
    # 打開視頻保存文件
    with open('%s.mp4' % filePath, 'wb+') as f:
      # 循環
      for i in range(len(c)):
        # 打開 ts 視頻文件
        x = open('{}/{}.ts'.format(filePath, str(i + 1)), 'rb').read()
        f.write(x)
    print('合並完成')

我看了下,短短的一集葫蘆娃,總共被分割成272ts文件(好像比這個多,我沒下載完就把網斷了):

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

好了,到這里我們python爬取m3u8視頻資源的實例就結束了,今天的示例還算比較完美,目標也比較完美的達成了。

完整代碼如下:

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:
      # 以下拼接方式可能會根據自己的需求進行改動
      if '#EXTINF' in line:
        continue;
      elif line.endswith('.ts'):
        player_list.append(line)
    print('數據列表組裝完成-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);

運行上面的代碼,你就可以得到一集完整的葫蘆娃
當然,如果你能找到資源,用上面這段代碼爬取小電影也是可以的

總結

今天的視頻爬蟲很簡單,可以說非常簡單,核心技術點也不多,主要涉及如下幾點:

  • request請求
  • 字符串解析(響應結果解析)
  • 文件操作
  • ASE解密

只需要稍微有一點python基礎,就可以做出來,所以這里我也沒什么好總結的了。

最后,免費為python做一個無償廣告。我一直覺得python是一門不錯的語言,特別是作為腳本使用的時候,真的是太方便了,今天它也依然沒有讓我失望。

其實嚴格來說,我學python,但是之前一直詬病於它的縮進語法,所以也就一直沒入門,直到做了一段時間java web開發之后,回頭再看下python,覺得好簡單,於是就又愉快地使用它了,不過用它寫腳本真的太爽了,短短幾行代碼,搞定java一個繁瑣的項目,而且用起來也很輕便。

最近一年多的時間,我用它處理過數據、用它跑數據庫統計過數據,然后日程工作中我可以用它生成文件目錄、爬取資料,也感覺我對它越來越有好感,所以在這里強烈安利各位小伙伴都來學下python,特別是那些非開發崗位的小伙伴,python簡直是統計數據的利器,雖然不像廣告吹的那么秀,但是技多不壓身呀,而且它真的是可以極大提供我們的效率。

最后的最后,再強調下,技術無罪,切勿用技術搞違法犯罪的事,不然日子真的就越來越有判頭了!

技術無罪,切勿用技術搞違法犯罪的事,不然日子真的就越來越有判頭了!

技術無罪,切勿用技術搞違法犯罪的事,不然日子真的就越來越有判頭了!

重要的事情說三遍!!!

另外,今天忙着搞爬蟲了,設計模式的類圖還沒來得及補,明天要加下油了。好了,鐵子們,晚安吧

參考內容


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM