爬虫案例之视频爬取与合成
案例网址:https://v6.dious.cc
- 用到的知识点
- asynic,协程异步操作。
- ffmpeg,合成视频指令
- aiohttp,在协程里面发送异步请求
【一】分析
(1)分析网站
- 打开网站首页
-
打开开发者模式进行抓包
- 思路:我们已经知道每个视频都是由一个个ts文件片段组成
- 而这些ts片段的地址就存在m3u8文件中
- 所以我们现在的主要目标放在m3u8文件中
- 思路:我们已经知道每个视频都是由一个个ts文件片段组成
-
这里我们找到了第一个m3u8文件,查看其响应内容
- 我们可以发现,其响应内容中存在类似链接的地址
- 我们继续往下看到第二个m3u8文件,打开,查看其响应内容
- 我们可以清楚的看到,其响应内容中存在的就是我们一个个ts文件的链接
(2)准备爬取工作
- 本次案例使用的是asynic模块
- 不为别的,就是单纯的下载批量文件图一块!嘎嘎快!
【二】分析完就动手!
(1)导入模块
# 请求头中的headers中的User-Agent模拟
from fake_useragent import UserAgent
# os 系统模块
import os
# 正则表达式模块
import re
# 涉及到加密动作,需要解密模块(本次涉及到的是AES加密)
from Crypto.Cipher import AES
# asynic 异步协程模块
import asyncio
# 协程中的请求模块
import aiohttp
(2)初识化需要用到的全局变量
# 初始化模块
def __init__(self):
# 初始化请求头 headers
self.headers = {
'User-Agent': UserAgent().random
}
# 初始化文件夹名字(存储ts文件)
self.file_name = 'video'
# 判断文件夹是否存在(做判断)
if not os.path.exists(self.file_name):
os.mkdir(self.file_name)
(3)获取到所有的ts文件的链接
# 这里是请求第一个m3u8文件操作
async def get_page_url(self):
# 这是找到的第一个m3u8的网址
index1_url = 'https://v6.dious.cc/20220428/X2mBsQ9X/index.m3u8'
# 对上述链接发起请求
async with aiohttp.ClientSession() as session:
async with session.get(url=index1_url, headers=self.headers) as response:
# 拿到第一个m3u8文件里的内容,将下一个m3u8文件地址解析
page_text = await response.text() # 这里是文本文件,所以用response.text()
# 从获取到的文件中解析获得第二个m3u8地址
index2_url = 'https://v6.dious.cc' + page_text.split('\n')[-2] # 做字符串拼接成完整的下一个链接
# 对上述链接发起请求
async with aiohttp.ClientSession() as session:
async with session.get(url=index2_url, headers=self.headers) as response:
# 拿到第二个m3u8文件,这里面存的就是所有ts文件的链接地址
page_text = await response.text() # 这里是文本文件,所以用response.text()
# 将带有ts地址的m3u8文件写入到本地 - 为下一步的合并做准备
# 定义文件路径
file_path = f'{self.file_name}' + '\\' + 'index.m3u8'
# 打开文件夹
with open(file_path, 'w') as f:
# 循环获取取到的第二个m3u8文件中的每一个ts文件
for line in page_text.split('\n'):
# 如果文件中不存在以 .ts 文件为结尾的文件
if not line.endswith('.ts'):
# 则直接写入
# 目的是为了合并做准备,ffmpeg合并文件,其中m3u8文件必须带头部,否则会报错
f.write(line + '\n')
# 如果文件中存在 .key 结尾的文件,将其头部补充完整
elif line.endswith('.key'):
# 拼接成完整的url
f.write('http://v6.dious.cc' + line + '\n')
# 如果存在 .ts 为结尾的文件
else:
# 将字符串进行分隔,取最后一部分,因为文件名不能存在 / \ 等特殊字符
line = line.split('/')[-1]
f.write(line + '\n')
# 将所有的ts文件链接提取出来
tss_urls = re.findall(r'(.*.ts)', page_text)
# 将所有ts文件链接提取出来返回,留待下一部分使用
return tss_urls
(4)下载所有ts文件
# 这个部分是下载文件的部分
async def download_file(self, i, sem):
'''
:param i: 每一个ts链接
:param sem: 信号量
:return: 打印每一个文件的下载情况
'''
# 设置并发量 --- 防止异步协程太快,造成timeout
async with sem:
# 将每一个ts链接拼接完整
ts_url = 'https://v6.dious.cc' + i
# 对每一个ts链接发起请求
async with aiohttp.ClientSession() as session:
async with session.get(url=ts_url, headers=self.headers) as response:
# 这里请求到的数据是被加密的ts文件数据(视频文件)
response_data = await response.read() # 这里返回的是二进制文件,所以用response.read()
# 这里是将每个ts文件的后缀名字取出来,当做每一个文件的名字
ts_name = os.path.basename(ts_url)
# 通过观察m3u8文件,发现其存在AES加密,这是cbc加密模式所需要的key的链接(在m3u8文件的上方,可以看到有一个key.key的文件,其中存的就是key)
key_url = 'https://v6.dious.cc/20220428/X2mBsQ9X/1500kb/hls/key.key'
# 对链接发起请求
async with aiohttp.ClientSession() as session:
async with session.get(url=key_url, headers=self.headers) as response:
# 拿到key并存储为二进制数据
key = await response.read() # 这里返回的是二进制文件,所以用response.read()
# 这是cbc模式所需要的iv偏移量(文件中没有声明,默认设置为16位的 0 )
iv = b'0000000000000000' # 需要将格式转为二进制模式,才能传到aes参数里面
# 创建aes对象
aes = AES.new(key=key, mode=AES.MODE_CBC, iv=iv)
# 声明文件路径
file_path = f'{self.file_name}' + '\\' f'{ts_name}'
# 打开文件
with open(file_path, 'wb') as f:
# 如果需要解密ts视频文件
# data = aes.decrypt(response_data)
# f.write(data)
# 这里采取不解密ts文件
f.write(response_data)
print(f'{ts_name}已经下载完成')
(5)ffmpeg视频合并命令
# 这里的部分是用来合并ts片段
async def merge_video(self, filename='video'):
'''
:param filename: 合成文件的文件名
:return:
'''
# ffmpeg -i ts文件夹下的m3u8格式文件 -c copy 合成后的视频名字
# ffmpeg -i index.m3u8 -c copy apple.mp4
# 切换到ts文件列表
os.chdir(f'{self.file_name}')
# 合成文件
os.system(f'ffmpeg -protocol_whitelist "file,http,crypto,tcp" -i index.m3u8 -c copy {filename}.mp4')
(6)设置协程的主程序入口
async def main(self):
# 声明任务列表
global tasks
# 创建任务列表
tasks = []
# 信号量:控制协程下载的并发量
sem = asyncio.Semaphore(50)
# 获取到所有ts链接
tss_urls = await self.get_page_url()
# 循环获取到每一个ts链接
for i in tss_urls:
# 创建任务,并传入参数,传给下一个函数调用下载
task = asyncio.create_task(self.download_file(i, sem))
# 将每一个任务添加到所有的任务中
tasks.append(task)
# 收集任务
await asyncio.wait(tasks)
# 待所有文件下载完成后进行ts文件合并
await self.merge_video()
(7)设置文件的主程序入口
if __name__ == '__main__':
# 实例化类对象
s = Spider()
# 调用类对象中的main方法
# 执行协程启动run函数
asyncio.run(s.main())
标签:视频,m3u8,url,self,xxx,爬虫,ts,文件,response
From: https://www.cnblogs.com/dream-ze/p/17391972.html