python实现某视频地址的解析及下载(全集下载)

查看 166|回复 11
作者:billsmiless   
python实现某视频地址的解析以及下载(全集下载)
1 前言
最近买了一个非联网的视频播放器,想导入些视频进去,找了一些下载站点,不是很满意,因此就想着从某些解析网站中直接下载视频。
经过验证,方案可行。这里找了个简单的,视频地址直接暴露的解析网站。这样可减轻些工作量。主要工作是自动化提取地址,自动化下载。
这样就提高了效率,避免几十集的剧,一个一个的复制黏贴。把折腾的过程,整理成文,分享的同时,也可加深自己的记忆。
实现过程主要分为几个流程:
a、逆向分析视频真实地址获取流程
b、python实现自动化获取视频地址
c、通过拿到的真实地址,进行视频下载
d、全部剧集批量自动化下载
开发环境:Mac系统、Safari浏览器、Charles抓包、SublimeText(主要用来写python代码)、Python3.0
2 逆向分析视频真实地址(直连地址)
2.1 寻找视频真实地址
在浏览器输入如下地址:
aHR0cHM6Ly9qeC54bWZsdi5jb20vP3VybD1odHRwczovL3YucXEuY29tL3gvY292ZXIvbXpjMDAyMDA3a25taDNnL2IwMDQ1Zm1uNWl2Lmh0bWw=
其中等号后面是企鹅视频的链接,这里放的是三体的视频。大家可替换为其他企鹅视频的地址
https://v.qq.com/x/cover/mzc002007knmh3g/b0045fmn5iv.html
等待解析完成后,打开web调试器,定位到视频标签,会在代码中,看到视频url信息。如图01:


d01.png (1.67 MB, 下载次数: 0)
下载附件
2023-3-1 17:11 上传

通过charles抓包,我们可以看到视频的信息是api.php接口返回而来的。如图02:


d02.png (859.86 KB, 下载次数: 0)
下载附件
2023-3-1 17:11 上传

至此,我们知道视频的地址是接口返回的,接下来我们就分析接口请求的过程
2.2 分析接口请求过程
下面来分析,api.php该请求的参数,通过抓包知道该请求的有5个参数如下。如图03:
url、time、ua、key、vkey   


d03.png (888.7 KB, 下载次数: 0)
下载附件
2023-3-1 17:11 上传

想定位请求的参数是如何生成的?我们全局搜索参数名,这里搜索vkey。因为相对来说,这个名字比较特殊。
可以看到,有几处含有vkey,这里我们直接定位到html文件中的367行的vkey。可以发现,很像请求的参数。如图04:


d04.png (1.04 MB, 下载次数: 0)
下载附件
2023-3-1 17:11 上传

验证下是否是api.php请求的参数。
在361行断点,单步执行。(执行的时候,行数居然变成了268行了)
可以发现,代码是经过混淆处理的,增加了阅读难度。没关系,可以调试就行。调试器就像杀猪刀,锋利啊,啥猪都得死。
这里看下 _0x4db875 该函数,对应的函数是:
function _0x9205(_0x52c2a4, _0x9b2f86) {
    var _0x46e8ae = _0x46e8;
    return _0x9205 = function(_0x9205c4, _0x5a9612) {
        _0x9205c4 = _0x9205c4 - 0x78;
        var _0x5839fd = _0x46e8ae[_0x9205c4];
        --- 太长了,省略掉部分 ---
            var _0x1be044 = _0x46e8ae[0x0],
            _0x1d8125 = _0x9205c4 + _0x1be044,
            _0x191fb8 = _0x52c2a4[_0x1d8125];
        return !_0x191fb8 ? (_0x9205['BHbhTy'] === undefined && (_0x9205['BHbhTy'] = !![]), _0x5839fd = _0x9205['FOnTbZ'](_0x5839fd, _0x5a9612), _0x52c2a4[_0x1d8125] = _0x5839fd) : _0x5839fd = _0x191fb8, _0x5839fd;
    }, _0x9205(_0x52c2a4, _0x9b2f86);
}
经分析可知,该函数就是个字符串算法,用于生成各种字符串,比如函数名啊,关键字啊等等。知道就行了,不用分析具体算法。
接下来,打印以下几个函数的返回值:
_0x4db875(0xff, '46r!') = 'userAgent'
_0x4db875(0xac, '5)&X') = 'match'
_0x4db875(0xab, 'b#cp') = '1'
_0x4db875(0xb2, 'PDVe') = '/api.php'
可以得知:请求的接口名字是api.php、方法为post、并匹配userAgent, 这个userAgent是必须的,如果不设置,请求会报错。如图05:


