0:绪

在上个月,我通过爬取youtube视频,提取mp3,制作了一个基于云函数的搜索音乐网站。后来,我抱着解决ifvod广告的想法,阅读了ifvod的网站源码,并且通过修改js代码,成功删除了ifvod的广告。受上述两个过程的启发,我想,通过提取ifvod上的视频m3u8链接来制作一个基于云函数的视频搜索网站。

1:什么是m3u8

m3u8文件是一种文本文件,其格式如下:

1
2
3
4
5
6
7
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:18
#EXTINF:10.677,
media\_0.ts?dnvodendtime=1613939123&dnvodhash=LMkLRmsUktNRN4h-OrHuElRGhV71N32vV4nueFAQJXw%3D&dnvodCustomParameter=0\_2003%3Ae3%3Af2f%3A5700%3A8417%3Aeeca%3A9a8e%3A1d35\_1&lb=3c19337f6084516e1517490ee377c4b9&us=1&vv=4e5dcf3b2432cd03dd525e0489d72248&pub=1613766323672
#EXTINF:14.515,
media\_1.ts?dnvodendtime=1613939123&dnvodhash=LMkLRmsUktNRN4h-OrHuElRGhV71N32vV4nueFAQJXw%3D&dnvodCustomParameter=0\_2003%3Ae3%3Af2f%3A5700%3A8417%3Aeeca%3A9a8e%3A1d35\_1&lb=3c19337f6084516e1517490ee377c4b9&us=1&vv=4e5dcf3b2432cd03dd525e0489d72248&pub=1613766323672

在这个文本中,记录了视频的源地址。。我们常见的视频格式比如mp4,mp4文件直接储存了视频的全部信息。可直接使用播放器播放。而m3u又称流媒体,这种方法,是将原来完整的视频,切割成几秒到十几秒不等的,ts格式的视频片段,将每个片段作为一个视频内容储存在服务器,而m3u8文件储存的就是每个片段的url地址。html5播放器通过检索m3u8文件的内容,逐个下载视频片段,并播放。如今有许多软件支持,根据m3u8文件下载或播放视频。也就是说,如果我们有了m3u8文件的url,就可以在网站外下载和播放视频了。

2:构造json请求链接

通过分析ifvod网站返回的数据,可以发现。在一个请求返回的json数据中,包含了我们想要的视频地址,请求是

https://m8.ifvod.tv/api/video/play?cinema=1&id=xcql1mznpF0&region=DE&device=1&ispath=true&usersign=1&vv=ad4bee064a3af6c8a41cda35111966fd&pub=1613905710379

分析这个请求url,可以发现,其中id是视频的id,直接出现在视频播放的地址栏中,vv和pub是签名和公钥,有了这三个数据,我们就可以根据视频的id来获取视频的m3u8地址了。

尝试请求这个可以看到返回的数据是:

