三.大致思路

        网上有很多讲爬虫一般思路的。我认为爬虫最关键的就是三步,分别是获取资源url,请求url和保存资源。网页上的视频很多都是m3u8的,然后分割成很多ts文件,这些ts文件就是一些视频的片段,如果我们把所有的ts文件都下载下来然后合在一起就拼接成了一部完整的视频。当然这其中会存在一些困难,比如说,手动获取所有的资源url,这是不现实的。我们需要找到获取所有ts文件地址的方法,然后利用这个方法,对更衣人偶总共十二集进行爬取。

三.获取资源url

        我们先打开这个网页http://www.hbhist.com/play/64059-1-1.html。同时鼠标右键或者f12打开浏览器开发工具。我们能够看到很多的ts文件,这些就是视频的片段

python 检测网页视频是否暂停_爬虫

随便点击一个你就能看到标头中,他的url,比如这样:

python 检测网页视频是否暂停_开发语言_02

你可能会想,这些url似乎都很有规律,我完全可以按照规律推算出所有url,没错,对于这部视频来说,确实是这样的,但是,很多情况下,对于同一个视频的ts片段url,他们可能大相径庭,完全没有规律可言,所以这并不是我们想要的方法。

        在浏览器开发工具中,按下ctrl+f,我们输入m3u8进行搜索,我们可以看到一个叫做index.m3u8的文件。

        

python 检测网页视频是否暂停_爬虫_03

这个文件是关键,我们打开他的响应,发现所有ts的地址都在这里面了:

python 检测网页视频是否暂停_开发语言_04

 

        这就非常好了,这样就不用我们手动一个一个去找ts文件的url了。但是我们忽然又发现一个问题,难道每一集的m3u8这个文件都要我们手动去找么?虽然工作量不算很大,但还是不妥。而且每一集的这个m3u8文件的url是没有规律的。 

        我们回到我们的页面源代码中看看,搜索m3u8。

python 检测网页视频是否暂停_爬虫_05

        我们发现这其中正好有我们前面m3u8的url。这下我们的问题基本算是解决了。因为每一集的地址都是很有规律的,比如说第一集:http://www.hbhist.com/play/64059-1-1.html,第二集:http://www.hbhist.com/play/64059-1-2.html。我们只要对这些地址发送请求获取源代码,然后用正则表达式获取m3u8文件的url地址,再对m3u8的url请求,利用正则表达式获取所有ts文件的url,这样就可以把1到12集的动漫全部下载下来了。

四.请求资源

        我们先写一个叫做html的函数,这个函数的功能就是给定每一集的网址,然后从网页源代码中获取到m3u8的url。

#存放处理好的m3u8url地址
m3u8list=[]

def html(htmlurl):
    """
    从页面源代码里提取视频url
    :return:
    """
    response = requests.get(url=htmlurl).text
    #正则表达式匹配
    parttren = re.compile(r"https.*?m3u8")
    #findall返回值是列表,取列表中的第一个
    result = parttren.findall(response)[0]

    # 分割一下url因为有太多转义符
    #这个url要好好处理一下,不然待会儿请求会报错
    result = result.split(r"/")
    result = result[0] + result[2] + result[3] + result[4] + result[5]
    result = result.replace('\\', '//')
    m3u8list.append(result)

        当然我写的不是很漂亮,大家可以自行改进。总之,这样我们就可以把获取到的所有m3u8的地址存放到一个列表中。接下来我们只要对 列表中的每一个地址进行请求,再用正则表达式进行匹配,就可以获取到每一集的所有ts地址了,所以我们再写一个函数。

def getTslist(url1):
    """
    传入一个处理好的url
    :param url1:
    :return: 返回所有ts的url,一个列表
    """
    # 访问该结果,获取所有ts的地址
    tsUrls = requests.get(url=url1, headers=header).text
    partern = re.compile(r"https.*?\.ts")
    # 返回ts列表
    result = partern.findall(tsUrls)
    for i in result:
        print(i)
    return result

这个函数的功能就是对一个m3u8的地址进行请求,然后利用正则表达式,匹配所有的ts的地址,返回一个列表(我在函数中还打印了一遍看看效果)。

        接下来就要对所有ts地址进行请求了,我们考虑使用多线程提高效率。因为有12集,所以我每一集用了一个线程,这里我使用的是类的写法,大家也可以写成一个函数的形式。

