利用协程爬取m3u8视频

利用协程爬取m3u8视频

​ 在进行了爬虫的相关学习之后,自己尝试爬取了一些视频,但动辄ts文件就几百个,单线程伤不起那时间,一个一个等实在太慢了,想利用多线程,查看相关资料,又说python是假的多线程,而且爬取视频这操作也属于IO阻塞操作多的那种,感觉时间提升也不大,多线程和多进程还有协程,协程挺适合这种的,就毅然决然的使用协程了。注:各位大佬轻喷

一.查看网站并分析

1.找到各集数对应网站

首先理清爬取思路,对我这种刚入门的菜鸡来说,爬视频就是看它是不是mp4,或者是不是m3u8格式的,但如标题所言,我今天找的这个是m3u8格式的。直接查看网站源码,如图所示,我们想爬取所有集数首先需要找到这些集数的href

在这里插入图片描述

可以看到,我们如果想进这些集数的网站里面得到m3u8文件还是需要进行拼接操作的,这样才能得到进一步m3u8文件。

​ 我们首先进入第一集里面看一看,瞅一瞅,熟悉的打开网站,熟悉的搜索video,打开一看,哇,

在这里插入图片描述

是不是很简单

在这里插入图片描述

我的上帝啊,瞧瞧那document上面一排是什么,哇,是iframe标签,看来还需要打开一个网站,这标签代表着视频是在内嵌的框架播放的,因为网页中嵌入的<Iframe></Iframe>所包含的内容与整个页面是一个整体(上面这句专业的话那肯定不是我说的),大概就是相当于另一个网站吧。直接requests肯定得不到m3u8文件了。接下来找到那个内嵌网站

2.找到内嵌网站

接下里我们就直接进入网站源码里面进行查找吧,腚眼一看

在这里插入图片描述

这不就找到了内嵌网站了吗,只是斜杠在处理的时候需要换一下,这里注意,处理的时候需要转义一下,单斜杠替换的时候不会处理掉的。

3.进入内嵌网站拿到m3u8

进入视频网站,可以知道,整个屏幕都是视频,我们怎么查看源码呢,同样是按F12,在sources里面打开网址路径对应的文件夹下打开,可以找到m3u8文件

在这里插入图片描述

什么,你说你看到上面也有一个m3u8文件

在这里插入图片描述

我才不会跟你说我写这一篇博客的时候才看到,没事,大不了多一个步骤。刷新页面,可以看到这个m3u8的预览
在这里插入图片描述

很明显,我们需要通过这个m3u8文件拿到完整的m3u8文件,

当拿到完整的m3u8文件之后,我们也可以通过这个网站先看一下完整的m3u8文件预览
在这里插入图片描述

对味了,啊,那居然还有加密,可以看到是AES-128加密,没事,我们到时候把每一集的m3u8文件这个对应URI拿到并且得到key就行,从uri里面得到密码直接解密,AES的原理详情我也不是很清楚,但好像解密步骤就那回事,需要看看是AES的什么类型。这些可以拿到每一集的m3u8文件了,并且边解密边写入文件

4.将ts文件写入对应的文件夹

这里就利用到我们需要的协程了,直接贴完整代码吧,首先是总体思路

在这里插入图片描述

总体思路就是这样,其他的都是一些细枝末节了,接下来也就是看协程怎么运用了。至于ts的合并,就放在下边,也不多赘述了,主要就是利用顺序来合并,自动合并的话顺序有些问题。

import os
import glob

for t in range(0,14):
    if t < 10:
        x = glob.glob0(f"./第0{t}集/*.ts")
        print(len(x))
        with open(f"第0{t}集总合并.ts", "ab") as g:
            for i in range(0, len(x) - 1):
                with open(f"./第0{t}集/{i}.ts", 'rb') as xx:
                    g.write(xx.read())
    else:
        x = glob.glob(f"./第{t}集/*.ts")
        print(len(x))
        with open(f"第{t}集总合并.ts", "ab") as g:
            for i in range(0, len(x) - 1):
                with open(f"./第{t}集/{i}.ts", 'rb') as xx:
                    g.write(xx.read())

二.完整代码

import os
import random
import aiofiles
from lxml import html
import requests
import asyncio
import aiohttp
import re
import datetime
from Crypto.Cipher import AES
import time

etree = html.etree
episode_list = []
html_list = []
headers = {
    'User-Agent': 'Mozilla / 5.0(WindowsNT10.0;Win64;x64) AppleWebKit / 537.36(KHTML, likeGecko) Chrome / '
                  '91.0.4472.124Safari / 537.36 '}

def get_key(name):
    with open(f"{name}的m3u8.txt", "r") as f:
        for i in f:
            # 使用正则来得到密码连接
            x = re.compile(r'URI="(.*?)"')
            t = x.findall(i)
            if len(t):
                # print(t[0])
                # 直接把密码原文得到,byte类型
                resp = requests.get(t[0], headers=headers).content
                # print(resp)
                return resp


# 正式写入文件夹里面的ts文件
# 必须得用n来排序,要不然顺序乱了
async def download_ts_descrpt(name, line, n, key,sem):
#这里就是调用同时并发协程的数量
    async with sem:
        # aiohttp.ClientSession() as session:可以放在上一个函数打开文件上面,因为我的操作相当于每一次运用协程
        # 都创建一个连接池,不建议我这样的做法
        async with aiohttp.ClientSession() as session:
            async with session.get(url=line, headers=headers) as f:
            #需要利用aiofiles来打开文件,也是异步操作
                async with aiofiles.open(f"{name}/{n}.ts", 'wb') as x:
                    t = await f.content.read()                      #使用协会时候需要使用read()
                    # 利用密钥进行解密操作,CBC为猜的,偏移量数量设置跟解出来的密码个数相同
                    aes = AES.new(key=key, IV=b'0000000000000000', mode=AES.MODE_CBC)
                    await x.write(aes.decrypt(t))


