m3u8簡介
M3U8是Unicode版本的M3U,用UTF-8編碼,m3u8文件其實是 HTTP Live Streaming(縮寫為HLS)協議的部分內容。
HLS 的工作原理是把整個流分成多個小的文件來下載,每次只下載一些。當媒體流正在播放時,客戶端可以選擇從許多不同的備用源中以不同的速率下載同樣的資源,允許流媒體會話適應不同的數據速率。
綜上,m3u8 文件實質是一個播放列表(playlist)
協議格式主要標簽:
EXTM3U:聲明該文件是一個 m3u8 文件;EXT-X-VERSION:聲明 HLS 的協議版本號;EXT-X-TARGETDURATION:表示每個視頻分段序列的最大時長(單位:秒);EXT-X-PLAYLIST-TYPE:表明流媒體類型,若值為VOD表示該視屏流為點播源,若值為EVENT表示該視頻流為直播源;EXT-X-MEDIA-SEQUENCE:表示播放列表第一個 URL 片段文件的序列號;EXT-X-KEY:使用此標簽可以指定解密方法,屬性列表主要包括:1) METHOD:指定加密方法,2) URI:指定密鑰路徑;EXTINF:表示其后 URL 指定的媒體片段序列時長(單位:秒),每個 URL 媒體片段序列之前必須指定該標簽.

代碼示例
在在線視頻網站中,使用python下載加密的流媒體m3u8視頻文件。
獲取文件名與m3u8地址
在瀏覽器中,打開開發者工具,切換到“網絡“選項卡,過濾獲得m3u8流媒體文件地址。
name = 'nz'
url = "https://vod3.buycar5.cn/20210402/Z4mMbiNW/1000kb/hls/index.m3u8"

媒體序列解密
為了爬蟲自動化獲取密鑰,需要了解密鑰獲取流程。
- 利用上述獲取的m3u8文件,切換到”響應(Response)“選型卡查看
EXT-X-KEY標簽的URI屬性的值:
https://ts3.xarxrljt.com:9999/20210402/Z4mMbiNW/1000kb/hls/key.key

- 復制密鑰地址,查看密鑰:

爬蟲獲取密鑰,解密需要使用Crypto庫,使用以下命令安裝所需庫:
pip install pycrypto
利用密鑰地址獲取密鑰:
if "#EXT-X-KEY" in line:
method_pos = line.find("METHOD")
comma_pos = line.find(",")
method = line[method_pos:comma_pos].split('=')[1]#獲取加密方式
print("Decode Method:", method)
uri_pos = line.find("URI")
quotation_mark_pos = line.rfind('"')
key_path = line[uri_pos:quotation_mark_pos].split('"')[1]
key_url = key_path
res = requests.get(key_url,headers=headers)
key = res.content #獲取加密密鑰
視頻序列片段下載
想要下載視頻,首先需要解析得到m3u8文件中的視頻片段序列,然后根據視頻片段序列列表下載視頻:
#視頻片段序列構建
if '#EXTINF' in line:
# 獲取每一媒體序列的.ts文件鏈接地址
if 'http' in list_content[index + 1]:
href = list_content[index + 1]
player_list.append(href)
else:
href = base_url + list_content[index+1]
player_list.append(href)
#視頻序列片段下載
for i,j in enumerate(player_list):
if not os.path.exists('{}/'.format(movie_name + str(i+1) + '.ts')):
cryptor = AES.new(key, AES.MODE_CBC, key)
res = requests.get(j,headers=headers)
requests.adapters.DEFAULT_RETRIES = 5
with open('{}/'.format(movie_name) + str(i+1) + '.ts','wb') as file:
file.write(cryptor.decrypt(res.content))#將解密后的視頻寫入文件
print('正在寫入第{}個文件'.format(i+1))
#time.sleep(5)
下載結果

完整代碼
import requests
import os
from Crypto.Cipher import AES
import time
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36 Edg/86.0.622.56",
"Connection": "close"
}
def m3u8(url,movie_name):
base_url = url[:url.rfind('/')+1]#用於拼接url
rs = requests.get(url,headers=headers).text
list_content = rs.split('\n')
player_list = []
#創建文件夾,用於存放ts文件
if not os.path.exists('{}'.format(movie_name)):
#os.system('mkdir merge')
os.mkdir('{}'.format(movie_name))
key = ''
for index,line in enumerate(list_content):
# 判斷視頻是否經過AES-128加密
if "#EXT-X-KEY" in line:
method_pos = line.find("METHOD")
comma_pos = line.find(",")
method = line[method_pos:comma_pos].split('=')[1]#獲取加密方式
print("Decode Method:", method)
uri_pos = line.find("URI")
quotation_mark_pos = line.rfind('"')
key_path = line[uri_pos:quotation_mark_pos].split('"')[1]
key_url = key_path
res = requests.get(key_url,headers=headers)
key = res.content #獲取加密密鑰
#print("key:", key)
""" 獲取.ts文件鏈接地址方式可根據需要進行定制 """
if '#EXTINF' in line:
# 獲取每一媒體序列的.ts文件鏈接地址
if 'http' in list_content[index + 1]:
href = list_content[index + 1]
player_list.append(href)
else:
href = base_url + list_content[index+1]
player_list.append(href)
if(len(key)):
print('此視頻經過加密')
#print(player_list)#打印ts地址列表
for i,j in enumerate(player_list):
if not os.path.exists('{}/'.format(movie_name + str(i+1) + '.ts')):
cryptor = AES.new(key, AES.MODE_CBC, key)
res = requests.get(j,headers=headers)
requests.adapters.DEFAULT_RETRIES = 5
with open('{}/'.format(movie_name) + str(i+1) + '.ts','wb') as file:
file.write(cryptor.decrypt(res.content))#將解密后的視頻寫入文件
print('正在寫入第{}個文件'.format(i+1))
#time.sleep(5)
else:
#print(i)
pass
else:
print('此視頻未加密')
#print(player_list)#打印ts地址列表
for i,j in enumerate(player_list):
if not os.path.exists('{}/'.format(movie_name + str(i+1) + '.ts')):
res = requests.get(j,headers=headers)
with open('{}/'.format(movie_name) + str(i+1) + '.ts','wb') as file:
file.write(cryptor.decrypt(res.content))#將解密后的視頻寫入文件
print('正在寫入第{}個文件'.format(i+1))
print('下載完成')
name = 'nz'
url = "https://vod3.buycar5.cn/20210402/Z4mMbiNW/1000kb/hls/index.m3u8"
m3u8(url,name)
后記
可以使用多線程,提高視頻下載速度。
文中示例的m3u8視頻文件的加密方式簡單,其他復雜加密方式可以利用JS斷點獲得密鑰,然后傳遞給解密函數。
Enjoy coding!
