1. 主要过程
-
- 找到m3u8文件(各种手段)
-
- 通过m3u8文件下载ts文件
-
- 将ts文件合并为一个MP4文件
首先整理一下思路
视频网站原理
- 将ts文件合并为一个MP4文件
1.低效率模式 <video src=‘123.mp4’>name</video>
2. 用户上传 ——> 转码(把视频做处理,2k,1080,标清) ——> 切片处理(把单个文件进行拆分)
用户在点播或者拉动进度条的时候
问题:
顺序, 需要一个文件记录至少两个东西(1文件播放顺序,2视频存放路径.)
M3U8 txt json => 文本
存在的问题解决方案
得到了JPG文件
hexdumop -C -n 1000 test.jpg
dd if=test.jpg of=outputs.ts bs=1 skip=126
配合 终端命令合并文件
ffmpeg -safe 0 -f concat -i file_list.txt -c copy output.mp4
2. 具体代码解析
2.1 需要引入的库文件
这里主要导入了需要用到的库,异步网络请求、文件读写等
import os
import aiohttp
import asyncio
import aiofiles
import requests
2.2 选取的下载地址
这里省略了如何获得这个下载地址,很简单,因为这里要学的东西很多,直接一步到位了
url = 'https://api.wangchuanxin.top/m3/国外剧/叶卡捷琳娜大帝第一季/叶卡捷琳娜大帝第一季01.m3u8'
2.3 主函数
第一步:
async def main():
mkdir('ts')
建立一个存放所有ts文件的文件夹。
第二步:
resp = requests.get(url)
a = resp.content.decode('utf-8')
# with open('叶卡捷琳娜大帝.m3u8', mode='wb') as f:
# f.write(resp.content)
resp.close()
使用 requests.get() 方法访问URL得到 m3u8数据,注意要解码!,当然也可以通过 ** open()** 直接写入文件,这里使用前者;直接打印会得到以下结果:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-ALLOW-CACHE:YES
#EXT-X-TARGETDURATION:8
#EXTINF:8.000000,
https://ali6.a.yximgs.com/udata/music/music_1cf8274d305d42b8a9737bbd1af249410.jpg
#EXTINF:4.000000,
https://ali6.a.yximgs.com/udata/music/music_2e7dc59d24fe4892b92fcf7a9cd811450.jpg
#EXTINF:4.000000,
.........
.........
我们需要的数据仅为下载链接和序列信息,所以要对原始的数据进行处理,使用以下方法
ss = a.replace('\n', '').split('#')
第三步:创建异步任务
首先建立一个空表,和一个计数器 i (方便后续文件命名),从链接表中提取到单个链接,使用单个链接创建异步任务,最后将任务挂起
tasks = []
i = 0
for s in ss[6:-1]:
# print(s)
i = i + 1
[_, tap] = s.split(',')
# sec = sec.replace('EXTINF:', '')
# tsp = tap.replace('jpg', 'ts')
# print(i, tap)
tasks.append(asyncio.create_task(downLink(tap,i)))
await asyncio.wait(tasks)
print('over')
第四步:输出文件信息
mklist('ts')
2.4 ts文件下载函数
传入 link 和 计数器 i 后 创建异步会话,这里的 aiohttp.ClientSession() 等价于 requests模块 ,进行一系列操作后写入ts文件。
async def downLink(links,i):
async with aiohttp.ClientSession() as session:
async with session.get(links) as resp:
async with aiofiles.open('ts/{:03d}.ts'.format(i), mode='wb') as fp:
ts = await resp.content.read()
await fp.write(ts[126:])
在这里存在一个坑不知道大家发现没有,应该写入的是 .ts 文件,但是从2.3第二步可以知道,url后缀是以 .jpg 结尾的!
将下载好的jpg文件使用 hexdump 打开如下(部分)
luoyang@xdwll:~/Desktop/数据挖掘/04$ hexdump -C -n 1000 test.jpg
00000000 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 |.PNG........IHDR|
00000010 00 00 03 20 00 00 03 20 08 03 00 00 00 ec ae f6 |... ... ........|
00000020 5a 00 00 00 19 74 45 58 74 53 6f 66 74 77 61 72 |Z....tEXtSoftwar|
00000030 65 00 41 64 6f 62 65 20 49 6d 61 67 65 52 65 61 |e.Adobe ImageRea|
00000040 64 79 71 c9 65 3c 00 00 00 06 50 4c 54 45 00 00 |dyq.e<....PLTE..|
00000050 00 00 00 00 a5 67 b9 cf 00 00 00 01 74 52 4e 53 |.....g......tRNS|
00000060 00 40 e6 d8 66 00 00 02 86 49 44 41 54 78 da ec |.@..f....IDATx..|
00000070 c1 01 0d 00 00 00 c2 a0 f7 4f 6d 0e 37 a0 47 40 |.........Om.7.G@|
00000080 11 10 00 42 f0 25 00 01 c1 00 00 ff 01 ff 00 01 |...B.%..........|
00000090 fc 80 14 48 12 01 06 46 46 6d 70 65 67 09 53 65 |...H...FFmpeg.Se|
000000a0 72 76 69 63 65 30 31 77 7c 43 ca ff ff ff ff ff |rvice01w|C......|
000000b0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
*
可以看到头文件确实是图像存储格式,但是从 00000070 的 7D 位置开始为 FFmpeg 文件的格式 , 即在 第 127字节 开始的位置。
使用如下代码验证一下:
dd if=test.jpg of=outputs.ts bs=1 skip=126
得到的 outputs.ts 可以正常进行播放!
而在python里就简单的很多
await fp.write(ts[126:])
将得到的字节按照从126位置开始进行切片操作就行了,配合传入的计数器i对ts文件进行命名,亦可以非常容易的完成播放时序排序的操作,至此,我们算是完成了一部视频的所有ts文件的下载操作。
2.5 其他函数
这里还是用到了两个函数,一个是创建文件夹的函数非常简单,
而另外一个是创建所有ts文件信息的函数,这是为了便于后续的ts文件合并成MP4文件。
def mkdir(path):
folder = os.path.exists(path)
if not folder: # 判断是否存在文件夹如果不存在则创建为文件夹
os.makedirs(path) # makedirs 创建文件时如果路径不存在会创建这个路径
print("--- new folder... ---")
print("--- OK ---")
else:
print("--- There is this folder! ---")
def mklist(path):
file_list = sorted(os.listdir(path))
with open('file_list.txt', 'w+') as f:
for file in file_list:
f.write('file ./ts/{}\n'.format(file))
后续合并所需要使用的脚本如下
ffmpeg -safe 0 -f concat -i file_list.txt -c copy output.mp4
最后可以得到完整的MP4文件
3. 完整代码
可能与上方代码不同,要注意啦!
import os
import aiohttp
import asyncio
import aiofiles
import requests
url = 'https://api.wangchuanxin.top/m3/国外剧/叶卡捷琳娜大帝第一季/叶卡捷琳娜大帝第一季01.m3u8'
def mkdir(path):
folder = os.path.exists(path)
if not folder: # 判断是否存在文件夹如果不存在则创建为文件夹
os.makedirs(path) # makedirs 创建文件时如果路径不存在会创建这个路径
print("--- new folder... ---")
print("--- OK ---")
else:
print("--- There is this folder! ---")
def mklist(path):
file_list = sorted(os.listdir(path))
with open('file_list.txt', 'w+') as f:
for file in file_list:
f.write('file ./ts/{}\n'.format(file))
async def viewLink(links, i):
async with aiohttp.ClientSession() as session:
async with session.get(links) as resp:
async with aiofiles.open('ts/{:03d}.ts'.format(i), mode='wb') as fp:
ts = await resp.content.read()
await fp.write(ts[126:])
async def main():
mkdir('ts')
resp = requests.get(url)
a = resp.content.decode('utf-8')
# with open('叶卡捷琳娜大帝.m3u8', mode='wb') as f:
# f.write(resp.content)
resp.close()
ss = a.replace('\n', '').split('#')
print(ss)
tasks = []
i = 0
for s in ss[6:-1]:
# print(s)
i = i + 1
[_, jap] = s.split(',')
# sec = sec.replace('EXTINF:', '')
# jap = jap.replace('jpg', 'ts')
print(i, jap)
tasks.append(asyncio.create_task(viewLink(jap, i)))
await asyncio.wait(tasks)
mklist('ts')
print('over')
if __name__ == '__main__':
asyncio.run(main())
4. 总结
总的来说,爬取没有加密的视频还是比较简单的,就怕对方在 m3u8 和 ts 文件进行加密,这样还要自己看 JS 代码,这是相当的痛苦。。。,但是大部分视频网站确实是这样的,我能找到这样的,仅仅只需要修改返回的文件头就行了,属实是我比较幸运。
异步下载是视频是真的快,我下午看用平板看直播呢,忽然电脑风扇狂转,而且直播也卡起来了,这一集的视频大小有1.3G大,下了不到 30s 吧。