首页 文章详情

​Python爬虫:使用协程下载m3u8视频

蚂蚁学Python | 866 2022-03-04 19:29 0 0 0
UniSMS (合一短信)

前言

学了一段时间爬虫,终于把进程、线程、协程给搞明白了,还没搞明白的强烈推荐看一下蚂蚁老师B站的视频:https://www.bilibili.com/video/BV1bK411A7tV?p=1,思路很清晰。爬虫属于IO密集型任务,且资源开销少,对于爬虫来说很有优势,对于使用爬虫下载视频,那效率就更高了。我们以人人影视最新一集的国王排名为例:https://www.rryy123.com/play/40479-1-18.html

一,页面分析

通过抓包我们很容易发现index.m3u8这个包,但是响应内容却没显示可用的数据。为了验证一下我们对这个网址https://c2.monidai.com/20220225/yOm07IlJ/index.m3u8发起请求,并保存为m3u8格式,打开发现得到的结果如下:

但是发现里面的网址是以png结尾?于是我们对上面的一个网址发起请求进行验证,并以二进制写入文件,文件名后缀以.ts结尾。下载完成后打开正是我们想要的视频文件!

那如何通过视频的URL定位到index.m3u8地址呢?通过检查网页源代码,搜索关键字m3u8很容易就定位到,如下图:

至此,第一步我们已经完成,通过正则匹配可以获得对应地址,代码如下:

    def get_m3u8_url(self):
        # 根据剧集网址,获取m3u8下载索引
        res =requests.get(url=self.url,headers=self.headers).text
        first_m3u8_url = re.findall(';var now="(.*?)"',res,re.S)[0]     # 获取m3u8下载地址
        self.name = re.findall("<li>正在播放:(.*?)</li>",res,re.S)[0]   # 获取下载片名
        print(self.name+"准备开始下载")
        res_second = requests.get(url=first_m3u8_url,headers=self.headers).text
        with open(self.name+".m3u8",'w',encoding='utf-8'as f:
            f.write(res_second)

二,获取m3u8播放列表

根据上一步下载的m3u8文件,提取下载地址,代码如下:

    def get_ts_url(self):
        # 根据m3u8下载索引,获取各个下载地址
        ts_url_list = []
        with open(self.name+".m3u8",'r',encoding='utf-8'as f:
            for line in f:
                if line.startswith('#'):
                    continue
                line = line.strip()
                ts_url_list.append(line)
        return ts_url_list

三,编码实现

然后下一步我们就开始下载视频了,完整代码如下:

# -*- coding:utf-8 -*-
import time
import re
import os
import requests
import asyncio
import aiohttp
import aiofiles
import nest_asyncio
nest_asyncio.apply()

class RRyy123:
    def __init__(self):
        self.url = None
        self.name = None
        self.headers = {'User-Agent':'Mozilla / 5.0(Windows NT 10.0;Win64;x64) AppleWebKit / 537.36(KHTML, likeGecko) Chrome / 95.0.4638.69Safari / 537.36'}


    def get_m3u8_url(self):
        # 根据剧集网址,获取m3u8下载索引
        res =requests.get(url=self.url,headers=self.headers).text
        first_m3u8_url = re.findall(';var now="(.*?)"',res,re.S)[0]     # 获取m3u8下载地址
        self.name = re.findall("<li>正在播放:(.*?)</li>",res,re.S)[0]   # 获取下载片名
        print(self.name+"准备开始下载")
        res_second = requests.get(url=first_m3u8_url,headers=self.headers).text
        with open(self.name+".m3u8",'w',encoding='utf-8'as f:
            f.write(res_second)


    def get_ts_url(self):
        # 根据m3u8下载索引,获取各个ts文件地址
        ts_url_list = []
        with open(self.name+".m3u8",'r',encoding='utf-8'as f:
            for line in f:
                if line.startswith('#'):
                    continue
                line = line.strip()
                ts_url_list.append(line)
        return ts_url_list


    async def download_ts(self,url,session,sem):
        # 使用协程下载单个ts文件
        ts_name = os.path.splitext(url)[0].split("/")[-1]
        async with sem:     # 控制协程信号量
            async with session.get(url =url) as res:
                if not os.path.exists(f"./temp_{self.name}"):
                    os.makedirs(f"./temp_{self.name}/")
                async with aiofiles.open(f"./temp_{self.name}/{ts_name}.ts",'wb'as f:
                    await f.write(await res.read())
                    print(f"{ts_name}.ts下载完成")

    async def main(self):
        # 根据m3u8文件中的ts网址,下载ts视频
        tasks = []
        self.get_m3u8_url()
        ts_url_list = self.get_ts_url()
        sem = asyncio.Semaphore(5)  # 设置信号量,控制异步数量
        async with aiohttp.ClientSession(headers=self.headers) as session:
            for ts_url in ts_url_list:
                tasks.append(asyncio.create_task(self.download_ts(url=ts_url,session=session,sem=sem)))
            await asyncio.wait(tasks)



if __name__ == '__main__':
    t = time.time()
    f = RRyy123()
    f.url = "https://www.rryy123.com/video/?40479-1-18.html"
    asyncio.run(f.main())
    print(f"{f.name}下载完成,总耗时{int(time.time()-t)}s.")

我这是测了一下,下载一个128M的文件只要15s!!!

四,ts文件合并

下载完成后我们要对ts文件进行合并了,这里推荐使用ffmpeg,windows下载地址:http://ffmpeg.org/download.html#build-windows。这里要注意的是 将解压后的文件目录中 bin 目录(包含 ffmpeg.exe )添加进 path 环境变量中。使用 ffmpeg 合并ts 文件方法非常简单,只需要在终端输入一行命令:

ffmpeg -f concat -i filename.txt -c copy output.mp4

首先在ts文件相同目录中,把所有要合并的ts文件名保存在filename.txt:如下图:

然后使用命令行进行合并,这里要注意文件路径,合并完成后删除ts文件,这里为了防止误删使用send2trash库,删除内容可以放进回收站,代码如下:

import send2trash
import os

def merge_video(name):
    # 合并视频
    ts_url_list=[]
    with open(name + ".m3u8"'r', encoding='utf-8'as f:
        for line in f:
            if line.startswith('#'):
                continue
            line = line.strip()
            ts_url_list.append(line)

    f = open(f"./temp_{name}/filename.txt" ,'w' ,encoding="utf-8")
    for ts_url in ts_url_list:
        ts_name = os.path.splitext(ts_url)[0].split("/")[-1]
        f.write("file  "+ ts_name +".ts\n")
    f.close()
    print(f"{name}开始合并。。。。")
    os.system(f"ffmpeg -f concat -i ./temp_{name}/filename.txt -c copy {name}.mp4")
    print(f"{name}视频合成成功")



def remove_temp(name):
    # 删除ts文件,放入回收站
    send2trash.send2trash(f"./temp_{name}")
    send2trash.send2trash(f"{name}.m3u8")
    print("临时文件已删除。")

if __name__ == '__main__':
    name = "国王排名-第19集"
    merge_video(name)
    remove_temp(name)


最后,推荐蚂蚁老师的Python课程:


如果购买课程,加微信:ant_learn_python

找蚂蚁老师,进群、领资料

good-icon 0
favorite-icon 0
收藏
回复数量: 0
    暂无评论~~
    Ctrl+Enter