网吧电影 过期 俩层m3u8 爬取 合并 检验

# 针对网吧电影的分析:  M3U8 在页面源代码中
# 1. 拿到视频页的页面源代码。
# 2. 从视频页的页面源代码中找到对应的 iframe ,提取到 iframe 里面的src
# 3. 请求到url对应的页面源代码。在该页面中解析出真正的M3U8文件地址
# 4. 下载第一层M3U8.从第一层M3U8中解析出第二层的地址
# 5. 下载第二层M3U8.从第二层M3U8中解析出每一个TS文件的路径,启动协程任务
# 6. 对TS文件进行解密操作:先拿到key
# 7. 对TS文件进行合并,还原回mp4文件
import requests
from lxml import etree
from urllib import parse
import re
import asyncio
import aiofiles
import aiohttp
import os # 用它来执行操作系统的命令
from Crypto.Cipher import AES # AES-123 解密导的包 pip install pycryptodome


def get_page_source(url): # 好几次请求,拿到某某某的页面源代码,统一写法,你给我src,我就给你页面源代码
resp = requests.get(url)
# print(resp.text)
return resp.text


def get_iframe_src(url):
print("获取iframe的src值")
page_source = get_page_source(url)
# 1. 拿到视频页的页面源代码。
# 2. 从视频页的页面源代码中找到对应的 iframe ,提取到 iframe 里面的src
tree = etree.HTML(page_source)
src = tree.xpath("//iframe/@src")[0] # xpath 默认取到的东西是个列表 所以加个[0]
# print(url)
# print(src)
src_url = parse.urljoin(url, src)
# print(src_url)
print("成功获取iframe的src值", src_url)
return src_url


def get_first_m3u8_url(src_url):
print("获取第一层M3U8地址")
page_source = get_page_source(src_url)
# 在js里提取数据。最好用的方案:re
obj = re.compile(r'url: "(?P<m3u8_url>.*?)",', re.S) # 从页面源代码复制粘贴过来,把想要的改成 (.*?) 顺便取个名字 (?P<m3u8_url>.*?)
result = obj.search(page_source)
m3u8_url = result.group("m3u8_url")
# print(m3u8_url)
print("成功获取到第一层M3U8地址", m3u8_url)
return m3u8_url


def download_m3u8_file(first_m3u8_url):
print("下载第二层M3U8地址")
# 获取到第二层m3u8文件,并保存在硬盘中
first_m3u8 = get_page_source(first_m3u8_url)
# print(first_m3u8)
# 返回的页面源代码,字符串 解析:换行了 .split()
second_m3u8_url = first_m3u8.split()[-1]
second_m3u8_url = parse.urljoin(first_m3u8_url, second_m3u8_url)
# print(second_m3u8_url)
# 下载第二层m3u8文件 为什么要下载:一行一行去读,解码,合并,记录了文件的正确顺序
second_m3u8 = get_page_source(second_m3u8_url)
with open("second_m3u8.txt", mode="w", encoding="utf-8") as f:
f.write(second_m3u8)
print("下载第二层M3U8成功....数据保存在second_m3u8.txt 文件中...")


async def download_one(url): # url:ts文件的下载路径
# 自省 报错了,没关系,还可以正常启动起来 while 1: (for i in range(10):) try: break except:
for i in range(10): # 如果10次都不行,可能是url有问题,或者服务器挂了
try:
file_name = url.split("/")[-1]
async with aiohttp.ClientSession() as session:
async with session.get(url, timeout=90) as resp:
content = await resp.content.read()
# 先手动创建个目录,名为 电影_源_加密后
async with aiofiles.open(f"./电影_源_加密后/{file_name}", mode="wb") as f:
await f.write(content)
print(url, "下载成功!!!")
break
except: # 里面可以什么都不写
print("下载失败,出现错误", url)
await asyncio.sleep((i+1)*5) # 为了爬取的成功率,可以适当的进行睡眠


async def download_all_ts():
tasks = []
with open("second_m3u8.txt", mode="r", encoding="utf-8") as f:
for line in f: # 得到每一行数据
if line.startswith("#"): # 如果以#开头
continue # 过掉你
line = line.strip() # 必须处理, 不然下载的ts后面带?因为每一行数据后面有空格 .strip() 去除数据后面的空格
task = asyncio.create_task(download_one(line)) # 把每个任务造出来
tasks.append(task) # 把每个任务装起来
await asyncio.wait(tasks) # 等待每个任务全执行完


def get_key():
obj = re.compile(r'URI="(?P<key_url>.*?)"')
key_url = ""
with open("second_m3u8.txt", mode="r", encoding="utf-8") as f:
result = obj.search(f.read())
key_url = result.group("key_url")
# 请求到key的url,获取到真正的密钥
# key.key Prieview 返回是是字符串 或者乱码是(字节bytes) key 解密的时候希望的不是字符串,而是字节(bytes) 字符串处理成字节
key_str = get_page_source(key_url)
return key_str.encode('utf-8') # 把得到的字符串转成字节

