「Python」爬虫-5.m3u8(视频)文件的处理

简介: >本文主要讲解了如何下载m3u8的视频文件到本地,加密解密,将ts文件合并为一个mp4文件三个知识点。

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第18天, 点击查看活动详情


前言

本文主要讲解了如何下载m3u8的视频文件到本地,加密解密,将ts文件合并为一个mp4文件三个知识点。

关于爬虫,欢迎先阅读一下我的前几篇文章😶‍🌫️😶‍🌫️😶‍🌫️:

「Python」爬虫-1.入门知识简介 - 掘金 (juejin.cn)

「Python」爬虫-2.xpath解析和cookie,session - 掘金 (juejin.cn)

「Python」爬虫-3.防盗链处理 - 掘金 (juejin.cn)

「Python」爬虫-4.selenium的使用 - 掘金 (juejin.cn)

参考链接:

1.【全网最全】m3u8到底是什么格式?一篇文章搞定m3u8下载 - 知乎 (zhihu.com)


视频爬取

先聊一聊视频吧,

现在大多数的视频网站采用的是流媒体传输协议,大致就是把一整个视频,比如周董的《最伟大的作品》,虽然只有几分钟,但是很有可能这整个视频并不是一起加载进来的,而是当播放完某一段的时候,再给你加载下一段的内容。

也就是将一段视频切成无数个小段,这些小段就是ts格式的视频文件。

这样做的好处是为了让用户获得更加流畅的观感体验,因为他会根据网络状况自动切换视频的清晰度,在网络状况不稳定的情况下,对保障流畅播放非常有帮助。

一个视频播放的全过程如下:

1.服务器采集编码传输视频到切片器
2.切片器对视频创建索引文件, 并且切割nts文件
3.将ts文件传输到http服务器上
4.网站/客户端根据索引文件查找http服务器上的ts文件,连续播放这n个ts文件,就可以了。

所以我们知道,索引文件非常重要,毕竟没有这个文件你就不知道每个部分的ts文件里面到底存储的是什么信息。

索引文件里面存储的是ts文件的网络url链接。视频播放网站要播放视频,首先需要拿到索引文件,按照文件中的url链接下载存储在http服务器中的ts文件。

拿到了ts文件之后,由于本身这些ts文件就是原视频中的一小段视频,将所有ts文件下载之后并按照顺序播放就可以凑成整个视频了。

m3u8文件就是索引文件

也就是说,如果在观看网页视频的时候,能够找到加载该视频的m3u8文件,就可以下载相关视频了!

M3U8其实是M3U文件经过UTF-8的编码后的文件

M3U8文件的格式如下图所示:

image.png
其中#EXTINF:10.000代表的是每一个切片的播放时间,
而下面的cFN8034360001.ts实际上是一个url的一部分,在请求视频的时候可能需要拼接字符串从而拿到真正的视频地址。

eg:https://www.baidu.com/cFNxxxxxxx

image.png

一般的视频处理过程

用户上传 -> 转码(把视频做处理 ,2k,1080,标清) -> 切片处理(把单个的文件进行拆分)60

用户在进行拉动进度条的时候需要一个文件记录:

1.视频的播放顺序

2.视频存放的路径 -
M3U (类似于txt,json ) => 文本

抓取视频步骤

1.找到M3U8(各种手段)

2.通过M3U8下载到ts文件

3.
可以通过各种手段(不仅是编程手段:pr) 把ts文件合并为一个mp4文件

下面以下载https://91kanju.com/ 网站的一个电视《哲仁王后》为例。

这个网站的页面源代码中直接就有m3u8的url,所以这里直接采用正则表达式进行提取即可。

obj = re.compile(r"url: '(?P<url>.*?)',", re.S)

m3u8_url = obj.search(resp.text).group("url")

完整代码如下:

