购买了一年的QQ音乐绿钻豪华版,还有几天就到期了,虽然平时听音乐比较少,但是还比较喜欢听歌曲的。计划会员到期前下载一些音乐文件,继续针对QQ音乐网站源码分析和歌曲下载链接的进行研究。
平时通过APP和软件播放歌曲也是趋势,所以在QQ音乐Web网站显示的歌曲数量有限。但是还是可以下载一些歌曲。通过分析和思考后,决定把QQ音乐网站所有歌星和所有音乐榜单的歌曲进行下载,其中每个歌星最多10首热门歌曲,每个榜单也就是近期热门Top20的歌曲。如果歌曲有MV文件也同步下载。所有爬虫代码已上传网盘,可以关注公众号【站在前沿】,回复qqmusic,获取网盘下载链接。有其它需求也可以在微信公众号留言。
下载技术使用Python爬虫和多线程技术,在分析页面源码的过程中,主要有三分之二的时间在解决sign参数加密的问题,而且要通过执行js脚本的方式进行解决,如果不能下载,重新拷贝新js脚本程序,QQ网站js脚本有可能已更新。QQ音乐网站资料使用了Webpack打包技术,也学习了webpack逆向分析技术来解决。先看一下音乐下载程序的运行效果,如下图
一、网站代码分析截图
歌手页面默认显示第一页80个歌星,滚动鼠标可以加载第二页80个和更多页。
sign加密分析,需要抓取和分析js代码
获取歌星热门歌曲列表,主要获取歌曲ID和歌曲名称
下载歌曲页面代码分析,也需要使用加密函数返回sign才可以下载
二、部分核心代码
爬虫核心代码
# 获取明星人员列表,根据音乐类型
def getStars(genreID, genreName):starList = []url = 'https://y.qq.com/n/ryqq/singer_list?index=-100&genre=' + str(genreID) + '&sex=-100&area=-100'logging.info('-------------------艺术类型 ' + genreName + '-------------------')res = until.getResText(url, headers)# print(res)stars = re.findall('<a href="/n/ryqq/singer/(.*?)">(.*?)</a>', res)for starid, star in stars:starList.append((starid, star))stars = re.findall('<a class="singer_list_txt__link js_singer" title=".*?" href="/n/ryqq/singer/(.*?)">(.*?)</a>',res)for starid, star in stars:starList.append((starid, star))urlMusic = 'https://u.y.qq.com/cgi-bin/musics.fcg'index = 2while True:logging.info('计划获取第' + str(index) + '页明星人员姓名')param = {'_': time.time() * 1000,'sign': 'zzb32ecc024u4twyf1jekdcip0smo3psq506afc8e','data': ''}param['data'] = '{"comm":{"cv":4747474,"ct":24,"format":"json","inCharset":"utf-8","outCharset":"utf-8","notice":0,"platform":"yqq.json","needNewCode":1,"uin":你的QQ号,"g_tk_new_20200303":579926986,"g_tk":579926986,"mesh_devops":"DevopsBase"},"req_1":{"module":"music.musichallSinger.SingerList","method":"GetSingerListIndex","param":{"area":-100,"sex":-100,"genre":' + str(genreID) + ',"index":-100,"sin":' + str(80 * (index - 1)) + ',"cur_page":' + str(index) + '}}}'# print(param['data'])param['sign'] = getSign(param['data'])# print(param['sign'])jsonSingers = until.getResJson3(urlMusic, headers, param)# print(jsonSingers)if jsonSingers['req_1']['code'] != 0:logging.info('网页请求返回错误,请排查js脚本或加密相关代码,补充环镜信息继续尝试')breakfor singer in jsonSingers['req_1']['data']['singerlist']:starList.append((singer['singer_mid'], singer['singer_name']))if len(jsonSingers['req_1']['data']['singerlist']) < 80:logging.info('已请求到最后一页,完成当前条件下所有明星人员获取')breakindex = index + 1return starList# 获取一个明星的歌曲列表
def getStarSongList(url, savePath):res = until.getResText(url, headers)# print(res)jsonStr = re.findall('window.__INITIAL_DATA__ =(.*?)</script>', res, re.S)[0]# print(jsonStr.replace('undefined','"undefined"'))jsonData = json.loads(jsonStr.replace('undefined', '"undefined"'))logging.info('开始下载' + jsonData['singerDetail']['basic_info']['name'] + '的热门歌曲')for song in jsonData['songList']:# print(song['id'],song['mid'],song['mv']['id'],song['mv']['vid'])singers = []for singer in song['singer']:singers.append(singer['name'])songName = song['name'] + '-' + ','.join(singers)downMp3(song['mid'], str(song['id']), savePath, songName[0:40])if str(song['mv']['vid']) != '':downMV(song['mv']['vid'], savePath, songName[0:40])# 获取音乐榜单列表
def getTopList(url):topList = []res = until.getResText(url, headers)resText = re.findall('window.__INITIAL_DATA__ =(.*?)</script>', res)[0]# print(resText)jsonData = json.loads(resText.replace('undefined', '"undefined"'))# print(jsonData)for topNavData in jsonData['topNavData']:for toplist in topNavData['toplist']:# print(topNavData['groupName'],toplist['topId'],toplist['title'])topList.append((toplist['topId'], topNavData['groupName'] + '-' + toplist['title']))return topList# 获取当前音乐榜单的歌曲列表
def getTopMusic(topID, savePath):url = 'https://y.qq.com/n/ryqq/toplist/' + str(topID)res = until.getResText(url, headers)resText = re.findall('window.__INITIAL_DATA__ =(.*?)</script>', res)[0]jsonData = json.loads(resText.replace('undefined', '"undefined"'))# print(jsonData)logging.info('开始下载音乐榜单' + savePath.rsplit('\\', 1)[1] + '的热门歌曲')for songInfo in jsonData['songInfoList']:singers = []for singer in songInfo['singer']:singers.append(singer['name'])songName = songInfo['name'] + '-' + ','.join(singers)# print(songInfo['mid'], songInfo['id'], songName)downMp3(songInfo['mid'], str(songInfo['id']), savePath, songName[0:40])if str(songInfo['mv']['vid']) != '':downMV(songInfo['mv']['vid'], savePath, songName[0:40])# 下载歌曲
def downMp3(songMID, songID, savePath, musicName):durl = 'https://u.y.qq.com/cgi-bin/musics.fcg'param = {'_': time.time() * 1000,'sign': 'zzb6d35ac94xigmigc7koyntjy2hzqsg48c02666','data': ''}param['data'] = '{"comm":{"cv":4747474,"ct":24,"format":"json","inCharset":"utf-8","outCharset":"utf-8","notice":0,"platform":"yqq.json","needNewCode":1,"uin":你的QQ号,"g_tk_new_20200303":1218543479,"g_tk":1218543479},"req_1":{"module":"vkey.GetVkeyServer","method":"CgiGetVkey","param":{"guid":"5591983328","songmid":["' + songMID + '"],"songtype":[0],"uin":"你的QQ号","loginflag":1,"platform":"20"}},"req_2":{"module":"music.musicasset.SongFavRead","method":"IsSongFanByMid","param":{"v_songMid":["' + songMID + '"]}},"req_3":{"module":"music.musichallSong.PlayLyricInfo","method":"GetPlayLyricInfo","param":{"songMID":"' + songMID + '","songID":' + songID + '}},"req_4":{"method":"GetCommentCount","module":"music.globalComment.GlobalCommentRead","param":{"request_list":[{"biz_type":1,"biz_id":"' + songID + '","biz_sub_type":0}]}},"req_5":{"module":"music.musichallAlbum.AlbumInfoServer","method":"GetAlbumDetail","param":{"albumMid":"0033R2xQ2I0Uyf"}},"req_6":{"module":"vkey.GetVkeyServer","method":"CgiGetVkey","param":{"guid":"871930674","songmid":["' + songMID + '"],"songtype":[0],"uin":"你的QQ号","loginflag":1,"platform":"20"}},"req_7":{"module":"music.trackInfo.UniformRuleCtrl","method":"CgiGetTrackInfo","param":{"ids":[' + songID + '],"types":[0]}}}'param['sign'] = getSign(param['data'])res = until.getResJson3(durl, headers, param)# print(res)purl = 'https://dl.stream.qqmusic.qq.com/' + res['req_1']['data']['midurlinfo'][0]['purl']# print(musicName, purl)until.downBinFile(purl, headers, savePath, musicName + '.' + purl.split('?')[0].rsplit('.', 1)[1])
sign加密js脚本代码,2024年2月份最新代码
var window =global;
document = {};
// 在浏览器控制台输入navigator和location,拷贝和完善下列信息
navigator = {userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
};
location = {"ancestorOrigins": {},"href": "https://y.qq.com/n/ryqq/singer_list?index=-100&genre=7&sex=-100&area=-100","origin": "https://y.qq.com","protocol": "https:","host": "y.qq.com","hostname": "y.qq.com","port": "","pathname": "/n/ryqq/singer_list","search": "?index=-100&genre=7&sex=-100&area=-100","hash": ""
}var qqloader;
require("./qqsignfun")
!function(e) {function t(t) {for (var r, n, c = t[0], d = t[1], i = t[2], l = 0, u = []; l < c.length; l++)n = c[l],Object.prototype.hasOwnProperty.call(o, n) && o[n] && u.push(o[n][0]),o[n] = 0;for (r in d)Object.prototype.hasOwnProperty.call(d, r) && (e[r] = d[r]);for (b && b(t); u.length; )u.shift()();return f.push.apply(f, i || []),a()}function a() {for (var e, t = 0; t < f.length; t++) {for (var a = f[t], r = !0, n = 1; n < a.length; n++) {var d = a[n];0 !== o[d] && (r = !1)}r && (f.splice(t--, 1),e = c(c.s = a[0]))}return e}var r = {}, n = {21: 0}, o = {21: 0}, f = [];function c(t) {if (r[t])return r[t].exports;var a = r[t] = {i: t,l: !1,exports: {}};return e[t].call(a.exports, a, a.exports, c),a.l = !0,a.exports}c.e = function(e) {var t = [];n[e] ? t.push(n[e]) : 0 !== n[e] && {1: 1,3: 1,4: 1,5: 1,6: 1,7: 1,8: 1,9: 1,10: 1,11: 1,12: 1,13: 1,14: 1,15: 1,16: 1,17: 1,18: 1,19: 1,20: 1,22: 1,23: 1,24: 1,25: 1,26: 1}[e] && t.push(n[e] = new Promise((function(t, a) {for (var r = "css/" + ({1: "common",3: "album",4: "albumDetail",5: "album_mall",6: "category",7: "cmtpage",8: "download_detail",9: "index",10: "msg_center",11: "mv",12: "mvList",13: "mv_toplist",14: "notfound",15: "player",16: "player_radio",17: "playlist",18: "playlist_edit",19: "profile",20: "radio",22: "search",23: "singer",24: "singer_list",25: "songDetail",26: "toplist"}[e] || e) + "." + {1: "2e3d715e72682303d35b",3: "5cf0d69eaf29bcab23d2",4: "798353db5b0eb05d5358",5: "df4c243f917604263e58",6: "20d532d798099a44bc88",7: "e3bedf2b5810f8db0684",8: "e3bedf2b5810f8db0684",9: "ea0adb959fef9011fc25",10: "020422608fe8bfb1719a",11: "8bdb1df6c5436b790baa",12: "47ce9300786df1b70584",13: "4aee33230ba2d6b81dce",14: "e6f63b0cf57dd029fbd6",15: "1d2dbefbea113438324a",16: "d893492de07ce97d8048",17: "9484fde660fe93d9f9f0",18: "67fb85e7f96455763c83",19: "5e8c651e74b13244f7cf",20: "3befd83c10b19893ec66",22: "b2d11f89ea6a512a2302",23: "c7a38353c5f4ebb47491",24: "df0961952a2d3f022894",25: "4c080567e394fd45608b",26: "8edb142553f97482e00f"}[e] + ".chunk.css?max_age=2592000", o = c.p + r, f = document.getElementsByTagName("link"), d = 0; d < f.length; d++) {var i = (b = f[d]).getAttribute("data-href") || b.getAttribute("href");if ("stylesheet" === b.rel && (i === r || i === o))return t()}var l = document.getElementsByTagName("style");for (d = 0; d < l.length; d++) {var b;if ((i = (b = l[d]).getAttribute("data-href")) === r || i === o)return t()}var u = document.createElement("link");u.rel = "stylesheet",u.type = "text/css",u.onload = t,u.onerror = function(t) {var r = t && t.target && t.target.src || o, f = new Error("Loading CSS chunk " + e + " failed.\n(" + r + ")");f.code = "CSS_CHUNK_LOAD_FAILED",f.request = r,delete n[e],u.parentNode.removeChild(u),a(f)},u.href = o,0 !== u.href.indexOf(window.location.origin + "/") && (u.crossOrigin = "anonymous"),document.getElementsByTagName("head")[0].appendChild(u)})).then((function() {n[e] = 0})));var a = o[e];if (0 !== a)if (a)t.push(a[2]);else {var r = new Promise((function(t, r) {a = o[e] = [t, r]}));t.push(a[2] = r);var f, d = document.createElement("script");d.charset = "utf-8",d.timeout = 120,c.nc && d.setAttribute("nonce", c.nc),d.src = function(e) {return c.p + "js/" + ({1: "common",3: "album",4: "albumDetail",5: "album_mall",6: "category",7: "cmtpage",8: "download_detail",9: "index",10: "msg_center",11: "mv",12: "mvList",13: "mv_toplist",14: "notfound",15: "player",16: "player_radio",17: "playlist",18: "playlist_edit",19: "profile",20: "radio",22: "search",23: "singer",24: "singer_list",25: "songDetail",26: "toplist"}[e] || e) + ".chunk." + {1: "a19d13b1f550b0a74773",3: "4e095ebbcb9e70be04b4",4: "e3852fd8e0f7280f664b",5: "b9ad62f6d895d28fac34",6: "5b0a5766aceabae9cca5",7: "247a6801f111a0f0248d",8: "cd8494d8383ad9903094",9: "a7047eb4c37a6478ed4c",10: "b92a08f6c8f1e635ceaa",11: "f1f18259c1c57f8fbcdf",12: "bb54c66fafb41cb72caa",13: "ef9d323eabbc2c38bc3e",14: "aef3af0909ad27a9e18d",15: "f21aa0b57606c669a045",16: "a28f3be66a41e0b1a4e2",17: "b190de0c33f5c6303f60",18: "39c1a683083b05275679",19: "e218e34000b7d35e8764",20: "711790cae7a459dfaea5",22: "657433415a2330f5d636",23: "950ad1844b00ee5b48f5",24: "a99d35055652ba5024bf",25: "72d76900f95a93d319cf",26: "18163e92a2b8c2ebe8cf"}[e] + ".js?max_age=2592000"}(e),0 !== d.src.indexOf(window.location.origin + "/") && (d.crossOrigin = "anonymous");var i = new Error;f = function(t) {d.onerror = d.onload = null,clearTimeout(l);var a = o[e];if (0 !== a) {if (a) {var r = t && ("load" === t.type ? "missing" : t.type), n = t && t.target && t.target.src;i.message = "Loading chunk " + e + " failed.\n(" + r + ": " + n + ")",i.name = "ChunkLoadError",i.type = r,i.request = n,a[1](i)}o[e] = void 0}};var l = setTimeout((function() {f({type: "timeout",target: d})}), 12e4);d.onerror = d.onload = f,document.head.appendChild(d)}return Promise.all(t)},c.m = e,c.c = r,c.d = function(e, t, a) {c.o(e, t) || Object.defineProperty(e, t, {enumerable: !0,get: a})},c.r = function(e) {"undefined" !== typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, {value: "Module"}),Object.defineProperty(e, "__esModule", {value: !0})},c.t = function(e, t) {if (1 & t && (e = c(e)),8 & t)return e;if (4 & t && "object" === typeof e && e && e.__esModule)return e;var a = Object.create(null);if (c.r(a),Object.defineProperty(a, "default", {enumerable: !0,value: e}),2 & t && "string" != typeof e)for (var r in e)c.d(a, r, function(t) {return e[t]}.bind(null, r));return a},c.n = function(e) {var t = e && e.__esModule ? function() {return e.default}: function() {return e};return c.d(t, "a", t),t},c.o = function(e, t) {return Object.prototype.hasOwnProperty.call(e, t)},c.p = "/ryqq/",c.oe = function(e) {throw e};var d = window.webpackJsonp = window.webpackJsonp || [], i = d.push.bind(d);d.push = t,d = d.slice();for (var l = 0; l < d.length; l++)t(d[l]);var b = i;a()qqloader = c;
}([]);function main123(data){i = qqloader(354).default;return i(data);
}
还有一个js脚本文件太大,无法上传。
所有爬虫代码已上传网盘,可以关注公众号【站在前沿】,回复qqmusic,获取网盘下载链接。有其它需求也可以在微信公众号留言。
QQ音乐下载代码已分享,但是下载VIP歌曲需要开通QQ音乐会员并替换您的QQ号码,可以尝试进行下载。QQ会员结束前我已经下载所有歌曲和MV文件,文件总大小达2T以上,如果需要可以有偿提供,可以公众号留言提出您的需求。
备份和保存音乐、视频文件和照片文件可以购买移动硬盘或U盘,都是固态硬盘,文件拷贝可以达到每秒1G或500M。使用微信或京东APP扫码下午购买。