d05.png (883.16 KB, 下载次数: 0)
下载附件
2023-3-1 17:11 上传

接下来,继续查看url,time,ua,key,vkey的值。
此时,我们可以看到url的值,即为原视频的地址(即腾讯视频的地址)、
time为时间戳、ua为1,表示带有userAgent、key的值为sign函数生成的、vkey则是由keyen函数生成。
如下图所示。


d06.png (1.95 MB, 下载次数: 0)
下载附件
2023-3-1 17:11 上传

综上,我们可以确定,此处就是获取视频直连地址的post请求,也就是api.php请求。所以,只要我们将以上参数的值拿到,就可以模拟api.php请求获取视频直连链接了。
我们知道key和vkey的参数值是通过函数sign和keyen生成的,只需要传递一个参数 vkey。因此我们在文件内搜索变量vkey的值。
搜到了vkey以及ua、time的值,经对比,是请求参数的值。如下图:


d07.png (1.12 MB, 下载次数: 0)
下载附件
2023-3-1 17:12 上传

至此我们理清了获取视频直连链接的请求流程。大致如下:
1、访问解析链接,得到html,html中可以得到:原视频url、time时间戳、ua的值、sign和keyen函数所需要的参数的值(即变量vkey的值)
2、根据1中的值,取得api.php请求所需的各参数的值,模拟请求,以取得视频的直连链接。
2.3 抽离sign和keyen方法至独立的js
因为要获取模拟请求的参数,必须用到sign和keyen两个方法,因此需要将其提取到单独的js文件中,以供python调用。
把鼠标移动到366行的sign函数上,查看函数,并进入函数内部。可知,其在匿名的js中。将该匿名的js内容,全部拷贝到新建文件crypto.js中。如下图:


d08.png (988.49 KB, 下载次数: 0)
下载附件
2023-3-1 17:12 上传

将鼠标移动到367行的keyen函数上,并跳入函数内部,可知,该函数位于html内部的javascript中,因此,单独将此函数复制到crypto.js文件中。如下图:


d09.png (1.1 MB, 下载次数: 0)
下载附件
2023-3-1 17:19 上传

观察函数keyen函数内部,第一行的 _0x9205 的值,即为上一节中所说的生成字符串的函数。因此还需要将此函数拷贝至 crypto.js文件中。
在 _0x9205 函数内部,第一行的 _0x46e8 的值,是一个固定的字符串数组,猜测是用来生成函数的。需要将此数组拷贝至 crypto.js中。
经过一番操作,keyen和sign函数具备了一切的运行条件。下面来测试一下:
过程很简单,新建一个html文件,加载crypto.js,在web调试器中,手动调用sign和keyen函数,发现sign没有问题,但keyen生成的值,与原网页中的不同。最后发现,是 _0x46e8 字符串数组的问题。该数组需要用代码排序调整。用于排序调整的函数是一个匿名函数,将该匿名函数也拷贝至 crypto.js中。如图10


d10.png (2.55 MB, 下载次数: 0)
下载附件
2023-3-1 17:14 上传

至此,sign和keyen两函数的js文件,可单独使用了。
3 python实现模拟请求获取视频链接
我们需要模拟哪些请求呢?首先是获取html,从中解析出api.php请求需要的参数,然后发送api.php请求,得到视频数据
3.1 get请求获取html,提取需要的数据
使用pyhon的requests模块,发送get请求,获取html数据,请求地址如下:
aHR0cHM6Ly9qeC54bWZsdi5jb20vP3VybD1odHRwczovL3YucXEuY29tL3gvY292ZXIvbXpjMDAyMDA3a25taDNnL2IwMDQ1Zm1uNWl2Lmh0bWw=
前文我们提到过,需要配置userAgent, 因此,我们在请求头中,模拟userAgent的值,假装是浏览器访问。
很容易拿到html数据,提取我们需要的 time、ua、vkey的值。
这里使用pyhton的三方框架 BeautifulSoup,该框架可用于解析html数据、xml等
这里介绍下提取的过程。
a、首先使用BeautifulSoup框架,提取出所有的js的代码Tag。Tag可理解为html的标签。
如下代码即:
解析html文本,得到BeautifulSoup的对象。
查询出所有的script标签以及内容。
soup = BeautifulSoup(r.text, 'html.parser')
scriptResults = soup('script',{'type' : 'text/javascript'})
b、循环所有的script标签,通过正则匹配提取数据(我们知道需要的参数值都在script的代码内)。
看下关键的正则代码:
getVkeyValue是提取文本中的vkey的值,匹配规则如下:
先匹配以:"var vkey = ' "开头,中间文本为数字字母-长度不多于64位,末尾以单引号 ' 结束的字符串。
然后再将匹配的值进行第二次提取,二次匹配就以单引号开始,单引号结束,长度不多于64位。最后去除单引号,就得到了vkey的值。
getTimeValue是提取文本中的time的值,匹配规则如下:
先匹配以:"var time = ' "开头,中间是数字,至少1个,以单引号结束的文本。
然后将匹配到的值进行第二次提取,直接提取以单引号开头,至少一个的数字,以单引号结尾,再去除单引号,就得到了time的值。
def getVkeyValue(str) :
        pattern = r"var\s+vkey\s+=\s+'[\w]{0,64}'"
        pattern2 = r"'[\w]{0,64}'"
        ret = getPatternValue(pattern,str)
        if ret != None :
            ret = getPatternValue(pattern2,ret)
            if ret != None :
                return ret.replace("'","")
        return None
