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!