最近几年 Python 实在是太火了,网上的课程层出不穷。一般 Forece 都是在B站找视频看的。最近有个朋友问我,他加入了个 Python 群,每周都有课。用的平台是网易云课堂微专业直播。课程可以回放,想问我能不能帮他下载一下网易云课堂的视频。安排!

1. 抓包分析

首先打开直播网址,然后 F12 打开开发者工具,进行抓包。发现有类似如下的 GET 请求。

https://jdvodluwytr3t.vod.126.net/jdvodluwytr3t/nos/hls/2020/06/04/1216859039_01eca46abd424762a92e7a7e26a35b3d_hd0.ts
https://jdvodluwytr3t.vod.126.net/jdvodluwytr3t/nos/hls/2020/06/04/1216859039_01eca46abd424762a92e7a7e26a35b3d_hd1.ts
https://jdvodluwytr3t.vod.126.net/jdvodluwytr3t/nos/hls/2020/06/04/1216859039_01eca46abd424762a92e7a7e26a35b3d_hd2.ts
https://jdvodluwytr3t.vod.126.net/jdvodluwytr3t/nos/hls/2020/06/04/1216859039_01eca46abd424762a92e7a7e26a35b3d_hd3.ts
https://jdvodluwytr3t.vod.126.net/jdvodluwytr3t/nos/hls/2020/06/04/1216859039_01eca46abd424762a92e7a7e26a35b3d_hd4.ts
......

下载下来看了一下,发现只有几秒的视频。继续查看抓包情况。在加载这些视频之前,发现有个 M3U8 的文件。科普一下,这个文件其实是一个索引文件,我们可以把它当做一个 playlist。下载回来用文本工具打开

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:13
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:12.500000,
1216859039_01eca46abd424762a92e7a7e26a35b3d_hd0.ts
#EXTINF:12.500000,
1216859039_01eca46abd424762a92e7a7e26a35b3d_hd1.ts
#EXTINF:12.500000,
1216859039_01eca46abd424762a92e7a7e26a35b3d_hd2.ts
#EXTINF:12.500000,
1216859039_01eca46abd424762a92e7a7e26a35b3d_hd3.ts
#EXTINF:12.500000,
1216859039_01eca46abd424762a92e7a7e26a35b3d_hd4.ts
#EXTINF:12.500000,
1216859039_01eca46abd424762a92e7a7e26a35b3d_hd5.ts
#EXTINF:12.500000,
1216859039_01eca46abd424762a92e7a7e26a35b3d_hd6.ts
#EXTINF:12.500000,
1216859039_01eca46abd424762a92e7a7e26a35b3d_hd7.ts
#EXTINF:12.500000,
1216859039_01eca46abd424762a92e7a7e26a35b3d_hd8.ts
#EXTINF:12.500000,
1216859039_01eca46abd424762a92e7a7e26a35b3d_hd9.ts
#EXTINF:12.500000,
......
#EXTINF:2.900000,
1216859039_01eca46abd424762a92e7a7e26a35b3d_hd596.ts
#EXT-X-ENDLIST

可以看出这个整个视频被分隔成了597份,然后除了最后一段2.9s,每段只有12.5s的时间。有了地址,有了索引,下载下来,然后用 os.command 合并就可以了。

2. 代码

import requests
import os

# 手动添加了分割数目(有能力的同学自己完善一下)
for i in range(596):
    url = 'https://jdvodluwytr3t.vod.126.net/jdvodluwytr3t/nos/hls/2020/06/04/1216859039_01eca46abd424762a92e7a7e26a35b3d_hd%s.ts' % i
    headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'}
    response = requests.get(url, headers=headers)
    content = response.content
    with open(r"./163/%s.ts" % os.path.basename(url), 'wb') as fp:
        fp.write(content)

# 利用 cmd 中的 copy 命令合并文件
command = r'copy /b  163\*.ts  163\new.ts'
os.system(command)

发现问题:
发现用 copy 命令会出现文件顺序问题。自己重新生成了一下文件列表,然后直接用 write 方法拼接了文件。

file_list = []
for i in range(596):
    file_list.append("./163/1216859039_01eca46abd424762a92e7a7e26a35b3d_hd%s.ts" % i)


with open("./163/new.ts", 'wb+') as fp:
    for i in range(len(file_list)):
        fp.write(open(file_list[i], 'rb').read())

其实解决方法很多,你也可以将文件下载的时候就直接弄好编号,然文件顺序排列,比如补位0之类的。或者在做下载的时候,下载完一个视频就追加到合并视频当中。

完善了一下代码,从M3U8自动获取课程分割视频数目,直接以添加方式合并文件

import requests
import re
import os

url = 'https://jdvodluwytr3t.vod.126.net/jdvodluwytr3t/nos/hls/2020/07/21/1217041902_b0c26b5a8ff44633aeaa7c20089d07f7_hd.m3u8'
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'}
response = requests.get(url, headers=headers)
m3u8 = response.content.decode('utf-8')
pattern = '\d*?_.*?_hd\d*.ts'
file_index = re.findall(pattern, m3u8)
base = os.path.dirname(url)

for i in file_index:
    file_url = base + '/' + str(i)
    response = requests.get(file_url, headers=headers)
    content = response.content
    with open(r"./163/new.ts", 'ab') as fp:
        fp.write(content)
        print(i + '下载完成')

未完善
1. 自动获取课程所有课程M3U8
2. 异步下载课程