Python爬虫:从m3u8文件里提取小视频的正确操作

在网上爬取的小视频(.ts格式)打不开怎么搞?使用IDM下载有时候还会出现数据受法律保护,IDM无法下载该内容,如何解决?这篇博客就来聊聊如何正确提取m3u8文件里的.ts视频,并合成完整的.mp4格式视频。

Posted in Python onMay 14, 2021

在网上爬取的小视频(.ts格式)打不开怎么搞?使用IDM下载有时候还会出现数据受法律保护,IDM无法下载该内容,如何解决?这篇博客就来聊聊如何正确提取m3u8文件里的.ts视频,并合成完整的.mp4格式视频。

Python爬虫:从m3u8文件里提取小视频的正确操作
Python爬虫:从m3u8文件里提取小视频的正确操作

1. HLS协议与m3u8文件

HLS,即 H T T P   L i v e   S t r e a m i n g HTTP\ Live\ Streaming HTTP Live Streaming的缩写,是由苹果公司提出基于HTTP的流媒体网络传输协议。是苹果公司QuickTime X和iPhone软件系统的一部分。它的工作原理是把整个流分成一个个小的基于HTTP的文件来下载,每次只下载一些。当媒体流正在播放时,客户端可以选择从许多不同的备用源中以不同的速率下载同样的资源,允许流媒体会话适应不同的数据速率。在开始一个流媒体会话时,客户端会下载一个包含元数据的扩展 M3U (m3u8) 播放列表文件,用于寻找可用的媒体流。
  M3U8是 U n i c o d e Unicode Unicode 版本的 M3U,用 UTF-8 编码。"M3U"和"M3U8"文件都是苹果公司使用的 H T T P   L i v e   S t r e a m i n g HTTP\ Live\ Streaming HTTP Live Streaming 格式的基础,这种格式可以在 iPhone 和 Macbook 等设备播放。是一种播放多媒体列表的文件格式,文本内容是一系列媒体片段资源,顺序播放该片段资源,即可完整展示多媒体资源。其格式大致如下:

# 未加密
	#EXTM3U
	#EXT-X-VERSION:3
	#EXT-X-TARGETDURATION:8
	#EXT-X-MEDIA-SEQUENCE:0
	#EXTINF:4.000000,
	1af12fece7a000000.ts
	#EXTINF:4.320000,
	1af12fece7a000001.ts
	...
	#EXTINF:3.800000,
	1af12fece7a001155.ts
	#EXT-X-ENDLIST
	
	# 加密
	#EXTM3U
	#EXT-X-VERSION:3
	#EXT-X-TARGETDURATION:6
	#EXT-X-PLAYLIST-TYPE:VOD
	#EXT-X-MEDIA-SEQUENCE:0
	#EXT-X-KEY:METHOD=AES-128,URI="https://ts1.yuyuangewh.com:9999/20200808/1XdSSbTb/2000kb/hls/key.key"
	#EXTINF:3,
	https://ts1.yuyuangewh.com:9999/20200808/1XdSSbTb/2000kb/hls/EUtRrqJU.ts
	#EXTINF:4.72,
	https://ts1.yuyuangewh.com:9999/20200808/1XdSSbTb/2000kb/hls/HF90vrrN.ts
	...
	#EXTINF:0.24,
	https://ts1.yuyuangewh.com:9999/20200808/1XdSSbTb/2000kb/hls/b7ZLcRqT.ts
	#EXT-X-ENDLIST

  中文维基百科----HTTP Live Streaming
  中文维基百科----M3U

下面介绍几个m3u8文件中常见的标签:

标签 格式 作用
EXTM3U #EXTM3U 表明该文件是一个m3u8文件,每个m3u8文件必须将该标签放置在第一行
EXT-X-VERSION EXT-X-VERSION:<number> 表明该文件是一个m3u8文件,每个m3u8文件必须将该标签放置在第一行
EXT-X-TARGETDURATION #EXT-X-TARGETDURATION:<s> 表示每个视频分段最大的时长(单位秒)
EXT-X-PLAYLIST-TYPE #EXT-X-PLAYLIST-TYPE:<type-enum> 表明流媒体类型,VOD 表示该视屏流为点播源,因此服务器不能更改该m3u8文件;EVENT表示该视频流为直播源,因此服务器不能更改或删除该文件任意部分内容,但是可以在文件末尾添加新内容
EXT-X-MEDIA-SEQUENCE #EXT-X-MEDIA-SEQUENCE:<number> 表示播放列表第一个URL片段文件的序列号,每个媒体片段URL都拥有一个唯一的整型序列号,每个媒体片段序列号按出现顺序依次加 1,如果该标签未指定,则默认序列号从0开始
EXT-X-KEY #EXT-X-KEY:METHOD=AES-128,URI="http:xxxx",IV="xxxx" 表明视频流文件的加解密方法,METHOD表示加密方式,URI表示密钥路径,该密钥是一个 16 字节的数据,IV是一个128位的十六进制数值
EXTINF #EXTINF:<duration>,[<title>] 表示其后 URL 指定的媒体片段时长(单位为秒),duration可以为十进制的整型或者浮点型,其值必须小于或等于EXT-X-TARGETDURATION指定的值
EXT-X-ENDLIST #EXT-X-ENDLIST 表明m3u8文件的结束

  简书:m3u8 文件格式详解 作者:Whyn