"""
流程:
    1.拿到html的页面源代码
    2.从源代码中提取到m3u8的url
    3.下载m3u8
    4.读取m3u8,下载视频
    5.合并视频
"""
 import requests
 import re

 headers = {
     "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36 Edg/94.0.992.50"
 }
 obj = re.compile(r"url: '(?P<url>.*?)',", re.S)  # 用来提取m3u8的url地址
 url = "https://91kanju.com/vod-play/54812-1-1.html"

 resp = requests.get(url, headers=headers)
 m3u8_url = obj.search(resp.text).group("url")  # 拿到m3u8的地址

 resp.close()

 # 下载m3u8 resp2 = requests.get(m3u8_url, headers=headers)
 with open("哲仁王后.m3u8", mode="wb") as f:
     f.write(resp2.content)

 resp2.close()
 print("下载完毕")

通过上述代码已经下载到了当前视频的M3U8文件,接下来就是对该文件进行进一步处理了。

image.png

从之前m3u8文件的额格式来看,如果视频没有被加密的话,那么就直接提取以.ts结尾的就行,然后做一个url的拼接就可以获取到一段一段的ts文件了。

具体代码如下:

import requests
import os

n = 1
with open("哲仁王后.m3u8",mode="r", encoding="utf-8") as f:
    for line in f:
        line = line.strip() # 先除去空格,空白,换行符
        if line.startswith("#"):    # 如果以#开头,则舍去
            continue
        # 下载视频片段

        resp3 = requests.get(line)
        f = open(f"{n}.ts",mode="wb")
        os.chdir("D:/python/workSpace/study_demo/base/book_demos/demo/vedio/v_ts")
        f.write(resp3.content)
        f.close()
        resp3.close()
        n += 1

超复杂版本

通过上面的介绍,你一定会觉得上面的内容还是比较的水,并没有什么挑战性。

接下来将模拟从iframe中拿到m3u8文件,然后加密的文件进行解密,最后将所有的ts合并为一个MP4文件。

目标网址https://www.91kanju.com/

image.png

