QQ 音乐网页端爬虫之心酸历程。。
江枫渔火对愁眠
余生还长,学会放弃。。
2020 年了,QQ 音乐大部分的歌曲都要登录后才能听了。。
访问 QQ 音乐首页,获取 Cookies,里面有个参数过后会用到:
Cookie: pgv_pvid=1535153710
随便找一首不用 客户端 在网页上就能听得歌曲。比如:《少年》--梦然,进入此歌曲的首页:
https://y.qq.com/n/yqq/song/000S7TGL43hhBO.html
F12 -> 在 html 里可以得到歌曲的 "songmid":"000S7TGL43hhBO" (经验肯定有用)
这么看来,歌曲的首页就是:
’https://y.qq.com/n/yqq/song/{}.html’.format(songmid)
点击播放,进入了 https://y.qq.com/portal/player.html 页面,直接 Network->Media,发现出来好多以 “C40” 开头的链接,依次访问,发现除了下述链接其余的都好像啥也不是:
http://ws.stream.qqmusic.qq.com/C400000S7TGL43hhBO.m4a?guid=1535153710&vkey=694BA38A20BC43A90259C8A26B6C44C835976837AEB3D162EA6119795BAD1307DEA9479C049E92524A59A06B2E28CDB8294541B2C992E608&uin=6025&fromtag=66
此链接直接访问即可播放歌曲,并下载。
分析上述链接。
第 1 部分的域名应该是所有歌曲都一样;第 2 部分的格式为:C40 + songmid + .m4a?;第 3 部分为:guid=pgv_pvid;第 4 部分未知;第 5 和第 6 部分未知。
为了对比,找另一首的链接,下述是《后来遇见他》--胡66 的链接们:
{
歌曲首页:https://y.qq.com/n/yqq/song/000OjsEW0QrPAd.html,
songmid:000OjsEW0QrPAd,
点击播放得到歌曲播放链接:
http://ws.stream.qqmusic.qq.com/C400000Umkct2IjBZg.m4a?guid=1535153710&vkey=5DAF1A0B1552B9899C83BA1084027176FB6972FC7899E5786816C37ECC71B414BEA474674CB128ACD1D50BE9C4B02A6C2316DF7C133A20F7&uin=6025&fromtag=66
}
TMD!发现播放链接的第2部分尽然不符合 “C40 + songmid + .m4a?” 的规律,WTF!?
又找了另一首,《全世界最好的你》--许嵩 的链接们:
{
歌曲首页:https://y.qq.com/n/yqq/song/0049CXGa1b1IKI.html,
songmid:00049CXGa1b1IKI,
点击播放得到歌曲播放链接:
http://ws.stream.qqmusic.qq.com/C4000049CXGa1b1IKI.m4a?guid=1535153710&vkey=34DF57DAEEF25C4728710367EAC2DA9E617661F59E94D6006B530BBB2BABA3ABB1184AC8FEC73E66E73A9601F1AB6D62E00ACD74F7C988FF&uin=6025&fromtag=66
}
发现满足之前推到的规律了。。那就拿满足推到规律的两首对比,第1、2、3、5、6部分的值或规律相同。即暂时可得:歌曲的播放链接公式为:
‘http://ws.stream.qqmusic.qq.com/C40{0}.m4a?guid={1}&vkey={2}&uin=6025&fromtag=66’.format(songmid, pgv_pvid, vkey)
删除和修改 uin 和 fromtag 两个参数则链接无效,则播放链接不能精简化,先不考虑这两个参数。
寻找关键的参数 vkey,vkey 估计是其他请求得到的 Preview 响应,复制其值后查寻即可找到:
所以,通过下述链接便是可获得参数 vkey:(链接名中getplaysongvkey 也体现了这点)
https://u.y.qq.com/cgi-bin/musics.fcg?-=getplaysongvkey9325680807500534&g_tk=1461878732&sign=zzagurwtxwgqmd2e01f47b10c38892faf723c7d589d44&loginUin=QQ号&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq.json&needNewCode=0&data=%7B%22req%22%3A%7B%22module%22%3A%22CDN.SrfCdnDispatchServer%22%2C%22method%22%3A%22GetCdnDispatch%22%2C%22param%22%3A%7B%22guid%22%3A%221535153710%22%2C%22calltype%22%3A0%2C%22userip%22%3A%22%22%7D%7D%2C%22req_0%22%3A%7B%22module%22%3A%22vkey.GetVkeyServer%22%2C%22method%22%3A%22CgiGetVkey%22%2C%22param%22%3A%7B%22guid%22%3A%221535153710%22%2C%22songmid%22%3A%5B%220049CXGa1b1IKI%22%5D%2C%22songtype%22%3A%5B0%5D%2C%22uin%22%3A%22QQ号%22%2C%22loginflag%22%3A1%2C%22platform%22%3A%2220%22%7D%7D%2C%22comm%22%3A%7B%22uin%22%3AQQ号%2C%22format%22%3A%22json%22%2C%22ct%22%3A24%2C%22cv%22%3A0%7D%7D
PS:QQ号已隐藏
请求后的部分结果为:
发现 json 中的 purl 参数,直接就是歌曲播放链接的第 1 部分域名后的全部,意外收获。这样便可以不用管上述的 uin 和 fromtag 了。则通过请求此链接后得到的 purl 参数,再加上域名就是歌曲的播放链接了。
对于域名的选择,QQ音乐提供了 5 个域名,每个域名都可以获取文件,这是一种集群的管理方式。在 req 的 sip 下可以找到具体的5个域名:
所以域名无所谓了,就用 http://ws.stream.qqmusic.qq.com/ 得了。
经上述分析,最终推导出歌曲的下载(播放链接) URL :http://ws.stream.qqmusic.qq.com/ + purl
将上述获取到 purl 参数的 URL 逐一剔除些其它参数尝试访问,最后精简为只保留 sign 和 data:
https://u.y.qq.com/cgi-bin/musics.fcg?sign=zzagurwtxwgqmd2e01f47b10c38892faf723c7d589d44&data=%7B%22req%22%3A%7B%22module%22%3A%22CDN.SrfCdnDispatchServer%22%2C%22method%22%3A%22GetCdnDispatch%22%2C%22param%22%3A%7B%22guid%22%3A%221535153710%22%2C%22calltype%22%3A0%2C%22userip%22%3A%22%22%7D%7D%2C%22req_0%22%3A%7B%22module%22%3A%22vkey.GetVkeyServer%22%2C%22method%22%3A%22CgiGetVkey%22%2C%22param%22%3A%7B%22guid%22%3A%221535153710%22%2C%22songmid%22%3A%5B%220049CXGa1b1IKI%22%5D%2C%22songtype%22%3A%5B0%5D%2C%22uin%22%3A%22QQ号%22%2C%22loginflag%22%3A1%2C%22platform%22%3A%2220%22%7D%7D%2C%22comm%22%3A%7B%22uin%22%3AQQ号%2C%22format%22%3A%22json%22%2C%22ct%22%3A24%2C%22cv%22%3A0%7D%7D
解码一下:
https://u.y.qq.com/cgi-bin/musics.fcg?sign=zzagurwtxwgqmd2e01f47b10c38892faf723c7d589d44&data={"req":{"module":"CDN.SrfCdnDispatchServer","method":"GetCdnDispatch","param":{"guid":"1535153710","calltype":0,"userip":""}},"req_0":{"module":"vkey.GetVkeyServer","method":"CgiGetVkey","param":{"guid":"1535153710","songmid":["0049CXGa1b1IKI"],"songtype":[0],"uin":"QQ号","loginflag":1,"platform":"20"}},"comm":{"uin":QQ号,"format":"json","ct":24,"cv":0}}
推导出获得 purl 参数的链接公式为:
‘https://u.y.qq.com/cgi-bin/musics.fcg?sign={}&data={}’.format(sign, data)
其中暂时认为 data 的值对于不同的歌曲应该只有 songmid 这一项不一样。因为难点在于这个 sign 是什么鬼。。删除或更改都不能获得数据,看来不是随便的参数。又试了另一首歌曲,发现 sign 不同,而data 里只有 songmid 不同。以为 sign 的值也是通过其他请求得出的返回值,可是竟然搜不到。。
a. 尝试 sign 相同,但是 data 里的 songmid 不同:只更改 songmid 后,返回的是:
{"code":2000,"ts":1586694918093,"req":{"code":2000},"req_0":{"code":2000}}
b. 同一首歌曲,刷新网页后,sign 改变,但之前的链接在一段时间内还有效。。
总结:也就是说,同一首歌曲的无论请求多少次,不同的 sign 值可以和自己的 songmid 组合成有效的请求;但是用其他歌曲的 sign 值和自己的 songmid 则合成的请求无效。说明:一个 songmid 有多个 sign ?不同的 songmid 无法公用 sign ?
放弃了。。
在网上搜了一圈,都是 2019 年之前的文章了,本文写于 2020-04-12,但是但是但是,发现了个神奇的现象,借鉴 2019 年的文章,当时博主们获取 purl 或 vkey 的接口是:https://u.y.qq.com/cgi-bin/musicu.fcg?
如今的接口是:https://u.y.qq.com/cgi-bin/musics.fcg?
musicu 和 musics 的区别,之前的接口直接访问会是:{"code":-500001,"ts":1586690735276};如今的接口直接访问是:{"code":2000,"ts":1586690776045}。显示之前的接口应该已经作废了。
2019 年的博主们最后简化的接口为:
‘https://u.y.qq.com/cgi-bin/musicu.fcg?data={}’.format(data)
没有 sign 这个参数,并且文章中亦能看出简化前也是没有 sign 这个参数的。
无聊变拿 2019 年的接口组合了一下(虽然显示已经作废了),结果!!!真特么有返回!!!例如拿 songmid 为0049CXGa1b1IKI 尝试:
https://u.y.qq.com/cgi-bin/musicu.fcg?data={"req":{"module":"CDN.SrfCdnDispatchServer","method":"GetCdnDispatch","param":{"guid":"1535153710","calltype":0,"userip":""}},"req_0":{"module":"vkey.GetVkeyServer","method":"CgiGetVkey","param":{"guid":"1535153710","songmid":["0049CXGa1b1IKI"],"songtype":[0],"uin":"QQ号","loginflag":1,"platform":"20"}},"comm":{"uin":QQ号,"format":"json","ct":24,"cv":0}}
返回:
这意味着,只要知道歌曲的 songmid 便可以构造出 data,再传给 2019 年的接口就可以得到 purl,进而再和域名组合即可。。
但是 周杰伦 的就不行,可能是因为仅限客户端播放吧。。
而且如今 QQ 音乐听歌需要登录,所以 data 中有两个 uin,值为 QQ号,但用 2019 年的接口,把 uin 都设为 0 也是可以的,也就是无登录状态。看来 2020 年变化挺多啊。。
获取 purl 参数的 2019 年的 URL,经测试可以无登录状态使用:guid(cookies)和 uin(QQ 号)都可以设为 0 。
a. 获取歌曲的 songmid (不难)
b. 构造 data:{"req":{"module":"CDN.SrfCdnDispatchServer","method":"GetCdnDispatch","param":{"guid":"0","calltype":0,"userip":""}},"req_0":{"module":"vkey.GetVkeyServer","method":"CgiGetVkey","param":{"guid":"0","songmid":["值"],"songtype":[0],"uin":"0","loginflag":1,"platform":"20"}},"comm":{"uin":0,"format":"json","ct":24,"cv":0}}
c. 构造 2019 年的接口:https://u.y.qq.com/cgi-bin/musicu.fcg?data=值
d. 访问构造好的接口,获取到歌曲此时对应的 purl
e. 通过 “域名 + purl” 爬取歌曲。
1. https://blog.csdn.net/ghl1390490928/article/details/83384511
2. https://blog.csdn.net/ghl1390490928/article/details/86888063
3. http://www.imooc.com/article/284877
感谢!
心力交瘁,如被踩入泥里的落叶。
2020-04-12 凌晨
已知能力有限,但想放弃得漂亮些。。
移动端页面的爬虫难度要比 PC 端低一些。。
谷歌浏览器,F12。调试成移动端,网址已成移动端的了。
依旧拿 《少年》--梦然 来试水,一顿操作后直接就能提取到播放链接了:
http://aqqmusic.tc.qq.com/amobile.music.tc.qq.com/C400000S7TGL43hhBO.m4a?guid=25759886&vkey=64F550A6ECEDA3C75C55BEA2C38E7457BD874F23D860AD325BCD9E51FF86C4430A1D00682E127066084663FF249DA2B612C6B64CA144946A&uin=0&fromtag=38
经验和结论:http://aqqmusic.tc.qq.com/amobile.music.tc.qq.com/ + purl
还是拿 vkey 的值复制查找,结果。。直接是在一个 GET 请求里找到的,这个 GET 请求里包含了完整的歌曲播放路径。。
完整的播放链接在一个 src 中,好吧。
https://i.y.qq.com/v8/playsong.html?songmid=000S7TGL43hhBO&ADTAG=myqq&from=myqq&channel=10007100
精简后的公式为:'https://i.y.qq.com/v8/playsong.html?songmid={}'.format(songmid)
所以,结束了。只要知道歌曲的 songmid,变可以直接通过访问移动端的链接后取到的 html 中提取出完整的播放链接。
a. 获取歌曲的 songmid (不难);
b. 伪造成移动端访问,例如:
user-agent:
Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1
c. 直接 GET 移动端链接,从返回的 html 中提取出歌曲的播放 URL。
林尽水源,便得一山,山有小口,仿佛若有光。
2020-04-13 白日