2. 第三方库----m3u8

  m3u8是一个专门用于解析m3u8文件的解析器,有关库的详细操作请参阅官方示例

# 安装m3u8
	pip install m3u8
# 加载m3u8文件
	import m3u8

	# 返回一个M3U8对象
	playlist = m3u8.load(uri='http://videoserver.com/playlist.m3u8')	# url
	# playlist = m3u8.load(uri='playlist.m3u8')	# file
	print(playlist.segments)	# 打印EXT-X-KEY标签和所有的EXTINF标签:
	print(playlist.target_duration)	# 打印EXT-X-TARGETDURATION标签的值

	for key in playlist.keys:
		if key:
			# 如果视频文件加密,可以查看加密参数
			print(key.uri, key.method, key.iv)

3. 合成mp4文件

Python爬虫:从m3u8文件里提取小视频的正确操作
  本次就以这个视频为例,流程如下:
  1. 找到视频对应的.m3u8文件
  2. 解析.m3u8文件,从中提取.ts视频的url
  3. 下载.ts格式的视频
  4. 解密.ts格式的视频(如果视频流没有加密,则该步不是必须的)
  5. 合成.mp4或其他格式的视频

# 第1步,我载了m3u8文件,也可以直接使用m3u8文件对应的url
	playlist = m3u8.load(uri='./data/index.m3u8')

	# 第2步,提取URL
	for seg in playlist.segments:
        print(seg.uri)
    
    # 第3步,下载ts视频
    with open('xxxxx.ts', 'wb') as f:
        ts = get_ts(url)
        f.write(ts)
   
	# 第4步,解密
	cipher_text = pad(data_to_pad=cipher_text, block_size=AES.block_size)
    aes = AES.new(key=key, mode=AES.MODE_CBC, iv=iv)
    cipher_text = aes.decrypt(cipher_text)
    
	# 第5步,合成
	files = glob.glob(os.path.join('./video', '*.ts'))
    for file in files:
        with open(file, 'rb') as fr, open('./video_de/baitoushan.mp4', 'ab') as fw:
            content = fr.read()
            fw.write(content )

4. 完整代码

# -*- coding: utf-8 -*-
# @Time    : 2021/5/10 20:11
# @Author  : XiaYouRan
# @Email   : youran.xia@foxmail.com
# @File    : video.py
# @Software: PyCharm


from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from concurrent.futures import ThreadPoolExecutor
import requests
import m3u8
import glob
import os
import time
import logging


logging.getLogger("urllib3").setLevel(logging.WARNING)


def AESDecrypt(cipher_text, key, iv):
    cipher_text = pad(data_to_pad=cipher_text, block_size=AES.block_size)
    aes = AES.new(key=key, mode=AES.MODE_CBC, iv=iv)
    cipher_text = aes.decrypt(cipher_text)
    # clear_text = unpad(padded_data=cipher_text, block_size=AES.block_size)
    return cipher_text


headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
                         'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}


def get_ts(url):
    try:
        response = requests.get(url, verify=False)
        response.raise_for_status()
        response.encoding = 'utf-8'
        return response.content
    except Exception as err:
        print(err)
        return b''


def save_ts(url, index):
    filename = os.path.join('./video', str(index).zfill(5) + '.ts')
    with open(filename, 'wb') as f:
        ts = get_ts(url)
        f.write(ts)
    print(filename + ' is ok!')


if __name__ == '__main__':
    playlist = m3u8.load(uri='./data/index.m3u8')

	# 线程池,引入index可以防止合成时视频发生乱序
    with ThreadPoolExecutor(max_workers=10) as pool:
        for index, seg in enumerate(playlist.segments):
            pool.submit(save_ts, seg.uri, index)

    key = get_ts(playlist.keys[-1].uri)

    files = glob.glob(os.path.join('./video', '*.ts'))
    for file in files:
        with open(file, 'rb') as fr, open('./video_de/baitoushan.mp4', 'ab') as fw:
            content = fr.read()
            encontent = AESDecrypt(content, key=key, iv=key)
            fw.write(encontent)
        print(file + ' is ok!')

