音泉广播下载方法探索
音泉也算是我的老朋友了。早在高中的时候,我就尝试从他的网页和APP端下载广播,还以此为契机用黑箱式的理解探索了浏览器F12和Fiddler
抓包的用法。当时的发现是,音泉的媒体传输机制相当笨蛋,当然也为我提供了便利:客户端实际上是通过固定的URL直接下载(当然也可以分片下载)完整的音频文件,也不需要任何用户验证,这意味能想办法抓包获取URL就算成功。
就算技术上再守旧,音泉大概也不会一直这样下去。果不其然,到了2020年(不记得确切时间)前后,我又想去下载广播的时候才发现,他换上了一套比较成熟的流媒体机制了,当然这也意味着自动抓取下载的难度增大。直至本文写作的时间(2022年11月)为止,下载音泉广播的策略还不需要调整。这里,我们会从抓包内容出发,简单梳理排除干扰获取所需内容的思路。
我们假定读者对HTTP请求的组成和功能有基本的了解。当然不了解也不是不行。
抓包过程
我们的目标是用尽可能低的成本获取想要的信息。在正式开始之前,我们需要有这样的意识:
只要是客户端出现的东西,就一定有他的来源。 ——我
只要网页播放器放出了音视频,我们就一定能找到媒体文件;如果我们找到的是加密过的版本,那浏览器就一定在某个时候得到了密钥;如果浏览器请求了114514个片段,那他一定事先通过请求知道了片段的数量和编号。大体上来说,我们在浏览器上看到的东西归根结底只有两个源头:
- 加载页面时,HTML源码提供的静态信息。
- 加载JS时动态获取的信息片段,可能以纯文本、JSON、多媒体、JavaScript之类的形式出现。
处理静态信息是最方便的,在网页源码上就一览无余,也不难构造HTTP请求做进一步处理。动态信息是万恶之源,但如果不想深究的话也未尝没有捷径。使用F12和Fiddler
的主要目的也是处理动态信息。
这里以孤独摇滚广播为例,首先打开抓包工具确认状态正常,然后点击播放。播放之前可以清空已有的请求,因为我们并不关心那些内容。
不出意外,我们会在F12的网络选项里看到蜂拥而至的HTTP请求,看到差不多得了就可以停止记录分析情况。(Tips:可以将抓包记录保存成文件)
我们发现这里有些值得注意的地方:
- playlist.m3u8,看起来就像是记录了所有片段的汇总表。
- chunklist.m3u8,看起来和上一个差不多。
- key.m3u8key,看起来是经过了加密。
- media_{数字}.aac,看起来就是我们需要的音频。
我们需要的东西估计就在那里,点进去一个一个检查请求头和内容,祈求没有看不懂的部分便是。从playlist开始,我们发现他的URL是 “https://onsen-ma3phlsvod.sslcs.cdngc.net/onsen-ma3pvod/_definst_/202211/bocchi-radio221116m4pb-09.mp4/playlist.m3u8” ,里面出现了原始文件的名字,不错。再看看内容:
1 | #EXTM3U |
感觉意思不大,只是指向了下一个记录元信息的文件,此外记录了带宽和编码格式。接着看chunklist,我们发现他的URL格式也类似,这是个好消息。
1 | #EXTM3U |
我们关心的内容都在这个文件里得到了解答:
- 完整的文件分成了393个片段,每段长度约10秒。
- 每个片段都经过了加密,方式是AES-128,虽然不确定具体采用了哪种实现方式。
- 加密密钥URI是"…/key.m3u8key"。
带着这些已知信息,我们发现密钥的Content-Length是16,和加密方式的128位对上了;媒体文件看不出任何文本内容,而媒体文件的容器标识本应有一些和ASCII兼容的部分,所以可以推测整个文件都使用AES做了一次加密。如果抓包的时候使用的是Fiddler,这时我们就可以把响应内容保存成文件验证猜想。最后的结论是,我们可以用AES-128-CBC模式解密,偏移量和密钥都是key.m3u8key的内容。
至此,信息获取的准备工作基本完成,接下来的任务就是尝试自动下载所有的aac文件,然后把它们拼接起来。在这一步,我们仍然会用到抓包的信息,只不过目的性更强一些。下面介绍两种下载方法,其中第一种比较简单,对无料广播适用,会员限定广播不确定,因为还没验证;第二种比较麻烦,但是原理上不受限制。
使用ffmpeg下载广播
这种方法只需要用到ffmpeg这一个命令行工具。
考虑到m3u8是一种通用的流媒体格式,强大的ffmpeg照理说应该能直接处理Playlist,上网搜了一下发现也确实如此,这样唯一需要顾忌的地方只有请求头了。检查一下Playlist,Chunklist和各片段的请求头,会发现它们是完全一样的。以本人使用的浏览器为例:
1 | Accept: */* |
我们发现这里没有用到Cookie,是好事。起关键作用的可能是Origin,Referer和User-Agent,不过保险起见我们还是想办法都塞给ffmpeg好了。我们以Powershell为例,写个简单的脚本:
1 | # 注意是Playlist |
值得注意的是,Powershell的转义格式比较独特,常用的反斜杠都要改成反引号;而且由于ffmpeg的参数解析问题,请求头里出现的双引号都要加上反斜杠转义。至于Linux用户,想必无需我多费口舌也能自己找到具体写法。在测试时,可以加上-v trace
的参数,让ffmpeg打印运行过程排查问题。
由于ffmpeg帮我们处理了所有的细节,只需等待一会就能拿到成品,相当轻松。尽管获取m3u8的过程不够自动化,但除非想要大批量下载,一般需要也不值得笔者再花时间处理自动抓取网页信息的部分。
备用方法
在糟糕的情况下,请求头可能附带一些不容易获取的信息,这意味着构造请求的代价变得难以承受,但我不过是想下载广播而已。在这种情况下,可以考虑结合Fiddler的自定义脚本,在抓取到相关请求的时候把文件保存下来,按照上面的分析方法解密、拼接。这实际上也是我最早想到的方法。
Fiddler的脚本并不基于JavaScript,但是语法大差不差,对于简单功能的实现来说没有必要区分。打开Script Editor后,我们希望在请求捕捉结束之后保存HTTP的响应内容,所以在类属性加入自定义选项,再到OnBeforeResponse
静态方法里加点私货:
1 | // 加到类属性处 |
文件命名规则可以自行处理。保存自定义脚本之后,我们会在选项卡看到刚才加入的部分:
点击勾选,开始捕捉之后就能在我们指定的路径里找到m3u8、key和aac三类文件。给aac解密的部分很好操作,略去不提。随便打开一个拼接后的aac,我们发现普通的播放器也能放出来,接下来的问题就是把aac拼接起来。
尽管这个问题看来不难,但是让我困扰了一会,因为这些片段文件似乎并不是标准格式,用常规的拼接操作会出现问题。思来想去,最后感觉不如试着把Playlist喂给ffmpeg,告诉他这是从流媒体来的,结果这样成功了:
1 | ffmpeg -i playlist.txt output.aac # 其实和上面是一样的 |
不过在运行之前要去掉Chunklist里面提示加密方式的部分,否则他会尝试去请求密钥。可以看到,这种方式周折颇多,不过原理上来说基本上是万用的。