由于是付费课程,加密是必不可少的,这个网站使用了m3u8作为播放方式,m3u8是一个播放列表索引文件。它本质上是一个普通的文本文件,只不过后缀名是 .m3u8。
它是苹果公司推出的 HLS (HTTP Live Streaming) 协议的核心部分,现在广泛应用于各类手机直播、点播以及网页视频播放。
如果你直接下载一个 1GB 的 MP4 视频,网络不好时会非常卡。m3u8 采用了不同的逻辑:
.ts 格式,每段几秒钟)。如果你用记事本打开一个 m3u8 文件,你会看到类似这样的内容:
#EXTM3U#EXT-X-TARGETDURATION:10#EXTINF:10.0,https://example.com/video/part1.ts#EXTINF:10.0,https://example.com/video/part2.ts...普通的ts文件是不加密的,下载后直接可以用一些播放器播放,所以解析m3u8 中所有ts文件,然后把这些所有ts文件合并成一个mp4文件即可,可以使用ffmpeg合并
但问题就是在这类网站中ts是加密的,m3u8中自带一个#EXT-X-KEY用于加密,简单来说,它的作用是告诉播放器:"接下来的视频分片(ts文件)是加密的,你需要去哪里下载密钥,并用什么算法来解密它们。"
一个典型的 #EXT-X-KEY 标签看起来像这样:#EXT-X-KEY:METHOD=AES-128,URI="https://example.com/key",IV=0x123456789...
它通常包含以下几个核心参数:
NONE:不加密(默认)。AES-128:最常见的加密方式,使用 128 位密钥。SAMPLE-AES:通常用于高阶加密(如 FairPlay DRM),仅加密音频或视频的部分采样数据。identity 表示原始二进制,或者特定的 DRM 标准)。当播放器解析 M3U8 文件时,它的流程如下:
#EXT-X-KEY。URI 指定的地址发送请求,下载 16 字节的密钥。.ts 视频分片。由于这些分片是加密的原始数据,无法直接播放,播放器会利用刚才下载的密钥和指定的算法进行实时解密。所以对于这类加密的网站,下载密钥后,自行解密也是可以做到逆向的。
而现在面对的不同,实际中的密钥,是无法解密的,而本次演示的网站,在长达2天半的研究下,他一共有4道锁,
第一道是前端限制,在打开"开发者工具"时候,页面会瞬间跳转到空白页面,这显然是前端做了监听,防止打开开发者工具,只需要安装油猴+"反 devtools-detector 反调试"插件即可,在 devtools-detector 中配置要反的域名。
第2、3道是根据课程id获取播放的m3u8地址,虽然可以通过开发者工具直接看到请求地址,但是我们要做的是通过程序批量下载,所以这种方式不行,需要逆向他的解密过程,好在这部分不是很难,而最后一道是很难的,用于获取ts的解密密钥,跟踪了老半天,发现最他的解密太复杂了,虽然定位到了具体的方法,但是涉及到的上下文太多,用程序无法还原出他的解密过程,所以放弃了。
但是,天无绝人之路,后来又研究了下,这个网站使用了腾讯云点播,引入他的js,然后用下面代码,传入指定参数,就可以播放视频了,其中appID是固定的,应该是腾讯云分配给应用的唯一id,fileID是视频的id,而psign是签名,这个也比较好获取。var config = {autoplay: true,posterImage: false,muted: true,playbackRates: [0.8, 1, 1.25, 1.5, 1.75, 2, 3],controlBar: {subsCapsButton: false,textTrackSettings: false,QualitySwitcherMenuButton: false,},persistTextTrackSettings: true,fileID: fileID,psign: psign,appID: xxxx,};let h = undefined;var player = TCPlayer("player-container-id", config, h);
psign的获取方式是请求他的服务器,有一个https://www.xxxx.com/xxx/api/video_play_detail地址,传入课程的id,和章节的id,会返回这个课程的详细信息,然后用一个aes解密就可以得到,下面是python的代码,aes的key是直接在js中写死的,跟踪一下就可以获取到。
url = f"https://www.xxx.com/xxx/api/video_play_detail?from=web&channel=web&devtype=web&platform_type=web&course_section_id={course_section_id}&course_id=xxx&play_course_section_id={play_course_section_id}&t={time.time()}"cursor_detail = requests.get(url, headers=headers).json()PLAY_AES_KEY = "bl538e945d5d3c41047b3b50j34ca72c"ENCRYPTED_T = cursor_detail.get('data').get('play_auth')decrypted_json = decrypt_play_auth(ENCRYPTED_T, PLAY_AES_KEY)p_sign = json.loads(decrypted_json).get('p_sign')然后网页就可以自动播放视频这个课程了,但是播放了有什么用呢,如何下载呢,下面就是一个技巧。
我们把TCPlayer("player-container-id", config, h);这些已经放在单独html服务了,然后用playwright访问http://127.0.0.1:5500/index.html,传入psign和file_id,在用playwright监听发起的m3u8链接,这样我们就通过程序提取到了关键的一部分,剩下一部分就是解密ts的密钥。
我们可以直接修改hls.min.1.1.7.js中的代码,在解密的方法结束后,直接console.log出密钥,哈哈哈哈哈,playwright是可以监听控制台输出的,这下我们就有了m3u8和ts的解密密钥,通过python就可以直接下载和解密了

而hls.min.1.1.7.js是在腾讯云点播的tcplayer.v5.1.0.min.js中引入的,默认当然是请求他的服务器,我们在修改一下他的代码,让hls地址请求我们修改过后的。

下面是简短的代码演示defcall_playwright(my_psign, my_file_id): result = {"url": None, "decrypt_key": None}defhandle_response(response): url = response.url pattern = r'video_[^/]+.m3u8'if re.search(pattern, url): result["url"] = urldefhandle_console(msg): text = msg.text match = re.search(r'解密:(\S+)', text)if match: result["decrypt_key"] = match.group(1) print(f"解密:{result['decrypt_key']}")with sync_playwright() as p: browser = p.chromium.connect_over_cdp("http://localhost:9222") default_context = browser.contexts[0] page = default_context.new_page() page.on("response", handle_response) page.on("console", handle_console) page.goto(f"http://127.0.0.1:5500/index.html?psign={my_psign}&fileID={my_file_id}") max_wait = 60for _ in range(max_wait):if result["url"] isnotNoneand result["decrypt_key"] isnotNone:break page.wait_for_timeout(500)if result["url"]: print(f"检测到URL: {result['url']}")ifnot result["decrypt_key"]: print("超时:未检测到解密密钥") page.close() browser.close()return result
最后就是批量下载了,这就很容易了,每个课程都有一个课程id,请求他的课程详细接口,会返回所有章节信息,每个章节都有它位于腾讯云点播中的file_id。这些都是没有加密的。
另外从上面代码中可以发现,我们用chromium通过cdp协议连接本地的服务,因为发现,playwright自动下载的chromium,是无法播放视频的,提示当前浏览器不支持此协议,具体不知道是哪里的问题,所以我们通过chrome.exe --remote-debugging-port=9222 --user-data-dir="C:\temp\chrome_dev"启动自己浏览器,一定要指明--user-data-dir。