MENU

使用 python 自动下载 m3u8 播放列表ts视频并合并(多线程版)

November 24, 2020 • 数据采集与数据分析(python)

最近在学习python多线程,正好拿下载m3u8入门--使用 python 自动下载 m3u8 播放列表视频(采集 m3u8 文件并下载合并成 MP4)。 本次是多线程版本,只是对之前的稍作修改。

先来说说python多线程:标准的pyhton解释器是Cpython编译的,他的痛处是不存在真正的并发多线程。Cpython提供了一个全局解释器锁GIL,Python 的线程被限制到同一时刻只允许一个线程执行这样一个执行模型。所以,Python 的线程更适用于处理 I/O 和其他需要并发执行的阻塞操作(比如等待 I/O、等待从数据库获取数据等等),而不是需要多处理器并行的计算密集型任务。无法利用多呵CPU同时执行多个和线程,以提高效率。

从单线程项目来看,单单下载m3u8列表文件和合并视频文件来看速度还是可以的,慢速是请求ts地址和下载ts文件部分。这里就请求和下载ts文件的这部分函数予以多线程,代码如下:

Thread(target=m3u8.downloadts,args=(m3u8.q_data,j,event))

使用多线程还要注意一下几点:

  • 因为线程是不可控的,所以需要线程安全的拿到ts地址。这里使用Queue列队,将ts文件地址加入Queue列队,使线程安全的拿去数据。
  • 文件合并要在所有的ts文件全部下载完才开始执行。这里需要使用事件对象来在线程之间做简单通信。

具体看源代码:

from urllib.parse import urlparse
import os
from queue import Queue
import requests
import re
from threading import Thread
import time
from threading import Event

class Gatherm3u8():
    __header = {
        'Connection': 'keep-alive',
        'Host': 'webplay.weilekangnet.com:59666',
        'Referer': 'https://www.myotwq676p5cre1az5p74.xyz:59980/static/player/dplayer.html?v=1',
        'User-Agent': 'Mozilla / 5.0(Linux;Android6.0;Nexus5Build / MRA58N) AppleWebKit / 537.36(KHTML, likeGecko) Chrome / 86.0.4240.183MobileSafari / 537.36'
    }

    def __init__(self,url):
        self.url = url
        self.cddir = 'video'
        self.dir = urlparse(url)[4]
        self.q_data = Queue(0)
        self.i = 0

    def getm3u8(self):
        os.chdir(self.cddir)
        os.mkdir(self.dir)
        req = requests.get(url,verify=False,headers=self.__header)
        text = req.text
        result = re.findall('https.*?out\d*.ts', text, re.S)
        print(result)
        for item in result:
            self.q_data.put(item)
        return True

    def downloadts(self,data,j,event):          #ts请求下载功能函数,将它设为线程。变量:data是ts地址Queue列队,j是线程号,event是事件对象
        while data.qsize() > 0:          #当ts地址Queue列队为空时,线程跳出循环。
            print('当前线程序号', j)
            tsurl = self.q_data.get()
            outname = re.search(r'out\d+',tsurl)
            outnum = re.search(r'\d+',outname.group())
            tsname = '{num:0>4}'.format(num=outnum.group())
            response = requests.get(tsurl, verify=False,headers=self.__header)
            print('正在下载当前页面为', tsurl)
            with open(self.dir + r'\\' + tsname + '.ts', 'wb') as file:
                file.write(response.content)
            self.i = self.i + 1
            time.sle ep(0.5)
        event.set()                 #循环结束,说明地址列队已空,设置事件标志位True,唤醒其他线程(下载完毕,继续向下执行,执行合并函数)
 


    def combine(self):
        print('下载完毕!!!,准备合并')
        print('开始合并成MP4')
        for file in os.listdir(self.dir):
            with open(self.dir + '\\' + file, 'rb') as file_read:
                with open(self.dir + '.mp4', 'ab') as file_write:
                    print('正在合并', file, '加入中。。。')
                    file_write.write(file_read.read())

        print('合并完毕!!!')
        print('正在删除原始文件。。。')
        for file in os.listdir(self.dir):
            os.remove(self.dir + '\\' + file)
        os.rmdir(self.dir)
        t2 = time.time()
        print('删除源文件完毕!~')


if __name__ == '__main__':
    url = input('输入m3u8url')
    m3u8 = Gatherm3u8(url)
    event = Event()     #创建事件对象
    m3u8.getm3u8()      
    for j in range(5):   #创建5个线程
        t = Thread(target=m3u8.downloadts,args=(m3u8.q_data,j,event))
        t.start()   开始执行线程

    event.wait()  #阻塞直到事件内部标准为True,如果内部标志变为True,程序将向下执行。
    m3u8.combine()  #合并视频函数

ts.png