def getTimeValue(str) :
    pattern = r"var\s+time\s+=\s+'[\d]+'"
    pattern2 = r"'[\d]+'"
    ret = getPatternValue(pattern,str)
    if ret != None :
        ret = getPatternValue(pattern2,ret)
        if ret != None :
            return ret.replace("'","")
    return None
如下图


d11.png (1.65 MB, 下载次数: 0)
下载附件
2023-3-1 17:12 上传

3.2 通过api.php请求,取得视频信息
在发送请求之前,我们先准备请求的参数。
我们知道有5个参数:url、ua、time、key、vkey
其中url我们已知,ua写死"1",time从html中取得
key需要通过sign获取,vkey通过keyen函数获取。参数为变量vkey的值,已从html中取得。
下面看如何 调用sign函数与keyen函数。
我们需要python调用js方法,这里用到 python的execjs框架,用于执行js方法。
我们新建一个py文件,封装下js的方法调用。代码如下:
内容很少,首先加载crypto.js文件,编译文件得到js对象,通过js对象调用内部方法。
这里封装了2个方法,getSign和getVKey,用来获取key和vkey参数的值。
import execjs
def get_js():
    f = open("crypto.js", 'r', encoding='UTF-8').read()
    # f = open(r"CryptoJS.js").read()
    return f
js = execjs.compile(get_js())
def getSign(key) :
    return js.call('sign', key)
def getVKey(key) :
    return js.call('keyen',key)
下面看看发送请求的代码
注意几点:请求的header,与获取html请求相同。
请求url固定值,参数调用函数获取。post请求响应结果为json格式。从中取得视频链接,如下图:


d12.png (1.22 MB, 下载次数: 0)
下载附件
2023-3-1 17:12 上传

至此,我们就用python打通了获取视频地址的全部流程。接下来实现解析的同时,添加下载功能。
4 下载与批量下载
4.1 单个视频下载过程
我们有了视频的直连地址,mp4类型。下载就很容易了。
找了一段python下载视频的开源代码,复制保存到down.py文件中,提供两个方法,同步和异步。看下函数名称:
# 单线程下载
def downloadByUrl(url, downloadPath, fileName):
# 异步下载
async def async_download_from_url(url, downloadPath, fileName):
3个参数:下载url、保存路径、文件名。
路径和文件名可自行设置。
取得视频地址后,直接调用下载方法进行下载。
这里将视频地址提取与下载过程,一同封装在了main.py中,这样在下载时,只需要指定视频的地址以及视频名称,就可以下载了。
这里我写了一个单独下载的pyhton代码。内容很简单,执行py代码时,要求输入url与名称。如下图:


d13.png (1.46 MB, 下载次数: 0)
下载附件
2023-3-1 17:12 上传

上图中的命令如下:
python3 vd.py https://v.qq.com/x/cover/mzc002007knmh3g/b0045fmn5iv.html 三体1
vd.py中的代码如下:
import sys
import main
# print(len(sys.argv))
if len(sys.argv) != 3 :
    print('请输入视频地址和视频名称')
    exit()
url = sys.argv[1]
name = sys.argv[2]
if url != None:
    main.downVideo(url,name)
else:
    print('请输入视频的地址和名字')
