@(博客)[QQ|会员音乐|下载]python
018.8.8git
1. 因为本人是法盲,因此是否涉及侵权QQ音乐不知;如若侵权,相告即删
2. 相关代码仅做参考学习,不用于商业目的github
原本想先编个故事再进入正文的,这符合个人风格。但因为要下载QQ音乐的VIP歌曲,代码方面不难,而是分析文件的过程有点绕。我已经以为这个过程我会说不清楚,继而意兴阑珊,故事什么的就了无趣味了chrome
QQ音乐中VIP才能下载的歌曲浏览器
主要使用的库:
- requests
向服务器发起请求
- urllib
构建url地址
- re
提取须要的数据服务器
首先咱们来到QQ音乐的网页端,播放一首歌曲,这里就以【小半】为例
工具
利用chrome的开发者工具,勾选Preserver log,而且选中Media,刷新页面
学习
刷新页面
测试
此时会发现有这么一个不知道什么的文件出现,暂且称之为文件A。右下角红色方框内是请求这个数据时带上的query参数url
点进来以后会发现其实这就是咱们须要的歌曲文件
因此如今的问题成了如何请求文件A。咱们已经有了请求参数,也能够找到服务器的接口
根据反复测试,发现只有关键字vkey的值在发生变化,因此只要咱们获取了动态变化的vkey值,拿到文件A就易如反掌了
经过开发者工具,我找到了一个JS文件,暂且称之为文件B,它在歌曲文件以前被请求,而且其返还数据里面有vkey值
咱们也发现,须要请求这个文件,须要的query参数不可谓少
一样,在反复测试之后会发现,songmid的值会根据歌曲的不一样而发生改变;filename的值是在songmid值的左边加上C400,右边加上.m4a
因而问题变成了如何获取songmid的值
继续顺藤摸瓜前边的文件,在一个JS文件,暂且称之为文件C中找到了
仔细分析会发现,关键字list是包含了【小半】所在专辑《小梦大半》里面的所有歌曲,而还有个关键字singername是歌手名字,为了确保咱们下载的歌曲是咱们想要的歌手唱的,因此我用正则提取出来。针对list,个人方法是将整个专辑中全部歌曲的songmid以及歌曲的名字所有提取出来,而后再从中确认咱们须要的songmid
# 提取歌手名字
SINGER = re.search(r'"singername":"(.+?)"', data).group(1)
# 提取专辑中全部的songmid,以及对应的歌曲名字
results = re.findall(r'"songmid":"(\w+?)","songname":"(.+?)"', data)
# 咱们知道,经过findall()方法获得的结果是由元组组成的列表(如:[(songmid1, songname1), (songmid2, songname2),...]),因此对其遍历,当歌曲名字SONGNAME在这个元组里边时,返回对应的songmid
if results:
for result in results:
if SONGNAME in result:
return result[0]
else:
return None
else:
return None
而如何得到这个文件呢?
能够看到,获取这个文件的关键点是albummid的值
来到QQ音乐的搜索界面
当咱们在搜索框中键入文字之后点击右边的搜索按钮,会发现浏览器接收到一个文件,我称之为文件D
文件D中的list里边就包含了咱们搜索出来的结果,由于存在歌曲同名啊,翻唱之类的,因此通常list里边都包含多个值,而通常状况下,比较火的歌,且在QQ音乐中有版权的,都会存放在第一个(若是有其余目的,可自行在list的数据中进行取舍),这里我就只取出第一个
# 提取albummid的值
result = re.search(r'"mid":"(\w+?)"', data)
if result:
return result.group(1)
else:
return None
文件D的请求方式就比较简单了
尽管须要的参数不少,但最重要的就是w了,它对应的是歌曲名字
此时再回去看看请求文件A的接口,其实有一部分就是文件B中的关键字filename所对应的值,因此咱们对这个接口要动态改变
# 构建下载歌曲的query参数
PARAMS_FOR_VIPSONG["vkey"] = vkey
url = parse.urljoin(URL_FOR_VIPSONG, "C400"+songmid+".m4a?")
分析是从里到外,找到的文件是A->B->C->D;而代码的执行顺序应该是从外到里,请求文件的顺序是D->C->B->A
如下是我代码的主要结构
为了更加友好,我另写了一个main.py的文件,来提示程序的用法
代码运行效果以下
完整代码已上传Github(有详细注释)