# 创建保存ts的文件夹,从m真实的m3u8文件里面读取ts
async def create_ts_encrpt(name, key):
    count = 0
    #使用count来计数,这样不会使协程写入的时候,ts文件顺序变乱
    tasks = []
    #判断是否有文件夹 ,如果有就跳过,没有就创建
    if not os.path.exists(f'{name}'):
        os.makedirs(f"{name}")
    #这里注意,协程里面再次创建协程,这就是进行异步操作,把每一步ts文件添加到协程任务列表
    #sem = asyncio.Semaphore(5)代表允许的并发协程数量为5,这里不建议设多,也不建议删掉,太多
    # 的话容易信号灯超时,也就是aiohttp.ClientSession创建的连接池里面请求,网站会响应不过来
    sem = asyncio.Semaphore(5)
    async with aiofiles.open(f"./{name}的m3u8.txt", mode='r') as f:
        async for line1 in f:
            if line1.startswith("#"):
                continue
            else:
                line1 = line1.strip()
                # print(line1)
                # 添加另外一个协程事件进行操作,这就是写入加密得ts
                tasks.append(download_ts_descrpt(name, line1,  count, key,sem))
                count += 1
        await asyncio.wait(tasks)


# 把第二层m3u8文件写下来
async def download_m3u82(url, name):
    async with aiohttp.ClientSession() as session:
        async with session.get(url, headers=headers) as f:
            f1 = await f.content.read()
            # 因为只能写入content所以不需要decode
            with open(f"{name}的m3u8.txt", 'wb') as f2:
                f2.write(f1)
                # 这里得到每一个密钥然后传入下一个协程函数。这里调用了get_key函数得到每一个密钥
                key_encrpt = get_key(name)
                await create_ts_encrpt(name, key_encrpt)


# 把第一层m3u8文件写下来,并把第二层m3u8的文件读出来
async def download_m3u8(url, name):
    async with aiohttp.ClientSession() as session:
        async with session.get(url, headers=headers) as f:
            f1 = await f.content.read()
            # 因为只能写入content所以不需要decode
            with open(f"{name}.txt", 'wb') as f2:
                f2.write(f1)
            #这里就是读出来
            with open(f"{name}.txt", 'r', encoding='utf-8') as y:
                for line in y:
                    if line.startswith("#"):
                        continue
                    else:
                        # 去掉空白和换行符
                        line = line.strip()
                        line = "https://vod2.buycar5.cn" + line
                        # print(line)
                        await download_m3u82(line, name)  #又传入下一个函数


# 在视频里面把每一个m3u8文件拿出来
async def get_html_m3u8(url, name):
    async with aiohttp.ClientSession() as session:
        async with session.get(url, headers=headers) as f:
            html2 = await f.content.read()
            html2 = html2.decode('utf-8')
            # print(html2)
            html2_m3u8 = re.findall(r'var main = "(.*?)";', html2)[0]
            html2_m3u8 = 'https://vod2.buycar5.cn' + html2_m3u8
            await download_m3u8(html2_m3u8, name)


# 从每一集的链接里面把视频的链接提取出来
async def get_html_m3u8_pre(url, name):
    async with aiohttp.ClientSession() as session:          #实例化session通过session来get
        async with session.get(url, headers=headers) as f:
            html1 = await f.content.read()
            html1 = html1.decode('utf-8')
            # print(html1),第一集吗,格式跟其他集数有所不同可以理解
            if name == "第01集":
                html1_m3u8 = re.findall(r'"link_pre":"","url":"(.*?)","url_next"', html1)[0].replace("\\", "")      #这里就是双斜杠,文中提到的需要转义的地方
            else:
                html1_m3u8 = re.findall(r'.html","url":"(.*?)","url_next"', html1)[0].replace("\\", "")
            # print(html1_m3u8)
            await get_html_m3u8(html1_m3u8, name)           #使用await调用协程函数

#得到每一集的初始链接
async def get_html(url):
    tasks = []  #因为有12集,从主页面的li下面的得到的每一集的链接需要进行拼接,并且将12集用协程来完成,创建协程列表
    resp = requests.get(url, headers=headers).content.decode('utf-8')
    resp = etree.HTML(resp)
    resp_list = resp.xpath('//ul[@class="stui-content__playlist clearfix"]')
    for res in resp_list:
        episode = res.xpath('./li/a/@href')                         #得到名字和网址,此时返回的都是列表,注意
        episode_name = res.xpath('./li/a/text()')
    # print(episode_name)
    for i in range(0, len(episode)):
        episode[i] = "https://www.autonicdq.com" + episode[i]
        tasks.append(get_html_m3u8_pre(episode[i], episode_name[i]))  #逐项添加协程任务,此时协程任务的函数写在这里面
    await asyncio.wait(tasks)                                           #执行协程函数


# 得到主页面的的每一集的链接


if __name__ == '__main__':
    t1 = datetime.datetime.now()   #记录开始时间
    url_main = "https://www.autonicdq.com/voddetail/4002.html"
    loop = asyncio.get_event_loop()         #协程的开始实例化一个loop对象
    loop.run_until_complete(get_html(url_main))     #在loop对象添加要完成的任务
    t2 = datetime.datetime.now()    #记录结束时间的
    print(t2 - t1)

速度比正常快了很多,虽然把协程并发数量进行了限制,但不限制,网站遭不住
注:该文章仅供学习交流

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值