1
{"ret":200,"data":{"code":0,"msg":"","info":[{"id":625223,"vl":null,"add\_date":null,"post\_Year":null,"channel":null,"videoType":null,"contxt":null,"updateweekly":null,"commentNumber":0,"isVideoFavrited":false,"taxis":0,"flvPathList":[{"isHls":false,"result":"https://s1-a1.dnvodcdn.me/fast/423B3C813853-70244.mp4","validator":null,"type":0,"link":"http://ppt.ifvod.tv/c/c?position%3dPI%26d%3d1169%26r%3d7","broker":null,"backup":null,"rtmp":null,"needSign":false},{"isHls":false,"result":"https://s1-a1.dnvodcdn.me/fast/A0B67356-87371.mp4","validator":null,"type":0,"link":"http://ppt.ifvod.tv/c/c?position%3dPI%26d%3d1230%26r%3d7","broker":null,"backup":null,"rtmp":null,"needSign":false},{"isHls":false,"result":"https://s1-a1.dnvodcdn.me/fast/F709860823-88837.mp4","validator":null,"type":0,"link":"http://ppt.ifvod.tv/c/c?position%3dPI%26i%3d575%26r%3d7","broker":null,"backup":null,"rtmp":null,"needSign":false},{"isHls":true,"result":"https://hss8.dnvodcdn.me/ppot/\_definst\_/mp4:s8/gvod/xj-fllyssx-480p-03656717C.mp4/chunklist.m3u8?dnvodendtime=1614078950&dnvodhash=c7O8AgFsLq6N1O8ysAfNyp6SlvOLrVniVvvW2PEz8zU=&dnvodCustomParameter=0\_2003%3ae3%3af2f%3a5700%3a8417%3aeeca%3a9a8e%3a1d35\_1&lb=1f4a12a7923561331e23eee32ad4be8a&us=1","validator":null,"type":0,"link":null,"broker":null,"backup":"","rtmp":"https://hss8.dnvodcdn.me/ppot/\_definst\_/mp4:s8/gvod/xj-fllyssx-480p-03656717C.mp4/chunklist.m3u8?dnvodendtime=1614078950&dnvodhash=c7O8AgFsLq6N1O8ysAfNyp6SlvOLrVniVvvW2PEz8zU=&dnvodCustomParameter=0\_2003%3ae3%3af2f%3a5700%3a8417%3aeeca%3a9a8e%3a1d35\_1&lb=1f4a12a7923561331e23eee32ad4be8a&us=1","needSign":false}],"title":"480P","imgPath":null,"unlockGold":0,"playRecordURL":"//counter.timegate.vip/api/Counter/PlusOne?key=AddHitToMovie&id=27212","favrateNumber":0,"filterGold":0,"key":"puamPfe8zf7","cid":null,"commentStatus":0,"shareCount":0,"pinfenRate":0.0,"renqiRate":0.0,"customData":null,"isGuestHasMore":false,"link":null,"isVIPHasMore":false,"pauseData":[{"isHls":false,"result":"https://ppt.ifvod.tv/upload/video/202010020923422328455s.jpg","validator":null,"type":1,"link":"http://ppt.ifvod.tv/c/c?position%3dPZ%26d%3d1174%26r%3d7","broker":null,"backup":null,"rtmp":null,"needSign":false},{"isHls":false,"result":"https://ppt.ifvod.tv/upload/video/202012140947004731731s.jpg","validator":null,"type":1,"link":"http://ppt.ifvod.tv/c/c?position%3dPZ%26d%3d1173%26r%3d7","broker":null,"backup":null,"rtmp":null,"needSign":false},{"isHls":false,"result":"https://ppt.ifvod.tv/upload/video/202011032259305913218s.jpg","validator":null,"type":1,"link":"http://ppt.ifvod.tv/c/c?position%3dPZ%26d%3d2145%26r%3d7","broker":null,"backup":null,"rtmp":null,"needSign":false},{"isHls":false,"result":"https://ppt.ifvod.tv/upload/video/202102191325542507558s.jpg","validator":null,"type":1,"link":"http://ppt.ifvod.tv/c/c?position%3dPZ%26d%3d2373%26r%3d7","broker":null,"backup":null,"rtmp":null,"needSign":false},{"isHls":false,"result":"https://ppt.ifvod.tv/upload/video/202102120906530682725s.jpg","validator":null,"type":1,"link":"http://ppt.ifvod.tv/c/c?position%3dPZ%26d%3d1179%26r%3d7","broker":null,"backup":null,"rtmp":null,"needSign":false}],"startData":[],"guestSeriesList":null,"vipSeriesList":null,"commentList":null,"languageList":null,"downloadList":null,"videoServer":{"status":0,"info":"","isMp4Available":false},"copyRightInfo":null,"publisher":null,"previewFormat":"","uniqueKey":"27212\_625221","starImg":null,"starID":0,"starBgColor":null,"age":null}]},"msg":"","isSpecialArea":0}

其中flvPathList键放的存放着广告和视频的地址。

现在分析,vv和pub参数。

通常,为了保证请求链接不被篡改,并且,请求连接不能长期有效,都会给请求连接添加一个签名,以保证它随时变化且不被篡改。pub是publickey的简写,也就是公钥,相对应的,他还有私钥privatekey。这两个值,通常都是由服务器指定的。也就是说,他们都是可变的。。分析js源码,可以发现,pubkey是由js生成的当前毫秒时间戳。。私钥是通过pubkey经过一段运算,在7个私钥列表中选择的一个。这种算法是可笑的,因为通常公钥应该通过私钥来生成。私钥更不可能储存在本地。

然后是vv参数,vv参数是上述请求的url参数加上公钥和私钥,再通过md5编码,返回的32位字符串。