"""
def new(key: Buffer, # 密钥
mode: AESMode, # 模式 mode=AES.MODE_CBC 直接怼够用了
iv : Buffer = ...,
IV : Buffer = ...,) # 它的偏移量
偏移量:有的m3u8会写,有的没有写,没有写的就直接怼 IV=b"0000000000000000" 16个0
"""


async def des_one(file, key): # 解密每一个ts文件,要把文件名和密钥给我
print("即将开始解密", file)
# 加密解密对象创建
aes = AES.new(key=key, IV=b"0000000000000000", mode=AES.MODE_CBC) # key 密钥 IV 偏移量 mode 模式
# aes.encrypt() # 加密
# aes.decrypt() # 解密
# 手动创建一个目录,名为 电影_源_解密后
async with aiofiles.open(f"./电影_源_加密后/{file}", mode="rb") as f1,\
aiofiles.open(f"./电影_源_解密后/{file}", mode="wb") as f2: # 读出来的东西,解密完,还要写到新文件里
# 从加密后的文件中读取出来,进行解密,保存到未加密文件中
content = await f1.read()
bs = aes.decrypt(content)
await f2.write(bs)
print("文件已经解密", file)


async def des_all_ts_file(key):
tasks = []
with open("second_m3u8.txt", mode="r", encoding="utf-8") as f:
for line in f:
if line.startswith("#"):
continue
line = line.strip()
file_name = line.split("/")[-1]
# 准备异步操作
task = asyncio.create_task(des_one(file_name, key))
tasks.append(task)
await asyncio.wait(tasks)


def merge_ts():
name_list = []
with open("second_m3u8.txt", mode="r", encoding='utf-8') as f:
for line in f:
if line.startswith("#"):
continue
line = line.strip()
file_name = line.split("/")[-1]
name_list.append(file_name)
# print(name_list)
# 切换工作目录 到 ./电影_源_解密后/
# 1.记录当前工作目录
now_dir = os.getcwd() # 拿到当前工作目录
print(now_dir)
# 2. 切换工作目录到 ./电影_源_解密后/
os.chdir("./电影_源_解密后/")
print(now_dir)
# 还是太长,分而治之
# 一次性合并100个文件
temp = []
n = 1 # 计数
for i in range(len(name_list)):
name = name_list[i]
temp.append(name)
if i != 0 and i % 100 == 0: # 每100个合并一次
# 合并
# mac 用 cat cat a.ts b.ts c.ts > xxx.mp4
# win 用 copy copy /b a.ts + b.ts + c.ts xxx.mp4
        # win 下面ts拼接
# names = "+".join(temp)
          
# os.system(f"copy /b {names} {n}.ts")

# mac 是下面的代码
names = "".join(temp)
os.system(f"cat {names} > {n}.ts") # 合并会报错,名字太长, 想方设法减少这个名字的长度 有2个方案
n += 1

temp = [] # 还原成新的待合并列表
# 把最后没有合并的进行收尾
names = "".join(temp)
os.system(f"cat {names} > {n}.ts")
n += 1
print(n) # 报错的话,看n到几

temp_2 = []
# 把所有的n进行循环
for i in range(1, n):
temp_2.append(f"{i}.ts")
names = "".join(temp_2)
os.system(f"cat {names} > movie.mp4")

# 3. 所有的操作之后,一定要把工作目录切换回来
os.chdir(now_dir)


def main():
# url = "http://www.wbdy.tv/play/30288_1_1.html" # url 过期
# src_url = get_iframe_src(url)
# # 3. 访问src_url,提取到第一层m3u8文件地址
# first_m3u8_url = get_first_m3u8_url(src_url)
# download_m3u8_file(first_m3u8_url)
# asyncio.run(download_all_ts())
# # loop = asyncio.get_event_loop()
# # loop.run_until_complete(download_all_ts())
# # 进行解密 进行解密前把上面(5行)代码注释掉
# key = get_key() # 通过key去解密
# asyncio.run(des_all_ts_file(key)) # 解密所以ts文件

# 合并ts文件 把上面的所有代码注释
merge_ts()


if __name__ == '__main__':
# main()
# print(5266/2) # 2633 ts
# 计算电影的总时长,检验是否与合并视频的时长一致
# 电影拖到最后看是几个小时,几分钟
# n = 0
# with open("second_m3u8.txt", mode="r", encoding='utf-8') as f:
# for line in f:
# if line.startswith("#EXTINF:"):
# line = line.strip()
# line = line.strip(",")
# n += float(line.split(":")[-1]) # 通过:去切,切完取-1
# print(n) # 得到电影的总时长
n = 7857
print(n / 60) # n 除以60
print(126.45 % 60) # % 余上
posted @ 2023-07-28 00:19  严永富  阅读(83)  评论(0)    收藏  举报