彻底扒光QQ音乐,批量下载音乐和MV文件

彻底扒光QQ音乐,批量下载音乐和MV文件

购买了一年的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('(.*?)', res)

for starid, star in stars:

starList.append((starid, star))

stars = re.findall('(.*?)',

res)

for starid, star in stars:

starList.append((starid, star))

urlMusic = 'https://u.y.qq.com/cgi-bin/musics.fcg'

index = 2

while 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脚本或加密相关代码,补充环镜信息继续尝试')

break

for 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('已请求到最后一页,完成当前条件下所有明星人员获取')

break

index = index + 1

return starList

# 获取一个明星的歌曲列表

def getStarSongList(url, savePath):

res = until.getResText(url, headers)

# print(res)

jsonStr = re.findall('window.__INITIAL_DATA__ =(.*?)', 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__ =(.*?)', 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__ =(.*?)', 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以上,如果需要可以有偿提供,可以公众号留言提出您的需求。

本文链接:https://blog.csdn.net/wzhibin/article/details/136026310

备份和保存音乐、视频文件和照片文件可以购买移动硬盘或U盘,都是固态硬盘,文件拷贝速度可以达到每秒1G或500M。使用微信或京东APP扫码下单购买。

相关灵感

mobile365 王丽意思及名字好不好解析

王丽意思及名字好不好解析

📅 09-17 👁️ 755
beat365体育亚洲入口 皇家水管质量怎么样

皇家水管质量怎么样

📅 10-03 👁️ 708
mobile365 八重神子专武面板一览 原神神乐之真意90级属性介绍
beat365体育亚洲入口 万方数据知识服务平台

万方数据知识服务平台

📅 07-27 👁️ 1278
mobile365 萸的解释

萸的解释

📅 08-06 👁️ 5488
365bet吧 2025万宝路爆珠价格表及高清图:蓝莓/柑橘/薄荷全系列价格一览(附真伪辨别指南)
365bet吧 铁矿石是如何开采的?分步流程详解
mobile365 荣耀20打王者能坚持多久

荣耀20打王者能坚持多久

📅 07-13 👁️ 8006
mobile365 从零开始!游戏搬砖能挣多少钱?新手避坑必看!