**1、**此处以下载《李荣浩—年少有为》为目标歌曲,首先我们来到QQ音乐网页版搜索歌曲:https://y.qq.com/portal/search.html#page=1&searchid=1&remoteplace=txt.yqq.top&t=song&w=年少有为 ,搜索到目标歌曲有许多版本。点击第一首来到播放页面,如下图操作打开chrome的开发者工具后切换到Network,重新刷新页面,找到请求到这首歌曲播放源的URL。
将上图的链接:http://dl.stream.qqmusic.qq.com/C400004DXFlC0nsTCZ.m4a?guid=602087500&vkey=3077FD9F94F9BFB3DF0544C32123C188D12042C45C22FDE8B19694D79C68A6A045EEB7F9D11598249BF43F7649684353E5DE8DEF10A50417&uin=0&fromtag=66 在另一个标签页打开可以听到该歌曲正常地播放,因此这个就是我们需要找的播放源。为了获取这个播放源数据,我们得先制造这个歌曲资源的URL,而这个歌曲资源的URL里vkey(秘钥)是由QQ音乐专门的秘钥服务器生成的,这时候就要去找哪个文件里保存了服务器返回的秘钥。
**2、**将上图的Network中切换到XHR,按下图经过一番查看你会看到这个文件
其中就有我们所需要的数据,且可以看到我们只要能够提取出purl再和http://dl.stream.qqmusic.qq.com/ 进行拼接就能得到播放源的数据了。在上图文件中我们切换到Headers查看其请求的URL https://u.y.qq.com/cgi-bin/musicu.fcg?-=getplaysongvkey3279590927017204&g_tk=5381&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq.json&needNewCode=0&data={“req”%3A{“module”%3A"CDN.SrfCdnDispatchServer"%2C"method"%3A"GetCdnDispatch"%2C"param"%3A{“guid”%3A"602087500"%2C"calltype"%3A0%2C"userip"%3A""}}%2C"req_0"%3A{“module”%3A"vkey.GetVkeyServer"%2C"method"%3A"CgiGetVkey"%2C"param"%3A{“guid”%3A"602087500"%2C"songmid"%3A[“004DXFlC0nsTCZ”]%2C"songtype"%3A[0]%2C"uin"%3A"0"%2C"loginflag"%3A1%2C"platform"%3A"20"}}%2C"comm"%3A{“uin”%3A0%2C"format"%3A"json"%2C"ct"%3A24%2C"cv"%3A0}}
,会看到比较乱,这是因为这个URL被编码了,我用chrome插件按下图操作对其进行解码:
解码后的URL为:https://u.y.qq.com/cgi-bin/musicu.fcg?-=getplaysongvkey3279590927017204&g_tk=5381&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq.json&needNewCode=0&data={“req”:{“module”:“CDN.SrfCdnDispatchServer”,“method”:“GetCdnDispatch”,“param”:{“guid”:“602087500”,“calltype”:0,“userip”:""}},“req_0”:{“module”:“vkey.GetVkeyServer”,“method”:“CgiGetVkey”,“param”:{“guid”:“602087500”,“songmid”:[“004DXFlC0nsTCZ”],“songtype”:[0],“uin”:“0”,“loginflag”:1,“platform”:“20”}},“comm”:{“uin”:0,“format”:“json”,“ct”:24,“cv”:0}} 。为了获取到purl我们需对这个URL进行一些测试,看下这些参数是否都必须要有,进过多次测试我得到简化后的URL为:https://u.y.qq.com/cgi-bin/musicu.fcg?data={“req_0”:{“module”:“vkey.GetVkeyServer”,“method”:“CgiGetVkey”,“param”:{“guid”:“602087500”,“songmid”:[“004DXFlC0nsTCZ”],“songtype”:[0],“uin”:“0”,“loginflag”:1,“platform”:“20”}},“comm”:{“uin”:0,“format”:“json”,“ct”:24,“cv”:0}} ,其返回结果如下图:
通过对比不同歌曲的URL发现其中的songmid不同外其它基本相同,但guid不太确定是否会一直不变,因此我按下图进行全局搜索找到定义该guid的文件。
可以看到guid其实是随机生成的,所以我们可以一直用一个固定值,此处就一直用上面URL中的guid值,因此我们只需获得songmid即可。
**3、**为了找到songmid,我们回到一开始的搜索页面https://y.qq.com/portal/search.html#page=1&searchid=1&remoteplace=txt.yqq.top&t=song&w=%E5%B9%B4%E5%B0%91%E6%9C%89%E4%B8%BA ,如下图操作打开chrome的开发者工具后切换到Network,重新刷新页面,找到songmid的值。
可以看到songmid的值就是mid,切换到Headers查看请求的URL为:https://c.y.qq.com/soso/fcgi-bin/client_search_cp?ct=24&qqmusic_ver=1298&new_json=1&remoteplace=txt.yqq.song&searchid=55391003577889450&t=0&aggr=1&cr=1&catZhida=1&lossless=0&flag_qc=0&p=1&n=20&w=年少有为&g_tk=5381&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq.json&needNewCode=0 ,对这个URL进行一些测试,看下哪些参数是否都必须要有,进过多次测试我得到简化后的URL为:https://c.y.qq.com/soso/fcgi-bin/client_search_cp?new_json=1&remoteplace=txt.yqq.song&t=0&aggr=1&cr=1&w=年少有为&format=json&platform=yqq.json
,也就是说我们只需更改歌曲名即可,简化后的结果如下图。
**4、**综合上述分析,我们开始敲代码,以下是我的全部源码,50行不到:
from requests_html import HTMLSession
import urllib.request,os,json
from urllib.parse import quote
class QQ_Music():
def __init__(self):
self.get_music_url='https://c.y.qq.com/soso/fcgi-bin/client_search_cp?new_json=1&remoteplace=txt.yqq.song&t=0&aggr=1&cr=1&w={}&format=json&platform=yqq.json'
self.get_song_url='https://u.y.qq.com/cgi-bin/musicu.fcg?data={"req_0":{"module":"vkey.GetVkeyServer","method":"CgiGetVkey","param":{"guid":"602087500","songmid":["%s"],"songtype":[0],"uin":"0","loginflag":1,"platform":"20"}},"comm":{"uin":0,"format":"json","ct":24,"cv":0}}'
self.download_url='http://dl.stream.qqmusic.qq.com/'
if not os.path.exists("d:/music"):
os.mkdir('d:/music')
def parse_url(self,url):
session = HTMLSession()
response = session.get(url)
return response.content.decode()
def get_music_list(self,keyword):
music_dirt=json.loads(self.parse_url(self.get_music_url.format(quote(keyword))))
music_list=music_dirt['data']['song']['list']
# print(music_list)
song_list=[]
for music in music_list:
sing_name=music['singer'][0]['name']
song_name=music['title_hilight'].replace(r"", "").replace("", "")
song_list.append({'songmid':music['mid'], 'singer':sing_name,'song_name':song_name})
print(str(len(song_list))+'、'+sing_name+'--'+song_name)
return song_list
def download(self,song):
song_dirt = json.loads(self.parse_url(self.get_song_url%song['songmid']))
download_url = song_dirt["req_0"]["data"]["midurlinfo"][0]["purl"]
if download_url:
try:
# 根据音乐url地址,用urllib.request.retrieve直接将远程数据下载到本地
urllib.request.urlretrieve(self.download_url+download_url, 'd:/music/' + song['song_name'] + '.mp3')
print('Successfully Download:' + song['singer']+'--'+song['song_name'] + '.mp3')
except:
print('Download wrong~')
if __name__ == '__main__':
qqmusic=QQ_Music()
while True:
keyword = input('请输入要下载的歌曲名:')
print('-----------歌曲《' + keyword + '》的版本列表------------')
music_list = qqmusic.get_music_list(keyword)
song_num = input('请输入要下载的歌曲序号:')
qqmusic.download(music_list[int(song_num) - 1])