4.2 电视剧全集下载
这里为了方便,没有使用多线程,是批量循环下载。此处主要解决的是 批量提取所有剧集的url,以及可以指定开始下载的剧集。
如何提取所有剧集的url呢?
首先打开某电视剧的地址,这里以《大决战》为例,地址如下:
https://v.qq.com/x/cover/mzc00200ckuum3m/f0039n2aiuh.html
打开web调试器,定位到剧集的div标签。然后复制所有剧集的div内容,存储到新建文件 djz.html 中。请看下图:


d14.png (1.67 MB, 下载次数: 0)
下载附件
2023-3-1 17:13 上传



d15.png (1.17 MB, 下载次数: 0)
下载附件
2023-3-1 17:13 上传

接下来,通过html解析,得到所有的url和剧集名称,每得到一个就下载,完成后,继续下一个,代码如下图:


d16.png (1.16 MB, 下载次数: 0)
下载附件
2023-3-1 17:13 上传


执行下载《大决战》全集命令:
python3 parse_vd.py
若想从第10集开始下载,则在后面跟上开始的集号即可,命令如下:
python3 parse_vd.py 10
看下执行效果图:


d17.jpg (63.09 KB, 下载次数: 0)
下载附件
2023-3-1 17:13 上传

若想下载其他剧集,则按照上方步骤,找到所有的剧集的div。然后提取url。提取的规则可能会有变化。
至此,就实现了批量下载全集。
5 Demo及文件说明
demo下载地址:
链接: https://pan.baidu.com/s/1E4qxsyhbDVLE1rO7pgOQFQ?pwd=8ykf 提取码: 8ykf
demo使用:(仅限企鹅视频)
1、下载demo
2、进入vdDemo目录,依次安装依赖包,命令如下:
pip3 install beautifulsoup4 -i https://pypi.tuna.tsinghua.edu.cn/simple
pip3 install PyExecJS -i https://pypi.tuna.tsinghua.edu.cn/simple
pip3 install cryptography -i https://pypi.tuna.tsinghua.edu.cn/simple
pip3 install cryptography -i https://pypi.tuna.tsinghua.edu.cn/simple
pip3 install aiohttp -i https://pypi.tuna.tsinghua.edu.cn/simple
pip3 install uvloop -i https://pypi.tuna.tsinghua.edu.cn/simple
pip3 install pymysql -i https://pypi.tuna.tsinghua.edu.cn/simple
3、下个单个视频命令:
python3 vd.py 企业视频url 视频名称
例如:
python3 vd.py https://v.qq.com/x/cover/mzc002007knmh3g/b0045fmn5iv.html 三体
4、批量下载
准备好视频全集的html文件,这里以djz.html为例。
在parse_vd.py中,替换djz.html为你保存的全集的div的html文件。
《大决战》全集下载示例:
python3 parse_vd.py
demo文件说明:


d18.png (458.86 KB, 下载次数: 0)
下载附件
2023-3-1 17:13 上传

6 总结
程序并不完美,因解析网址会有时效性,所以有时会超时。此外,如果网速太慢,下载时间过长,链接也会失效。
此时批量下载会中断。大家如有需要,可自行二次优化。
本次实现,难度一般,不过整理成文章确实较难的,能实现,但不一定能写清楚。这也是我第一次使用python。
在菜鸟教程上看了python的基础语法,也是边写边百度。不得不说,python真的是很高级。数据解析几行代码搞定。
希望此文,可以为大家带来收获,因本人水平有限,文中有不对之处,望指出。非常感谢!

函数, 视频

billsmiless
OP
  


云的彼岸918 发表于 2023-3-4 17:35
删掉了好像也是不行啊,大佬。是不是我删错了?能给个删除后的代码吗?谢谢

链接: https://pan.baidu.com/s/17sgRb_U8FoRBx-QHuIP4sg?pwd=8u2w 提取码: 8u2w
优化了下,win上应该可以了,我没试。可以的话,兄弟们告诉我下。
ysjd22   

uvloop  安装不了。。。
石斧开天   

楼主,感谢分享
不限速蓝奏分流
下载:https://wwi.lanzoup.com/igjkr0p14tmh 密码:6v4b
lyiann   

uvloop无法安装:
RuntimeError: uvloop does not support Windows at the moment
ruikai   

你别写这么简单,你这样我就以为我也会
beiank   

感谢楼主分享好工具  我也运行一下
lmds   

学习了,太强了
bzj1975   

学习了,太强了
lcy925   


ruikai 发表于 2023-3-2 11:25
你别写这么简单,你这样我就以为我也会

对,求带飞
您需要登录后才可以回帖 登录 | 立即注册

返回顶部