最近出了部新剧《一念关山》,觉得挺好看,就用python爬了一下这部剧,下面是完整代码~
注意:
1.博主python版本是3.11
2.使用的库都在下面有说明了
"""
爬取视频流程:
1.拿到页面源码
2.分析页面,是从接口处拿到m3u8(m3u)的url
3.下载m3u8
4.读取m3u8文件,下载视频
5.下载密钥,进行解密
5.合并视频
注意:
1.要先拿到源码,分析出m3u8地址,看看是否在源码还是接口处获取
2.通过m3u8拿到具体视频地址,要注意,有的平台会有两层m3u8,要注意甄别,一般正确的m3u8文件会很长
3.拿到地址异步下载视频
4.查看m3u8文件是否包含密钥,如果有要拿到密钥地址,获取密钥,然后解密视频
5.解密之后的密钥再来合并视频,合并视频有两种方法
--5.1 通过os合并,具体方法见下面代码,需要注意的是,超过300个.ts文件文件会合并失败
--5.2 这种方法直接写入文件,具体见下面代码,可以合并大量文件
注意:
基本上所有的视频内容都包含在m3u8文件中,下载其中的内容合并就可以了
m3u8文件例子:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:18
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=AES-128,URI="enc.key",IV=0x00000000000000000000000000000000
#EXTINF:10.416667,
https://xxx/hls/141/20231016/1969067/plist0.ts
#EXTINF:10.958333,
https://xxx/hls/141/20231016/1969067/plist1.ts
...多个结构
#EXTINF:9.958333,
https://xxx/hls/141/20231016/1969067/plist239.ts
#EXT-X-ENDLIST
"""
import requests
import re
import asyncio
import aiohttp
import aiofiles
# 导入解密相关包 只需下载 pycryptodome即可
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
import os
def get_m3u8(url): # 获取m3u8地址
resp = requests.get(url)
# print(resp.text)
pat = re.compile(r'now="(?P<m3u8>.*?)";', re.S) # 获取m3u8地址
m3u8 = pat.search(resp.text).group('m3u8')
resp.close()
# print(m3u8)
resp1 = requests.get(m3u8) # 通过地址获取到m3u8内容
# print(resp1.content)
with open('video/m3u8.txt', mode='wb') as f:
f.write(resp1.content) # 将m3u8内容保存到文件
return m3u8 # 返回m3u8地址,备用
async def download_video(url,session): # 异步下载视频
async with session.get(url) as resp: # 下载视频,下载完毕自动关闭
async with aiofiles.open(f"video/{url.rsplit('/')[-1]}", mode='wb') as f: # 写入下载视频内容
await f.write(await resp.content.read()) # 把下载内容写入文件(异步)
print(f"{url.rsplit('/')[-1]}下载完毕")
async def get_video(): # 添加下载视频任务
tasks = []
async with aiohttp.ClientSession() as session:
async with aiofiles.open('video/m3u8.txt', mode='r', encoding='utf-8') as f: # 读取url地址
async for line in f:
if line.startswith('#'): # 判断是否是url
continue
line = line.strip() # 去掉没用的空格和换行
task = asyncio.create_task(download_video(line, session)) # 创建下载视频任务
tasks.append(task) # 添加到任务队列
await asyncio.wait(tasks) # 等待任务结束
def get_key(url): # 获取key,进行视频解密
return requests.get(url).text
async def excute_decrype(key): # 添加异步解密视频任务
tasks = []
async with aiofiles.open(f'video/m3u8.txt', mode='r', encoding='utf-8') as f:
async for line in f:
if line.startswith('#'):
continue
# 传入key和文件名
name = line.rsplit('/')[-1].strip()
task = asyncio.create_task(change_video(key, name))
tasks.append(task)
await asyncio.wait(tasks)
async def change_video(key, name): # 传入key和文件名称,解压视频
# 使用CBC模式和随机生成的IV
iv = get_random_bytes(16)
cipher = AES.new(key.encode(), AES.MODE_CBC, iv) # 创建对象
# 打开要解压的视频 video/{name},并同时创建解压后的视频文件 video/temp_{name}
# \ 表示换行连接符,上下两行为一行
async with aiofiles.open(f"video/{name}", mode='rb') as f1, \
aiofiles.open(f"video/temp_{name}", mode='wb') as f2:
bs = await f1.read() # 从源文件读取内容
plaintext = unpad(cipher.decrypt(bs), AES.block_size) # 解密内容
await f2.write(plaintext) # 把解密好的内容写入文件
print(f"{name}写入成功")
def merge_video_ts():
# 第一种合并方法:适用于不超过300个文件合并。
# mac: cat 1.ts 2.ts 3.ts > xxx.mp4
# windows: copy /b 1.ts+2.ts+3.ts xxx.mp4
# video_list = []
# with open('video/m3u8.txt', mode='r', encoding='utf-8') as f:
# for line in f:
# if line.startswith('#'):
# continue
# line = line.rsplit('/')[-1].strip()
# video_list.append(f'video/temp_{line}')
# s = '+'.join(video_list)
# os.system(f"copy /b {s} 'movie.mp4'")
# 第二种方法;适用于大量文件合并
with open("video/video.mp4", "wb") as f:
with open("video/m3u8.txt", "r") as f1:
for line in f1:
if line.startswith("#"):
continue
line = line.rsplit('/')[-1].strip()
with open(f'video/temp_{line}', "rb") as f2:
f.write(f2.read())
print('合并完成')
def main(url): # 主执行函数
# 1.获取m3u8地址,并保存下载保持到文件
m3u8 = get_m3u8(url)
# 2.下载视频
asyncio.run(get_video()) # 这里一定要用asyncio.run()包裹执行函数
# 3.获取key值
key = ''
with open('video/m3u8.txt', mode='r', encoding='utf-8') as f:
for item in f:
# print(111,item)
if('EXT-X-KEY' in item):
# 获取m3u8中的key值
pat = re.compile(r'URI="(?P<key>.*?)"', re.S).search(item).group('key')
# 拼接key值,获取真正的获取Key的地址
key_url = m3u8.replace('index.m3u8', pat) # 获取到得到key的地址
key = get_key(key_url).strip() # 通过地址请求到key
# 4.通过key解密视频
asyncio.run(excute_decrype(key)) # 传入key,开始解压
# 5.合并视频
merge_video_ts()
if __name__ == '__main__':
# 爬取xxx电影网 一念关山电视剧第一集
url = 'https://xxxxxxx/play/106321-0-0.html'
main(url) # 传入要获取的地址
print('全部下载完成')
最后会得到一个video.mp4文件,这个就是最终文件了
到这里就可以观看视频了,有问题的可以留言~