这个网址不大稳定,所以想要自己实战可能需要看点运气(bushi

整体思路:

  1. 拿到主页面的源代码,找到iframe
  2. 从iframe源代码中拿到m3u8文件
  3. 下载第一层m3u8文件 -> 下载第二层m3u8文件(视频存放路径)
  4. 下载视频
  5. 下载密钥,进行解密操作
  6. 合并所有ts文件为一个mp4文件

获取m3u8:

直接在页面源代码中搜iframe标签,一般情况下可以看到一个.m3u8为后缀的链接。

image.png

获取url的参考代码如下:

def get_first_m3u8_url(iframe_src):
    resp = requests.get(url)
    obj = re.compile(r'var main = "(?P<m3u8_url>.*?)"', re.S)
    m3u8_url = obj.search(resp.text).group("m3u8_url")
    return m3u8_url

下载m3u8文件的代码参考如下:

def download_m3u8_file(url, name):
    resp = requests.get(url)
    with open(name, mode="wb") as f:
        f.write(resp.content)

异步:

由于考虑到爬虫的效率问题,这里采用异步下载的方式,下载所有的ts文件。

异步一般在函数定义的前面加上async即可。然后套用模板即可:

async def xxx(url , session):
    async with session.get(url) as resp:
        async with aiofiles.open(f"vedio/{name}", mode="wb") as f:
            await f.write(xx)

异步下载ts代码参考:

async def download_ts(url, name, session):
    async with session.get(url) as resp:
        async with aiofiles.open(f"vedio/{name}", mode="wb") as f:
            await f.write(await resp.content.read())  # 把下载的内容写入到文件中
    print(f"{name}下载完毕")

创建多个异步任务模板:

tasks = [] # 任务池子
task = asyncio.create_task() # 创建异步任务
await asyncio.wait(tasks) # 等待任务结束

完整代码:

async def aio_download(up_url):  # https://boba.52kuyun.com/20170906/M0h219zV/hls/
    tasks = []
    async with aiohttp.ClientSession() as session:  # 提前准备好session
        async with aiofiles.open("second_m3u8.txt", mode="r", encoding="utf-8") as f:
            async for line in f:
                if line.startswith("#"):
                    continue
                # line就是 xxxx.ts
                line = line.strip()  # 除去空格
                # 拼接真正的ts路径
                ts_url = up_url + line
                task = asyncio.create_task(download_ts(ts_url, line, session))  # 创建任务
                tasks.append(task)

            await asyncio.wait(tasks)  # 等待任务结束

解密:

这里定义对单个ts文件进行解密的函数:

def dec_ts(name, key):
    aes = AES.new(key=key, IV=b"0000000000000000", mode=AES.MODE_CBC)  
    # b"xx"是以字节的形式打开,16个0的长度与key的长度保持一致
    async with aiofiles.open(f"vedio/{name}", mode="rb") as f1, \
            aiofiles.open(f"vedio/temp_{name}", mode="wb") as f2:
        bs = await f1.read()  # 从源文件读取内容
        await f2.write(aes.decrypt(bs))  # 把解密好的内容写入文件
    print(f"{name}处理完毕")

将所有的ts文件都进行解密操作,并开启异步任务。

def aio_dec(key):
    # 解密
    tasks = []
    async with aiofiles.open("second_m3u8.txt", mode="r", encoding="utf-8") as f:
        async for line in f:
            if line.startswith("#"):
                continue
            line = line.strip()
            # 开始创建一步任务
            task = asyncio.create_task(dec_ts(line, key))
            tasks.append(task)
        await asyncio.wait(tasks)

合并ts:

合并ts可以采用命令行的方式,

如果是win系统直接
copy / b 1.ts+2.ts+3.ts xxx.mp4命令即可

如果是mac系统,则使用cat 1.ts 2.ts 3.ts > xxx.mp4即可。

具体代码如下:

def merge_ts():
    # mac:cat 1.ts 2.ts 3.ts > xxx.mp4
    # windows: copy / b 1.ts+2.ts+3.ts xxx.mp4
    lst = []
    with open("second_m3u8.txt", mode="r", encoding="utf-8") as f:
        for line in f:
            if line.startswith("#"):
                continue
            line = line.strip()
            lst.append(f"vedio/temp_{line}")

    # s = " ".join(lst)
    # os.system(f"cat {s} > movie.mp4") 苹果
    # windows 
    print("over")

完整代码如下:

import requests
import os
import re
from bs4 import BeautifulSoup
import asyncio
import aiohttp
import aiofiles
from Crypto.Cipher import AES


def get_iframe_src(url):
    resp = requests.get(url)
    main_page = BeautifulSoup(resp.text, "html.parser")
    src = main_page.find("iframe").get("src")
    # return src
    return "https://boba.52kuyun.com/share/xfPs9NPHvYGhNzFp"  # 测试使用


def get_first_m3u8_url(iframe_src):
    resp = requests.get(url)
    obj = re.compile(r'var main = "(?P<m3u8_url>.*?)"', re.S)
    m3u8_url = obj.search(resp.text).group("m3u8_url")
    return m3u8_url


def download_m3u8_file(url, name):
    resp = requests.get(url)
    with open(name, mode="wb") as f:
        f.write(resp.content)


async def download_ts(url, name, session):
    async with session.get(url) as resp:
        async with aiofiles.open(f"vedio/{name}", mode="wb") as f:
            await f.write(await resp.content.read())  # 把下载的内容写入到文件中
    print(f"{name}下载完毕")


async def aio_download(up_url):  # https://boba.52kuyun.com/20170906/M0h219zV/hls/
    tasks = []
    async with aiohttp.ClientSession() as session:  # 提前准备好session
        async with aiofiles.open("second_m3u8.txt", mode="r", encoding="utf-8") as f:
            async for line in f:
                if line.startswith("#"):
                    continue
                # line就是 xxxx.ts
                line = line.strip()  # 除去空格
                # 拼接真正的ts路径
                ts_url = up_url + line
                task = asyncio.create_task(download_ts(ts_url, line, session))  # 创建任务
                tasks.append(task)

            await asyncio.wait(tasks)  # 等待任务结束


def get_key(key_url):
    resp = requests.get(url)
    return resp.text


def dec_ts(name, key):
    aes = AES.new(key=key, IV=b"0000000000000000", mode=AES.MODE_CBC)  # b"xx"是以字节的形式打开,16个0的长度与key的长度保持一致
    async with aiofiles.open(f"vedio/{name}", mode="rb") as f1, \
            aiofiles.open(f"vedio/temp_{name}", mode="wb") as f2:
        bs = await f1.read()  # 从源文件读取内容
        await f2.write(aes.decrypt(bs))  # 把解密好的内容写入文件
    print(f"{name}处理完毕")


def aio_dec(key):
    # 解密
    tasks = []
    async with aiofiles.open("second_m3u8.txt", mode="r", encoding="utf-8") as f:
        async for line in f:
            if line.startswith("#"):
                continue
            line = line.strip()
            # 开始创建一步任务
            task = asyncio.create_task(dec_ts(line, key))
            tasks.append(task)
        await asyncio.wait(tasks)


def merge_ts():
    # mac:cat 1.ts 2.ts 3.ts > xxx.mp4
    # windows: copy / b 1.ts+2.ts+3.ts xxx.mp4
    lst = []
    with open("second_m3u8.txt", mode="r", encoding="utf-8") as f:
        for line in f:
            if line.startswith("#"):
                continue
            line = line.strip()
            lst.append(f"vedio/temp_{line}")

    # s = " ".join(lst)
    # os.system(f"cat {s} > movie.mp4") 苹果
    # windows 
    print("over")


def main(url):
    # 1.拿到主页面的页面源代码,找到iframe对应的url
    # iframe_src = https://boba.52kuyun.com/share/xfPs9NPHvYGhNzFp
    iframe_src = get_iframe_src(url)
    # 2.拿到第一层的m3u8文件的下载地址
    # first_m3u8_url = /20170906/M0h219zV/index.m3u8?sign=548ae366a075f0f9e7c76af215aa18e1
    first_m3u8_url = get_first_m3u8_url(iframe_src)
    # 通过iframe_src拿到iframe的域名 => iframe_domain = https://boba.52kuyun.com
    iframe_domain = iframe_src.split("/share")[0]
    # 拼接出真正的m3u8的下载路径 => https://boba.52kuyun.com/20170906/M0h219zV/index.m3u8?....
    first_m3u8_url = iframe_domain + first_m3u8_url
    # 3.1下载第一层m3u8文件
    # 处理之后得到 => hls/index.m3u8
    download_m3u8_file(first_m3u8_url, "first_m3u8.txt")
    # 3.2下载第二层m3u8文件
    # 第二层的m3u8请求地址 => https://boba.52kuyun.com/20170906/M0h219zV/hls/index.m3u8
    with open("first_m3u8.txt", mode="r", encoding="utf-8") as f:
        for line in f:
            if line.startswith("#"):  # 过滤掉#开头的
                continue
            else:
                line = line.strip()  # 去掉换行符等
                # 准备拼接第二层m3u8的下载路径
                # https://boba.52kuyun.com/20170906/M0h219zV/ + hls/index.m3u8
                # https://boba.52kuyun.com/20170906/M0h219zV/hls/cFN8o343600.ts
                second_m3u8_url = first_m3u8_url.split("index.m3u8")[0] + line
                download_m3u8_file(second_m3u8_url, "second_m3u8.txt")

    # 4. 下载视频
    second_m3u8_url_up = second_m3u8_url.replace("index.m3u8", "")
    # 异步协程
    aio_download(second_m3u8_url_up)
    # 5.1 拿到密钥
    key_url = second_m3u8_url_up + "key.key"  # key.key => 正常情况下应该去m3u8文件里找
    key = get_key(key_url)
    # 5.2 解密
    asyncio.run(aio_dec(key))

    # 6.合并
    merge_ts()


if __name__ == '__main__':
    url = "https://www.91kanju.com/vod-play/541-2-1.html"
    main(url)

本次的文章就暂且码到这儿了⛷️⛷️⛷️,如果对你有帮助的话,就点个,几天后继续更新~

01617692.png

往期好文推荐🪶

「MongoDB」Win10版安装教程

「Python」数字推盘游戏

「Python」sklearn第一弹-标准化和非线性转化

「Python」turtle绘制图形🎈

「Python」Pandas-DataFrame的相关操作二

相关文章
|
11天前
|
机器学习/深度学习 存储 算法
解锁文件共享软件背后基于 Python 的二叉搜索树算法密码
文件共享软件在数字化时代扮演着连接全球用户、促进知识与数据交流的重要角色。二叉搜索树作为一种高效的数据结构,通过有序存储和快速检索文件,极大提升了文件共享平台的性能。它依据文件名或时间戳等关键属性排序,支持高效插入、删除和查找操作,显著优化用户体验。本文还展示了用Python实现的简单二叉搜索树代码,帮助理解其工作原理,并展望了该算法在分布式计算和机器学习领域的未来应用前景。
|
1天前
|
数据采集 存储 数据挖掘
深入剖析 Python 爬虫:淘宝商品详情数据抓取
深入剖析 Python 爬虫:淘宝商品详情数据抓取
|
4天前
|
存储 数据采集 数据库
Python爬虫实战:股票分时数据抓取与存储
Python爬虫实战:股票分时数据抓取与存储
|
21天前
|
监控 网络安全 开发者
Python中的Paramiko与FTP文件夹及文件检测技巧
通过使用 Paramiko 和 FTP 库,开发者可以方便地检测远程服务器上的文件和文件夹是否存在。Paramiko 提供了通过 SSH 协议进行远程文件管理的能力,而 `ftplib` 则提供了通过 FTP 协议进行文件传输和管理的功能。通过理解和应用这些工具,您可以更加高效地管理和监控远程服务器上的文件系统。
51 20
|
27天前
|
存储 数据采集 数据处理
如何在Python中高效地读写大型文件?
大家好,我是V哥。上一篇介绍了Python文件读写操作,今天聊聊如何高效处理大型文件。主要方法包括:逐行读取、分块读取、内存映射(mmap)、pandas分块处理CSV、numpy处理二进制文件、itertools迭代处理及linecache逐行读取。这些方法能有效节省内存,提升效率。关注威哥爱编程,学习更多Python技巧。
|
28天前
|
存储 JSON 对象存储
如何使用 Python 进行文件读写操作?
大家好,我是V哥。本文介绍Python中文件读写操作的方法,包括文件读取、写入、追加、二进制模式、JSON、CSV和Pandas模块的使用,以及对象序列化与反序列化。通过这些方法,你可以根据不同的文件类型和需求,灵活选择合适的方式进行操作。希望对正在学习Python的小伙伴们有所帮助。欢迎关注威哥爱编程,全栈路上我们并肩前行。
|
30天前
|
数据采集 JSON 数据格式
Python爬虫:京东商品评论内容
京东商品评论接口为商家和消费者提供了重要工具。商家可分析评论优化产品,消费者则依赖评论做出购买决策。该接口通过HTTP请求获取评论内容、时间、点赞数等数据,支持分页和筛选好评、中评、差评。Python示例代码展示了如何调用接口并处理返回的JSON数据。应用场景包括产品优化、消费者决策辅助、市场竞争分析及舆情监测。
|
1月前
|
存储 算法 Serverless
剖析文件共享工具背后的Python哈希表算法奥秘
在数字化时代,文件共享工具不可或缺。哈希表算法通过将文件名或哈希值映射到存储位置,实现快速检索与高效管理。Python中的哈希表可用于创建简易文件索引,支持快速插入和查找文件路径。哈希表不仅提升了文件定位速度,还优化了存储管理和多节点数据一致性,确保文件共享工具高效运行,满足多用户并发需求,推动文件共享领域向更高效、便捷的方向发展。
|
1月前
|
数据采集 供应链 API
Python爬虫与1688图片搜索API接口:深度解析与显著收益
在电子商务领域,数据是驱动业务决策的核心。阿里巴巴旗下的1688平台作为全球领先的B2B市场,提供了丰富的API接口,特别是图片搜索API(`item_search_img`),允许开发者通过上传图片搜索相似商品。本文介绍如何结合Python爬虫技术高效利用该接口,提升搜索效率和用户体验,助力企业实现自动化商品搜索、库存管理优化、竞品监控与定价策略调整等,显著提高运营效率和市场竞争力。
89 3
|
2月前
|
数据采集 存储 缓存
如何使用缓存技术提升Python爬虫效率
如何使用缓存技术提升Python爬虫效率

推荐镜像

更多