下载.ts文件效果如下:

Python爬虫:从m3u8文件里提取小视频的正确操作
  合成.mp4文件效果如下:

Python爬虫:从m3u8文件里提取小视频的正确操作

Python爬虫:从m3u8文件里提取小视频的正确操作

5. 结束语

Musicer开源代码仓库


Musicer音乐爬虫已经开源了哦,持续更新中,欢迎来踩来Star哦!ヾ(≧∇≦*)ヾ

Python 相关文章推荐
pandas全表查询定位某个值所在行列的方法
Apr 12 Python
Python登录注册验证功能实现
Jun 18 Python
浅谈Python的条件判断语句if/else语句
Mar 21 Python
numpy.linspace函数具体使用详解
May 27 Python
pyqt5让图片自适应QLabel大小上以及移除已显示的图片方法
Jun 21 Python
python pandas生成时间列表
Jun 29 Python
python如何实现异步调用函数执行
Jul 08 Python
python 五子棋如何获得鼠标点击坐标
Nov 04 Python
详解用Python进行时间序列预测的7种方法
Mar 13 Python
Keras 切换后端方式(Theano和TensorFlow)
Jun 19 Python
使用python编写一个语音朗读闹钟功能的示例代码
Jul 14 Python
Django数据库(SQlite)基本入门使用教程
Jul 07 Python
MATLAB 全景图切割及盒图显示的实现步骤
使用pandas或numpy处理数据中的空值(np.isnan()/pd.isnull())
May 14 #Python
PyQt5爬取12306车票信息程序的实现
python flask框架快速入门
如何将numpy二维数组中的np.nan值替换为指定的值
May 14 #Python
使用numpy nonzero 找出非0元素
May 14 #Python
Python机器学习之KNN近邻算法
May 14 #Python
You might like
Yii使用find findAll查找出指定字段的实现方法
2014/09/05 PHP
PDO操作MySQL的基础教程(推荐)
2017/08/18 PHP
PHP生成随机数的方法总结
2018/03/01 PHP
PHP接口类(interface)的定义、特点和应用示例
2020/05/18 PHP
超级酷和最实用的jQuery实例收集(20个)
2010/04/21 Javascript
html中table数据排序的js代码
2011/08/09 Javascript
JavaScript简单计算人的年龄示例
2017/04/15 Javascript
JavaScript字符串_动力节点Java学院整理
2017/06/27 Javascript
使用Angular Cli如何创建Angular私有库详解
2019/01/30 Javascript
JS双向链表实现与使用方法示例(增加一个previous属性实现)
2019/01/31 Javascript
Vue.js实现可排序的表格组件功能示例
2019/02/19 Javascript
微信小程序收货地址API兼容低版本解决方法
2019/05/18 Javascript
浅探express路由和中间件的实现
2019/09/30 Javascript
javascript随机变色实例代码
2019/10/15 Javascript
vue计算属性无法监听到数组内部变化的解决方案
2019/11/06 Javascript
[00:35]可解锁地面特效
2018/12/20 DOTA
Python实现基本数据结构中队列的操作方法示例
2017/12/04 Python
pandas 小数位数 精度的处理方法
2018/06/09 Python
通过python的matplotlib包将Tensorflow数据进行可视化的方法
2019/01/09 Python
解决json中ensure_ascii=False的问题
2020/04/03 Python
Python如何急速下载第三方库详解
2020/11/02 Python
CSS3教程:background-clip和background-origin
2008/10/17 HTML / CSS
整理HTML5移动端开发的常用触摸事件
2016/04/15 HTML / CSS
薇诺娜官方网上商城:专注敏感肌肤
2017/05/25 全球购物
印尼披萨外送专家:Domino’s Pizza印尼
2017/12/28 全球购物
新西兰廉价汽车租赁:Snap Rentals
2018/09/14 全球购物
什么是Deployment descriptors;都有什么类型的部署描述符
2015/07/28 面试题
销售业务员岗位职责
2014/01/29 职场文书
法学院毕业生求职信
2014/06/25 职场文书
产品委托授权书范本
2014/09/16 职场文书
教师节感谢信
2015/01/22 职场文书
餐厅收银员岗位职责
2015/04/07 职场文书
2015年社区环境卫生工作总结
2015/04/21 职场文书
指导老师鉴定意见
2015/06/05 职场文书
SpringBoot2 参数管理实践之入参出参与校验的方式
2021/06/16 Java/Android
经典《舰娘》游改全新动画预告 预定11月开播
2022/04/01 日漫