class mythread(threading.Thread):
    def __init__(self,tslist:"传入一个ts列表",name:"名字",pos):
        super().__init__()
        self.tslist=tslist
        self.name=name
        self.pos=pos

    def run(self) -> None:
        # 使用tqdm创建一个进度条,总长度为tslist的长度
        with tqdm(total=len(self.tslist), desc=f"{self.name}下载进度",position=self.pos) as pbar:
            # 用追加模式,防止覆盖
            with open(f"{self.name}", 'ab') as f:
                session = requests.Session()
                for i in self.tslist:
                    try:
                        response = session.get(url=i, headers=header, timeout=2)
                        f.write(response.content)

                    except:
                        #print("下载失败")
                        for j in range(0, 100):
                            try:
                                response = requests.get(url=i, headers=header, timeout=5)
                            except:
                                pass
                            else:
                                if response.status_code == 200:
                                    f.write(response.content)
                                    pbar.update(1)  # 更新进度条
                                    break
                    else:
                        #print("下载成功")
                        pbar.update(1)  # 更新进度条

                session.close()

        在这个自定义的类中,构造函数需要传入三个参数,一个是包含所有ts地址的列表,然后是文件的名称,最后一个pos是进度条的位置。因为有大量的请求,所以我觉得用session比单纯的requests.get要好一些,然后还用了异常捕获,如果请求超时就重新请求。

        使用上面这些函数,我们就能对所有的12集进行下载。

五.ts解密

        如果你仔细看了我上面的内容,并且成功使用了这些函数完成了任务,那么恭喜你离成功又进了一步,你可能会问,不是已经完成了么?我已经成功下载下来了啊。因为当你满怀开心打开这些下载下来的文件的时候,就会出现这种情况

python 检测网页视频是否暂停_开发语言_06

根本无法播放,这是怎么回事?原因在于:我们下载下来的ts文件都是经过了加密的(有些网站的ts文件没有加密)。 

        那我们该怎么办?

        还记得我们获取的m3u8文件么?里面似乎还有一些内容

 

python 检测网页视频是否暂停_python 检测网页视频是否暂停_07

这里指明了加密的方式是AES,而且给出了 密钥的地址是enc.key。那么密钥到底是多少呢?我们结合m3u8的url地址看看https://hnzy.bfvvs.com/play/1aKOxzJa/index.m3u8,把最后的index.m3u8替换为enc.key,然后粘贴到浏览器中尝试访问,得到了一个密钥文件

python 检测网页视频是否暂停_开发语言_08

如果你尝试记事本打开这个文件,是一个乱码,不过不要紧,因为它就是我们需要的密钥。 

那么我们如何进行解密呢?还好,python中有相关的库pycryptodome,我们利用pip命令可以下载它然后就可以使用了。

import requests
from Crypto.Cipher import AES
import re
header={
    "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0"
}


def getkey(m3u8url):
    """
    这个函数用来获取解密用的密钥,需要传入m3u8的url
    :return:返回密钥的信息,用于解密
    """
    response=requests.get(m3u8url,headers=header).text
    pattern=re.compile(r'URI="(.*?)"')
    result=pattern.findall(response)
    #获取密钥字段
    key=result[0]
    #获取密钥url
    keyurl=m3u8url.replace("index.m3u8",key)
    return requests.get(url=keyurl,headers=header).content


def breakts(m3u8url,tspath,savepath):
    """
    利用密钥解密ts文件
    :param m3u8url:
    :param tspath: 加密的ts文件的路径
    :param savepath: 解密后的保存路径
    :return:
    """
    key=getkey(m3u8url)
    aes = AES.new(key, AES.MODE_CBC, b'0000000000000000')
    #读取加密的ts文件
    with open(tspath, 'rb') as f:
        video = f.read()

    with open(savepath, 'ab+') as f:
        f.write(aes.decrypt(video))


if __name__=="__main__":
    breakts("https://hnzy.bfvvs.com/play/1aKOxzJa/index.m3u8","D:\zhengyanyydsDownLoads\plist2.ts","测试更衣.ts")

        在这段代码里面,我写了两个函数,第一个是getkey函数,他的功能是根据传入的m3u8的url,获取到最终的密钥。而breakts函数就是利用密钥来进行解密了,使用了Crypto.Cipher库中的AES.new方法来创建一个AES加密对象,传入了三个参数,

  • key: 这是用于加密和解密的密钥。在你的代码中,这个密钥是通过get_key函数从M3U8文件中获取的。
  • AES.MODE_CBC: 这个参数指定了AES加密模式,其中CBC代表Cipher Block Chaining。在这种模式下,每个明文块在加密之前都会与前一个密文块进行异或操作,从而引入了一定的反馈机制,增强了安全性。
  • AES_KEY_PADDING: 这是用于填充密钥的值。在这里,它被设置为b'0000000000000000',表示16个字节的零填充。填充是为了使密钥长度达到AES算法规定的长度,通常是128、192或256位。在这里,填充的目的是确保密钥长度是符合AES规范的。

        最后我们使用decrypt方法进行了解密,传入的参数就是我们加密的ts文件的路径,并将解密后的内容写到一个新的文件中去。

        至此,我们的所有工作就全部完成了。

六.全部代码

        代码有两个文件,一个主要是用来下载的,一个就是用来解密的,下载那个是主文件。