我使用python书写了一个通过原始url,来获取签名和添加公钥后的url的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import time
from urllib import parse
import hashlib
import requests

class Signurl:
def \_\_init\_\_(self, url: str, publicKey: int):
self.publicKey = publicKey
self.privateKey = ["version001", "vers1on001", "vers1on00i",
"bersion001", "vcrsion001", "versi0n001", "versio\_001", "version0o1"]
self.oldUrl = url
self.newUrl = self.\_getSignUrl()

def \_getSignUrl(self):
paramsStr = self.\_getParamsStr()
r = str(self.publicKey) + "&" + paramsStr + "&" + self.\_getPrivateKey()
hl = hashlib.md5()
hl.update(r.encode())
newUrl = self.oldUrl+"&vv="+hl.hexdigest()+"&pub="+str(self.publicKey)
return newUrl

def \_getParamsStr(self) -> str:
paramsStr = ""
params = parse.parse\_qs(parse.urlparse(self.oldUrl).query)
for key in params:
paramsStr += key + "=" + params[key][0]
paramsStr += "&"
return paramsStr[0:-1].lower()

def \_getPrivateKey(self):
return self.privateKey[self.publicKey % len(self.privateKey)]

其中publickey可以通过以下代码生成,publicKey = int(time.time()*1000)

3:提取m3u8链接

在获取了,json数据后,提取出m3u8的链接:

https://hss8.dnvodcdn.me/ppot/_definst_/mp4:s8/gvod/xj-fllyssx-480p-03656717C.mp4/chunklist.m3u8?dnvodendtime=1614078521&dnvodhash=0KnVagh-3RIHR_0fE_qVTS3X_t1ogc9iRTbuUYK5GYI=&dnvodCustomParameter=0_2003%3ae3%3af2f%3a5700%3a8417%3aeeca%3a9a8e%3a1d35_1&lb=1f4a12a7923561331e23eee32ad4be8a&us=1

直接请求这个链接是,无法获取到m3u8文件的,因为我们还需要通过它重新构造请求,同样是使用2章节所用的Signurl类,对此url签名。会增加vv和pub参数在上述url末尾。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

class Video:
def \_\_init\_\_(self, id: str) -> None:
self.publicKey = int(time.time()\*1000)
self.id = id
self.dataUrl = "https://m8.ifvod.tv/api/video/play?cinema=1&id=" + \
id+"&region=DE&device=1&usersign=1"
self.playUrl = self.\_getM3u8Url()

def \_getM3u8Url(self) -> str:
m3u8url = self.\_getData()
if m3u8url:
return Signurl(m3u8url, self.publicKey).newUrl
else:
return ""

def \_getData(self):
reqUrl = Signurl(self.dataUrl, self.publicKey).newUrl
rep = requests.get(reqUrl)
if rep.status\_code == 200:
return rep.json()['data']['info'][0]['flvPathList'][-1]['result']
else:
return ""

print(Video("WdS12xLwXh6").playUrl)

将上述两端代码放到同一个py文件中执行,就可以获取m3u8的播放链接了。

4:结语

我尝试将此链接通过在线m3u8播放器进行播放,遇到的问题是,如果这个视频播放了一段时间,那么在网页播放器中,通过m3u8只能播放到ifvod网页上的位置。通过分析,我推测,ifvod对m3u8链接的请求做了防盗链设置,也就是说,在其他网站上播放这个m3u8视频,是无法播放的,之所以出现部分片段能够播放,是因为ifvod播放过的片段会经过cdn,在cdn上是没有防盗链的。。也就是说,想通过在线m3u8播放器和通过serverless服务搭建一个播放器的想法是不能实现的。

为此,我花了许多时间来尝试和搜索破解方法。其中,iframe中添加referrerpolicy,网页中添加meta标签并修改位no-referrer均无法使视频播放。

唯一的办法是使用反向代理,我在我一台服务器上,通过nginx反向代理,实现了这一过程。但是这个方法代价太大了,首先,serverless是有免费额度的。而服务器需要自己掏腰包。通过代理,流量会经过服务器收费。相当不划算。没有实现的价值。

如果不通过自己的网站,比如使用chrome浏览器插件,Play HLS M3u8,可以直接在本地播放m3u视频,基于此,可以自己做一个插件,通过搜索,在本地播放,因为浏览器插件是可以修改referer的。

评论