1. 项目概述:为什么我们要研究QQ音乐的加密参数?
如果你尝试过用Python写个脚本,想批量下载QQ音乐上的歌曲或者获取高音质音频链接,大概率会卡在第一步:那些看似随机的请求参数。尤其是vKey和sign这两个家伙,它们像两把锁,牢牢地保护着音乐资源的核心访问权限。没有正确的vKey,你拿到的可能只是一个无效或低质量的音频地址;没有正确的sign,你的请求在服务器看来就是非法的,直接返回错误。
我最初接触这个需求,是想做一个个人音乐库的整理工具。市面上的一些现成方案要么已经失效,要么过于黑盒,用起来不放心。于是,我决定自己动手,彻底搞清楚QQ音乐这套加密机制是怎么运作的。这个过程,本质上是一次典型的Web端JS逆向工程。它不像客户端逆向那样需要反编译,而是需要我们化身“侦探”,在浏览器和网络请求中寻找线索,最终用Python复现出加密逻辑。
这不仅仅是为了“下载音乐”。掌握这套方法,你就能理解现代Web应用如何通过前端JavaScript实现核心业务逻辑的加密与验证,这是一种非常有价值的技能。无论是数据分析、自动化测试还是其他需要与复杂Web接口打交道的场景,类似的逆向思路都是相通的。接下来,我就把我趟过的路、踩过的坑,以及最终成功的完整代码,毫无保留地分享给你。
2. 逆向核心思路与工具准备
逆向工程不是盲目猜测,它有一套成熟的“侦查”流程。我们的目标很明确:找到生成vKey和sign参数的JavaScript代码,理解其输入、输出和算法,然后用Python实现它。
2.1 核心思路拆解
QQ音乐作为一款成熟的产品,其加密逻辑必然放在前端代码中,因为最终是浏览器在执行这些代码来发起请求。我们的逆向路径可以概括为以下几步:
- 定位关键请求:首先,我们需要找到一个携带了
vKey和sign的真实网络请求。通常,播放一首歌曲时,浏览器会向类似https://u.y.qq.com/cgi-bin/musicu.fcg这样的接口发起POST请求,其请求体(payload)中就包含了这些加密参数。 - 搜索加密入口:在浏览器的开发者工具(DevTools)中,我们可以搜索这些参数名,比如
sign或vKey,来定位生成它们的JavaScript代码片段。由于代码可能被压缩或混淆,直接搜索字符串有时不够,需要结合调用栈(Call Stack)和XHR断点(XHR/fetch Breakpoint)来精确定位。 - 分析加密逻辑:找到关键函数后,需要耐心分析。代码可能是清晰的,也可能是经过混淆的(变量名变成a,b,c,逻辑被拆分等)。我们需要理清:这个函数接收哪些参数?参数是如何组合的?使用了哪种哈希算法(如MD5, SHA-1)或加密方式?是否有随机数或时间戳参与?
- Python复现:一旦理解了算法,就用Python编写对应的函数。重点确保每一步的逻辑、字符串编码、字节处理等细节与JavaScript完全一致。一个字符的差异都可能导致生成的签名无效。
2.2 工具与环境准备
工欲善其事,必先利其器。以下是本次逆向和分析过程中需要用到的核心工具:
- 浏览器:Google Chrome或Microsoft Edge(基于Chromium)。它们的开发者工具功能强大且统一,是我们的主战场。
- 开发者工具(DevTools):按F12打开。我们需要熟练使用以下几个面板:
- Network(网络):记录和查看所有网络请求,找到目标请求并查看其请求头和请求体。
- Sources(源代码):查看和调试JavaScript代码。可以设置断点、单步执行,是分析逻辑的核心。
- Console(控制台):执行JavaScript代码片段,用于测试我们找到的加密函数。
- Node.js环境:虽然不是必须,但有时为了在本地快速验证一段复杂的、依赖浏览器环境的JS代码,安装Node.js会很方便。我们可以把关键的JS函数提取出来,稍作修改后在Node.js中运行测试。
- Python环境:这是我们的最终目标。你需要安装Python 3.7及以上版本。关键的Python库包括:
requests:用于发送HTTP请求。execjs:一个非常关键的库。它允许我们在Python中执行JavaScript代码。当加密逻辑非常复杂,用纯Python复现困难或容易出错时,我们可以直接将找到的JS函数代码“搬”过来,用execjs调用。这是逆向工程中一个非常实用的“捷径”。hashlib:Python标准库,用于实现MD5、SHA-1等哈希算法,如果加密逻辑简单,可以直接用它实现。
注意:在实际操作中,QQ音乐的JS代码可能经常更新,加密逻辑也可能发生变化。因此,本文重点传授的是逆向的方法论和思路,以及如何构建一个健壮的、易于维护的代码框架。具体的函数名和代码位置可能会变,但“定位-分析-复现”的流程是不变的。
3. 实战逆向:定位并分析vKey与Sign的生成逻辑
理论说再多,不如动手走一遍。我们假设现在要获取一首歌的播放链接。
3.1 第一步:捕获目标网络请求
- 打开Chrome浏览器,访问QQ音乐网页版(https://y.qq.com)。
- 按
F12打开开发者工具,切换到Network(网络)面板。记得勾选上“Preserve log”(保留日志),防止页面跳转时请求记录被清空。 - 在网页上搜索并播放任意一首歌曲。
- 在网络面板中,仔细寻找请求URL中包含
musicu.fcg的请求。通常它是一个POST请求,指向https://u.y.qq.com/cgi-bin/musicu.fcg。 - 点击这个请求,在右侧的Headers(标头)选项卡中,确认其Request Method(请求方法)为 POST。然后切换到Payload(负载)或Request(请求)选项卡(取决于Chrome版本),查看其请求体。你会看到一个JSON格式的数据,其中很可能就包含了
sign和vKey(或者是一个用于换取vKey的参数)。
3.2 第二步:设置断点,追踪签名生成过程
仅仅看到参数还不够,我们需要知道它们是怎么算出来的。
- 在Network面板中,找到那个
musicu.fcg请求,右键点击它。 - 在弹出的菜单中,选择“Copy” -> “Copy as cURL (bash)”。这能帮我们后续在Python中快速构建请求。
- 更关键的一步:在同一右键菜单中,选择“Break on” -> “Request start”。这会在该请求发起时自动在Sources面板触发一个断点。
- 现在,在网页上重新触发一次歌曲播放(比如切歌再切回来)。此时浏览器会卡住,并自动跳转到Sources(源代码)面板,停在了发起请求的JavaScript代码处。
- 在右侧的Call Stack(调用堆栈)面板中,你可以看到函数调用的链条。从上到下浏览,寻找可能包含
sign、vKey或明显是加密、参数处理逻辑的函数。点击这些调用栈条目,可以跳转到对应的JS代码位置。
3.3 第三步:搜索与静态分析
如果断点方法因为代码混淆太复杂而难以追踪,我们可以结合搜索法。
- 在Sources面板,按
Ctrl+Shift+F(Windows/Linux)或Cmd+Opt+F(Mac)打开全局搜索。 - 因为参数名可能被压缩,我们可以尝试搜索一些关键词片段,如
sign:、"sign"、vkey、getSign、encrypt等。 - 在找到的代码行附近仔细阅读。通常,生成sign的函数会接收一个对象(即我们看到的请求参数JSON),然后对其进行排序、拼接、最后进行哈希(MD5常见)运算。
- 一个典型的、未被混淆的sign生成函数可能长这样(示例):
function getSign(params) { var keys = Object.keys(params).sort(); var str = ''; for (var i = 0; i < keys.length; i++) { str += keys[i] + '=' + params[keys[i]] + '&'; } str = str.slice(0, -1); // 去掉最后一个& str += '某个固定的盐值(salt)'; // 关键,这个盐值需要逆向找出 return md5(str); } - 对于
vKey,其生成可能更复杂,有时它不是一个本地计算的参数,而是通过另一个接口(比如https://u.y.qq.com/cgi-bin/musicu.fcg?data=...)传入一个guid、songmid等参数后,由服务器返回的一个临时密钥。我们需要找到是哪个请求返回了vKey,然后分析发起那个请求需要哪些参数。
3.4 第四步:在控制台验证与提取
一旦你认为找到了关键函数,可以在Console(控制台)中进行验证。
- 在Sources面板,将你认为的加密函数代码(包括其依赖的辅助函数,如md5函数)完整地复制出来。
- 切换到Console面板,将这些代码粘贴进去执行,相当于在当前页面环境定义了这些函数。
- 然后,构造一个简单的参数对象,调用这个函数,看看输出是否与之前捕获的网络请求中的
sign值一致。// 假设我们找到了一个名为 `o` 的签名函数 var testParams = {“comm”: {“ct”: 24}, “url_mid”: {“module”: “vkey.GetVkeyServer”, “method”: “CgiGetVkey”, “param”: {“guid”: “123456”, “songmid”: [“0039MnYb0qxYhV”]}}}; var mySign = o(testParams); console.log(“我计算的sign:”, mySign); // 与Network里捕获的sign对比 - 如果一致,恭喜你,核心逻辑找到了!你可以将这个JS函数代码完整地保存下来,用于后续的Python复现。
实操心得:QQ音乐的JS代码混淆程度可能较高,函数名可能是单个字母,逻辑可能被分散。这时,XHR/fetch断点结合Call Stack追踪是最可靠的方法。不要害怕复杂的调用栈,耐心地一层层跟进,重点关注那些处理对象、进行字符串操作或调用
crypto相关模块的函数。
4. Python代码实现:两种复现策略
理解了逻辑,我们就可以用Python来实现了。根据逆向出的JS代码复杂程度,我们有两种策略。
4.1 策略一:纯Python复现(适用于逻辑清晰的情况)
如果签名算法是标准的MD5或SHA-1,且参数拼接规则简单,我们可以用Python的hashlib轻松实现。
假设我们逆向发现sign的生成规则是:将所有参数按字典序排序,用&连接成key=value的形式,最后拼接一个固定字符串”mySecretSalt”,然后取MD5值(32位小写)。
import hashlib import json import time def generate_sign(params_dict, salt="mySecretSalt"): """ 根据逆向分析的规则生成sign :param params_dict: 请求参数字典 :param salt: 逆向找到的盐值 :return: 计算得到的sign字符串 """ # 1. 按key的字典序排序 sorted_keys = sorted(params_dict.keys()) # 2. 拼接成 key1=value1&key2=value2 的格式 sign_str = ‘&’.join([f”{k}={params_dict[k]}” for k in sorted_keys]) # 3. 拼接盐值 sign_str += salt # 4. 计算MD5 m = hashlib.md5() # 注意编码!JS和Python的字符串处理可能有差异,通常使用UTF-8 m.update(sign_str.encode(‘utf-8’)) sign = m.hexdigest() # 获取32位小写MD5值 return sign # 示例使用 if __name__ == ‘__main__’: # 模拟一个请求参数,具体结构需根据实际接口调整 test_params = { “comm”: {“ct”: 24, “cv”: 0}, “req”: { “module”: “music.stream.StreamUrlServer”, “method”: “GetStreamUrl”, “param”: { “guid”: “1234567890”, “songmid”: [“0039MnYb0qxYhV”], “uin”: “0” } } } # 注意:实际中可能需要将整个字典JSON序列化成字符串作为某个key的value # 这里仅为演示拼接逻辑 # 假设params_dict是已经扁平化或最外层的参数 flat_params = {“data”: json.dumps(test_params, separators=(‘,’, ‘:’), ensure_ascii=False)} my_sign = generate_sign(flat_params, salt=“真实的盐值(需逆向获取)”) print(f”生成的sign: {my_sign}”)4.2 策略二:使用execjs调用JS代码(适用于复杂或混淆严重的逻辑)
当JS代码混淆严重,逻辑曲折,或者依赖了浏览器特有的环境变量、函数时,用Python完全重写可能事倍功半,且容易出错。这时,execjs库是我们的“神器”。它的原理是在Python中嵌入一个JavaScript运行时(如Node.js),直接执行JS代码。
安装依赖:
pip install PyExecJS # 同时确保系统安装了Node.js,execjs会自动检测并使用它。提取并适配JS代码: 将之前在浏览器Console中验证成功的完整JS函数代码保存到一个字符串中。注意,如果代码中使用了浏览器全局对象如
window、document,或者某些特定的Web API,我们需要在Node.js环境中模拟它们,或者对代码进行简单修改,移除这些环境依赖。通常,加密函数是纯计算逻辑,不依赖DOM。Python调用示例:
import execjs import json # 1. 读取保存的JS代码文件,或直接定义成字符串 # 假设我们保存的JS文件为 `qqmusic_sign.js` with open(‘qqmusic_sign.js’, ‘r’, encoding=‘utf-8’) as f: js_code = f.read() # 2. 创建execjs上下文 ctx = execjs.compile(js_code) # 3. 准备参数(与在浏览器Console中测试时一致) # 注意:传递给JS函数的参数格式必须完全一致。 # 如果JS函数期望一个JSON对象,我们就传递一个Python字典,execjs会处理转换。 params_for_js = { “comm”: {“ct”: 24}, “url_mid”: { “module”: “vkey.GetVkeyServer”, “method”: “CgiGetVkey”, “param”: { “guid”: “123456”, “songmid”: [“0039MnYb0qxYhV”] } } } # 4. 调用JS函数。假设JS中函数名叫 `getSign` try: # 注意:如果JS函数需要多个参数,依次传入即可。 sign = ctx.call(“getSign”, params_for_js) print(f”通过execjs计算的sign: {sign}”) except Exception as e: print(f”调用JS函数失败: {e}”) # 可能是函数名不对,或JS代码执行环境有问题qqmusic_sign.js文件内容示例(简化版):// 这里可能包含一个MD5函数实现和主要的签名函数 function md5(str) { // ... 可能是逆向找到的或标准的MD5实现 ... } function getSign(params) { // ... 逆向找到的签名逻辑 ... var sortedKeys = Object.keys(params).sort(); var str = ‘’; for (var i = 0; i < sortedKeys.length; i++) { str += sortedKeys[i] + ‘=’ + JSON.stringify(params[sortedKeys[i]]) + ‘&’; } str = str.slice(0, -1); str += ‘某个固定的盐值’; return md5(str); }
注意事项:使用
execjs时,最大的坑在于参数传递的序列化和JS代码的环境依赖。务必确保从Python传到JS的对象结构,与在浏览器中测试时完全一致。对于复杂的嵌套对象,使用json.dumps确保格式正确。如果JS代码报错,可以尝试先在Node.js命令行中运行测试,排除环境问题。
5. 构建完整的请求流程与示例代码
掌握了sign的生成,我们还需要构建一个完整的请求链来获取vKey和最终的播放链接。QQ音乐的流程通常是:先通过一个接口,传入songmid、guid等参数,并携带正确的sign,来获取包含vKey的响应;然后再用这个vKey去拼接最终的音频文件地址。
5.1 流程梳理
- 获取歌曲基本信息:通常搜索或歌单接口返回的歌曲信息中会包含
songmid(歌曲媒体ID),这是后续请求的关键。 - 构造获取vKey的请求:
- 接口:
https://u.y.qq.com/cgi-bin/musicu.fcg - 方法:POST
- 参数:一个嵌套的JSON结构,通常包含
comm(公共参数,如客户端类型ct、版本cv等)和req(业务请求,其module和method指向获取vKey的服务,param里包含guid、songmid列表等)。 - 关键:这个请求的
sign参数需要根据整个请求体(或其中一部分)计算,并放在URL的查询字符串(query string)或请求体中(具体位置需逆向确定)。
- 接口:
- 解析响应,提取vKey:上一步的接口会返回一个JSON,其中包含
vkey字段。这个vkey是一长串字符串。 - 拼接音频URL:有了
songmid和vkey,就可以按照固定的URL模式拼接出音频文件的直接链接。格式通常类似于:http://ws.stream.qqmusic.qq.com/{vkey}?fromtag=0&guid={guid}&songmid={songmid}&...。但请注意,这个模式可能会变化,最准确的方式是观察浏览器在获取到vKey后,实际发起请求的音频文件URL是什么结构。
5.2 完整代码示例框架
下面是一个整合了上述逻辑的Python代码框架。请注意,其中的generate_sign函数、盐值(SALT)、请求参数的具体结构(payload_template)以及最终的URL拼接规则,都需要你根据自己逆向分析的结果进行填充和修改。
import json import time import random import hashlib import execjs # 如果使用策略二 import requests class QQMusicCrawler: def __init__(self): self.session = requests.Session() # 设置一些通用的请求头,模拟浏览器 self.headers = { ‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36’, ‘Referer’: ‘https://y.qq.com/’, ‘Origin’: ‘https://y.qq.com’, } self.session.headers.update(self.headers) # 一个假的guid,可以随机生成,但一次会话中最好固定 self.guid = str(random.randint(1000000000, 9999999999)) # 逆向得到的盐值,这是关键! self.SALT = “YOUR_REVERSED_SALT_HERE” def _generate_sign_pure_python(self, params_dict): “”“策略一:纯Python生成sign(示例,需根据实际逻辑修改)”“” # 示例逻辑:按key排序后拼接 sorted_keys = sorted(params_dict.keys()) sign_str = ‘’ for key in sorted_keys: value = params_dict[key] # 如果值是字典或列表,可能需要JSON序列化 if isinstance(value, (dict, list)): value = json.dumps(value, separators=(‘,’, ‘:’), ensure_ascii=False) sign_str += f”{key}={value}&” sign_str = sign_str.rstrip(‘&’) sign_str += self.SALT m = hashlib.md5() m.update(sign_str.encode(‘utf-8’)) return m.hexdigest() def _generate_sign_by_js(self, params_dict): “”“策略二:通过execjs调用JS代码生成sign”“” # 假设JS代码文件为 sign.js,其中暴露了一个 getSign 函数 with open(‘sign.js’, ‘r’, encoding=‘utf-8’) as f: js_code = f.read() ctx = execjs.compile(js_code) # 注意:params_dict 的结构必须与JS函数期望的完全一致 sign = ctx.call(“getSign”, params_dict) return sign def get_vkey(self, songmid): “”“核心方法:根据songmid获取vKey”“” # 1. 构造请求体,这个结构需要根据逆向分析确定 payload = { “comm”: { “ct”: 24, # 客户端类型,24可能代表Web “cv”: 0, “uin”: “0”, “guid”: self.guid }, “req”: { “module”: “music.stream.StreamUrlServer”, # 模块名,需确认 “method”: “GetStreamUrl”, # 方法名,需确认 “param”: { “guid”: self.guid, “songmid”: [songmid], # 注意是列表 “uin”: “0”, # 可能还有其他参数如 `musicfile`,`quality`等 } } } # 2. 生成sign。这里演示使用纯Python方式,你需要替换成实际可用的函数。 # 注意:sign可能是基于整个payload计算,也可能是基于其中某个字段。 # 假设sign是基于整个 `req` 对象计算,并作为一个顶级参数。 sign_str = self._generate_sign_pure_python(payload[‘req’][‘param’]) # 这里仅是示例位置! # 3. 将sign加入到最终请求的参数中。有时sign放在URL,有时放在请求体。 # 假设放在请求体的顶级 payload[‘sign’] = sign_str # 4. 发送请求 url = “https://u.y.qq.com/cgi-bin/musicu.fcg” # 注意:有时接口要求将整个payload作为名为 `data` 的查询参数,有时是POST的JSON body。 # 需要观察原始请求。这里假设是POST JSON。 try: resp = self.session.post(url, json=payload, timeout=10) resp.raise_for_status() # 检查HTTP错误 result = resp.json() except Exception as e: print(f”请求vKey接口失败: {e}”) return None # 5. 解析响应,提取vKey。响应结构需要根据实际分析。 # 这是一个示例路径,实际路径可能不同 try: vkey = result[‘req’][‘data’][‘midurlinfo’][0][‘vkey’] return vkey except KeyError as e: print(f”解析vKey失败,响应结构可能已变化: {e}”) print(f”完整响应: {json.dumps(result, indent=2, ensure_ascii=False)}”) return None def get_audio_url(self, songmid, vkey=None): “”“获取最终的音频文件直链”“” if not vkey: vkey = self.get_vkey(songmid) if not vkey: print(“获取vKey失败,无法构造音频URL”) return None # URL拼接规则需要逆向观察。这是一个非常常见的旧格式,现在可能已失效。 # 你必须从浏览器Network面板中,找到真实的音频文件请求URL,分析其规律。 # 示例(很可能不对): # audio_url = f”http://ws.stream.qqmusic.qq.com/{vkey}?guid={self.guid}&fromtag=30&songmid={songmid}” # 更可靠的方式:vKey接口的响应中,有时会直接返回一个或多个 `url` 或 `purl` 字段,那就是直链。 # 所以,在 `get_vkey` 方法中,直接返回那个url可能更好。 # 这里仅为演示流程。 print(f”警告:音频URL拼接规则需要你根据实际逆向结果更新!”) return None def fetch_song_info(self, keyword): “”“辅助方法:通过搜索获取songmid”“” search_url = “https://c.y.qq.com/soso/fcgi-bin/client_search_cp” params = { ‘w’: keyword, ‘format’: ‘json’, ‘p’: 1, ‘n’: 10, } try: resp = self.session.get(search_url, params=params) data = resp.json() song_list = data.get(‘data’, {}).get(‘song’, {}).get(‘list’, []) if song_list: # 返回第一个结果的 songmid 和 songname song = song_list[0] return song.get(‘songmid’), song.get(‘songname’) except Exception as e: print(f”搜索歌曲失败: {e}”) return None, None # 使用示例 if __name__ == ‘__main__’: crawler = QQMusicCrawler() # 第一步:搜索歌曲,获取songmid songmid, songname = crawler.fetch_song_info(“周杰伦 晴天”) if not songmid: print(“未找到歌曲”) exit() print(f”找到歌曲: {songname}, songmid: {songmid}”) # 第二步:获取vKey vkey = crawler.get_vkey(songmid) if vkey: print(f”获取到vKey: {vkey}”) # 第三步:获取音频URL(需要你完善拼接逻辑或从响应中提取) audio_url = crawler.get_audio_url(songmid, vkey) if audio_url: print(f”音频直链: {audio_url}”) # 你可以用 requests.get(audio_url, stream=True) 下载 else: print(“获取vKey失败”)6. 常见问题、调试技巧与避坑指南
在实际操作中,你一定会遇到各种问题。下面是我踩过坑后总结的一些经验。
6.1 常见问题速查表
| 问题现象 | 可能原因 | 排查思路与解决方案 |
|---|---|---|
sign计算不正确 | 1. 盐值(salt)错误或缺失。 2. 参数排序规则不对(非字典序或顺序固定)。 3. 参数值格式不对(如JSON字符串化时空格、引号问题)。 4. 参与计算的参数范围不对(是整个请求体,还是某个子对象)。 | 1.核对盐值:在JS调试中,打印出拼接后的字符串,与Python生成的字符串逐字符对比。 2.验证排序:在JS和Python中分别打印排序后的key列表,确保一致。 3.统一序列化:对于对象/数组参数,使用 json.dumps(params, separators=(‘,’, ‘:’), ensure_ascii=False)来生成与JS中JSON.stringify最接近的字符串(无空格,Unicode字符不转义)。4.缩小范围:通过断点,确定JS函数接收到的具体参数是什么。 |
execjs调用JS报错 | 1. JS代码依赖浏览器环境(如window、document)。2. JS代码语法错误或在Node.js中不兼容。 3. 函数名或参数传递错误。 | 1.环境模拟:在JS代码开头添加模拟对象,如var window = this; var document = {};,或删除无关的环境依赖代码。2.本地测试:将JS代码保存为 .js文件,先用Node.js命令行运行测试,修复语法错误。3.检查调用:使用 ctx.call(‘函数名’, arg1, arg2…)确保函数名和参数个数、类型匹配。 |
| 请求返回“参数错误”或“签名无效” | 1.sign虽然算法对,但放错了位置(应在URL查询参数还是请求体)。2. 缺少了必要的公共参数,如 guid,uin,ct,cv等。3. guid格式或生成规则不对。 | 1.对比原始请求:用Chrome的“Copy as cURL”功能,仔细对比你的Python请求和浏览器请求在Headers和Payload上的每一个差异。 2.补全参数:确保 comm等公共参数与浏览器中的请求完全一致,包括看似为0的值。3.固定guid:浏览器一次会话中的 guid通常是固定的,可以随机生成一个并保持不变。 |
能获取vKey但音频URL无法播放/404 | 1.vKey已过期(有效期很短)。2. 音频URL拼接规则错误。 3. 需要特定的请求头(如 Referer)才能访问音频资源。 | 1.即时使用:获取到vKey后应立即用于构造URL并访问,不要延迟。2.分析真实URL:不要猜测URL格式,务必从Network面板中找到真实的 .m4a或.mp3请求,直接复制其URL模式。3.添加Referer:下载音频时,请求头中需要包含 Referer: https://y.qq.com/。 |
| 代码昨天还能用,今天就不行了 | 接口更新,加密逻辑或参数发生变化。 | 1.重新逆向:这是常态。按照本文的步骤,重新抓包、定位、分析sign生成函数。 2.关注核心:加密的核心思路(排序、拼接、加盐、哈希)通常不变,变的可能是盐值、参数结构或哈希算法。 |
6.2 独家调试技巧
- “对比法”是王道:在Python中生成sign后,与浏览器中相同参数生成的sign进行逐字符对比。如果不一样,将两者用于计算签名的原始字符串都打印出来,进行对比。99%的问题出在这里。
- 善用“Copy as cURL”:在Chrome的Network面板右键请求,选择“Copy as cURL”,然后将这条cURL命令在终端执行,或粘贴到 https://curlconverter.com/python/ 这类网站转换成Python
requests代码。这能帮你快速搭建一个正确的请求框架,你只需要专注于解决sign的生成即可。 - 简化测试用例:在浏览器Console中测试JS函数时,不要直接用完整的、庞大的请求参数。先构造一个极简的、只有两三个键值对的参数对象,确保在这个简单情况下JS和Python的输出能对上,再逐步增加复杂度。
- 留意时间戳和随机数:有些sign算法会加入
_或timestamp这样的参数。确保你的Python代码中这些动态参数的值,与生成sign的那个时刻保持一致(比如,先定义好时间戳,再用它去计算sign和构造请求体)。
6.3 最后的叮嘱
逆向工程是与官方防御机制的持续博弈。QQ音乐的加密策略并非一成不变,今天有效的方法,明天可能就会失效。因此,这个教程赋予你的最重要的东西不是一段固定的代码,而是独立分析和解决问题的能力。
当你发现代码失效时,不要慌张,回头重新走一遍“抓包 -> 定位 -> 分析 -> 复现”的流程。多练习几次,你会越来越熟练。最终,你可以将核心的JS加密代码抽象出来,即使外部参数结构变了,你只需要调整传入参数的构造方式,而无需重新破解加密算法本身。
希望这篇超详细的指南能帮你打开Web逆向的大门。在实际操作中遇到任何具体问题,欢迎带着你的发现和错误信息来交流。记住,耐心和细心是逆向工程师最重要的品质。