import requests
import re
import threading
from tqdm import tqdm
from breakTs import breakts
import os
#总共有12集
html1="http://www.hbhist.com/play/64059-1-1.html"
html2="http://www.hbhist.com/play/64059-1-2.html"



header={
    "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0"
}
class mythread(threading.Thread):
    def __init__(self,tslist:"传入一个ts列表",name:"名字",pos):
        super().__init__()
        self.tslist=tslist
        self.name=name
        self.pos=pos

    def run(self) -> None:
        # 使用tqdm创建一个进度条,总长度为tslist的长度
        with tqdm(total=len(self.tslist), desc=f"{self.name}下载进度",position=self.pos) as pbar:
            # 用追加模式,防止覆盖
            with open(f"{self.name}", 'ab') as f:
                session = requests.Session()
                for i in self.tslist:
                    try:
                        response = session.get(url=i, headers=header, timeout=2)
                        f.write(response.content)

                    except:
                        #print("下载失败")
                        for j in range(0, 100):
                            try:
                                response = requests.get(url=i, headers=header, timeout=5)
                            except:
                                pass
                            else:
                                if response.status_code == 200:
                                    f.write(response.content)
                                    pbar.update(1)  # 更新进度条
                                    break
                    else:
                        #print("下载成功")
                        pbar.update(1)  # 更新进度条

                session.close()


def getTslist(url1):
    """
    传入一个处理好的url
    :param url1:
    :return: 返回所有ts的url,一个列表
    """
    # 访问该结果,获取所有ts的地址
    tsUrls = requests.get(url=url1, headers=header).text
    partern = re.compile(r"https.*?\.ts")
    # 返回ts列表
    result = partern.findall(tsUrls)
    for i in result:
        print(i)
    return result

#存放处理好的m3u8url地址
m3u8list=[]

def html(htmlurl):
    """
    从页面源代码里提取视频url
    :return:
    """
    response = requests.get(url=htmlurl).text
    #正则表达式匹配
    parttren = re.compile(r"https.*?m3u8")
    #findall返回值是列表,取列表中的第一个
    result = parttren.findall(response)[0]

    # 分割一下url因为有太多转义符
    #这个url要好好处理一下,不然待会儿请求会报错
    result = result.split(r"/")
    result = result[0] + result[2] + result[3] + result[4] + result[5]
    result = result.replace('\\', '//')
    m3u8list.append(result)

def removeTs():
    """
    删除所有ts文件
    :return:
    """
    current_directory=os.getcwd()
    files=os.listdir(current_directory)

    for file in files:
        if file.endswith(".ts"):
            filepath=os.path.join(current_directory,file)
            os.remove(filepath)

if __name__=="__main__":
    #从页面源代码里面提取url
    for i in range(1,13):
        html(f"http://www.hbhist.com/play/64059-1-{i}.html")
    #获取tslist
    #预备开12个线程下载,一个线程下载一集
    mythreads=[]
    num=1
    for i in m3u8list:
        tslist=getTslist(i)
        mythreads.append(mythread(tslist,f"更衣人偶第{num}集.ts",pos=num))
        num+=1
    #开始下载
    for i in mythreads:
        i.start()

    for i in mythreads:
        i.join()

    #开始解密
    num=1
    for i in tqdm(m3u8list,desc="解密完成进度"):
        breakts(i,f"更衣人偶第{num}集.ts",f"更衣人偶坠入爱河第{num}集.mp4")
        num+=1
    #删除所有ts文件
    removeTs()
    print("更衣人偶全集下载成功!")
import requests
from Crypto.Cipher import AES
import re
header={
    "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0"
}


def getkey(m3u8url):
    """
    这个函数用来获取解密用的密钥,需要传入m3u8的url
    :return:返回密钥的信息,用于解密
    """
    response=requests.get(m3u8url,headers=header).text
    pattern=re.compile(r'URI="(.*?)"')
    result=pattern.findall(response)
    #获取密钥字段
    key=result[0]
    #获取密钥url
    keyurl=m3u8url.replace("index.m3u8",key)
    return requests.get(url=keyurl,headers=header).content


def breakts(m3u8url,tspath,savepath):
    """
    利用密钥解密ts文件
    :param m3u8url:
    :param tspath: 加密的ts文件的路径
    :param savepath: 解密后的保存路径
    :return:
    """
    key=getkey(m3u8url)
    aes = AES.new(key, AES.MODE_CBC, b'0000000000000000')
    #读取加密的ts文件
    with open(tspath, 'rb') as f:
        video = f.read()

    with open(savepath, 'ab+') as f:
        f.write(aes.decrypt(video))


if __name__=="__main__":
    breakts("https://hnzy.bfvvs.com/play/1aKOxzJa/index.m3u8","D:\zhengyanyydsDownLoads\plist2.ts","测试更衣.ts")