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

共 5201字,需浏览 11分钟

 ·

2022-03-04 19:29

前言

学了一段时间爬虫,终于把进程、线程、协程给搞明白了,还没搞明白的强烈推荐看一下蚂蚁老师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("
  • 正在播放:(.*?)
  • "
    ,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("
  • 正在播放:(.*?)
  • "
    ,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

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

    浏览 97
    点赞
    评论
    收藏
    分享

    手机扫一扫分享

    分享
    举报
    评论
    图片
    表情
    推荐
    点赞
    评论
    收藏
    分享

    手机扫一扫分享

    分享
    举报