☰
网易云音乐AES+RSA双加密绕过实战:params与encSecKey生成解析
2026/11/5 2:12:50 网站建设 项目流程

简介:这是一套基于Java开发的网易云音乐数据抓取工具,面向Java初学者与爬虫实践者,解决音乐平台公开数据采集与结构化存储的学习需求。资源包共23个文件,含18个Java源码(涵盖HTTP请求、HTML解析、JSON处理及数据库写入等核心模块)、2个XML配置文件(用于Spring框架集成与MyBatis映射)、1个properties配置文件(管理数据库连接与爬虫参数)、1个SQL建表脚本(支持MySQL快速初始化)以及1份README.md说明文档,整体仅23KB,轻量易读。已有90人学习下载,适合在本地快速部署调试,理解反爬应对逻辑、异步请求封装与数据持久化流程。读者可直接运行主程序获取歌单、歌曲、评论等基础数据,掌握从网络请求→页面解析→数据清洗→入库落地的完整链路,并通过清晰的模块划分(如netease_music_spider.sql建表结构、src/main下分层包结构)深入理解工程组织方式。

1. 抓取网易云音乐的数据:这不是爬虫教学,而是一份能跑通的反调试绕过实战笔记

你手头有一份叫NetEaseMusicSpider.zip的压缩包,解压后看到spider.py、config.json、requirements.txt和几个.pyc文件——它不像教科书里“requests + BeautifulSoup”的玩具项目,也不像 GitHub 上挂着“仅供学习”的空壳 demo。这是某开发者在 2023 年底实测通过的网易云音乐数据采集脚本集合,能稳定拉取歌单详情、歌曲评论(含热评与最新 500 条)、歌手专辑列表、用户关注/粉丝关系,甚至带基础的 Cookie 持久化与 UA 轮换逻辑。它不依赖 Selenium,没用 Puppeteer,纯 requests + 加密模块硬刚前端 JS 加密逻辑;它不走公开 API,而是复现了 Web 端真实请求链路中的 AES/CBC + RSA 双重加密参数生成过程。适合正在做音乐平台竞品分析、小规模用户行为建模、或需要结构化曲库元数据的工程师——不是给你造轮子,是帮你省下三天逆向 JS、两天调密钥、一天填坑的时间。如果你刚被网易云的params和encSecKey卡住,或者发现网上教程全卡在登录态失效、评论接口 403、歌单页返回空数组,那这份资源就是你该立刻解压运行的「最小可行绕过方案」。


2. 从解压到首次运行:环境准备、依赖安装与配置文件解析

2.1 解压结构与核心文件职责划分

拿到NetEaseMusicSpider.zip后,先别急着python spider.py。解压后你会看到如下结构:

NetEaseMusicSpider/ ├── spider.py # 主入口:调度器+任务分发,含命令行参数解析 ├── core/ │ ├── crypto.py # 关键!AES/CBC 加密函数、RSA 公钥加载、params/encSecKey 生成逻辑 │ ├── session.py # 封装 requests.Session,自动管理 cookies、headers、重试策略 │ └── parser.py # HTML 解析器(BeautifulSoup)+ JSON 响应解析器,统一返回 dict 结构 ├── utils/ │ ├── logger.py # 带文件滚动与控制台输出的日志器,DEBUG 级别可查加密输入输出 │ └── config_loader.py # 读取 config.json 并校验必填字段,支持环境变量覆盖 ├── config.json # 配置中心:账号、代理开关、并发数、输出路径、加密密钥(若需) ├── requirements.txt # 明确指定版本:requests==2.31.0, pycryptodome==3.18.0, beautifulsoup4==4.12.2 └── README.md # 极简说明(仅两段:用途 + 运行命令)

提示:core/crypto.py是整个项目的黑匣子核心。它不调用任何外部 JS 引擎,所有加密逻辑均用 Python 原生实现,包括网易云 Web 端使用的window.asymmetricEncrypt和window.aesEncrypt的完整复刻。这不是“调用浏览器执行 JS”,而是“把 JS 逻辑翻译成 Python”。

2.2 环境隔离与依赖安装:为什么必须指定版本?

网易云的加密参数生成逻辑对pycryptodome版本极其敏感。实测中,pycryptodome>=3.19.0会因PKCS7填充方式微调导致encSecKey解密失败;而requests>2.31.0在处理某些重定向时会自动修改Referer头,触发服务端风控。因此,务必按以下步骤操作:

# 创建独立虚拟环境(推荐 python3.9+) python -m venv ncm_env source ncm_env/bin/activate # Linux/macOS # ncm_env\Scripts\activate.bat # Windows # 严格按 requirements.txt 安装(禁用升级) pip install --no-deps -r requirements.txt pip install pycryptodome==3.18.0 # 强制降级,避免填充异常

安装完成后,验证关键模块是否就位:

# 测试 crypto 模块是否能正常加载公钥并加密 from core.crypto import load_rsa_key, aes_encrypt, rsa_encrypt pub_key = load_rsa_key() test_data = b"hello" cipher_aes = aes_encrypt(test_data, b"1234567812345678") # mock key cipher_rsa = rsa_encrypt(b"1234567812345678", pub_key) print("AES cipher len:", len(cipher_aes), "RSA cipher len:", len(cipher_rsa)) # 正常输出应为:AES cipher len: 16 RSA cipher len: 128(1024bit 公钥)

参数说明:aes_encrypt使用AES-128-CBC,IV 固定为b'0102030405060708'(与网易云 Web 端一致);rsa_encrypt使用PKCS1_v1_5填充,公钥来自core/crypto.py内置的硬编码字符串(非网络获取),避免启动时网络抖动导致初始化失败。

2.3 config.json 配置详解:哪些字段必须改,哪些可以留空?

config.json是运行前唯一需人工编辑的文件。其结构如下(已脱敏):

{ "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36", "cookie": "", "proxy": { "enable": false, "http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080" }, "concurrency": 3, "output_dir": "./output", "timeout": 15, "retry_times": 3, "music_id_list": [186016, 2884035], "playlist_id_list": ["7912345678", "2876543210"], "user_id_list": ["123456789", "987654321"] }
  • cookie:必须填写。网易云所有接口均需登录态,且Cookie中的MUSIC_U(用户 token)和__csrf(防跨站令牌)为必需。获取方式:登录网易云 Web 端 → F12 → Application → Cookies → 复制整行Cookie字符串(含MUSIC_U=xxx; __csrf=yyy;等)。不要只复制MUSIC_U,否则评论接口会 403。
  • proxy.enable:生产环境建议设为true并配置本地代理(如 Charles/Fiddler),便于抓包比对请求体;但首次运行请设为false,避免代理未启动导致连接超时。
  • music_id_list/playlist_id_list/user_id_list:三者选填其一即可。脚本会根据非空列表自动选择采集模式(歌曲详情、歌单详情、用户主页)。ID 可从 URL 中提取:https://music.163.com/#/song?id=186016→186016;https://music.163.com/#/playlist?id=7912345678→7912345678。
  • output_dir:输出目录,脚本会自动创建./output/songs/、./output/playlists/、./output/users/子目录,JSON 文件按 ID 命名(如186016.json)。

注意:user_agent字段虽可自定义,但不建议频繁更换。网易云服务端会对 UA 做简单聚类,过于随机的 UA(如每次启动都变)反而触发频率限制。保持默认值即可。


3. 核心加密逻辑拆解:params 与 encSecKey 是怎么算出来的?

3.1 网易云 Web 请求的双参数机制:为什么不能只传一个?

网易云 Web 端所有 POST 接口(如/weapi/v1/resource/comments/get/)均要求两个加密参数:

  • params:AES 加密后的请求体(JSON 字符串),密钥为 16 字节随机字符串(称为secretKey),IV 固定。
  • encSecKey:RSA 加密后的secretKey,公钥固定(1024 bit),用于服务端解密出secretKey,再用其解密params。

这个设计本质是“用 RSA 保护 AES 密钥,用 AES 保护业务数据”,兼顾性能与安全。NetEaseMusicSpider的core/crypto.py完整实现了该流程,无需 Node.js 或 PyExecJS。

3.2 params 生成四步法:从明文 JSON 到 base64 编码

以获取歌曲评论为例,原始请求体为:

{"rid":"R_SO_4_186016","offset":"0","total":"true","limit":"20","csrf_token":""}

crypto.py中encrypted_request()函数执行以下步骤:

  1. 生成随机 secretKey:16 字节字符串,如b'1234567812345678';
  2. AES 加密明文:使用AES-128-CBC,IV=b'0102030405060708',结果为 bytes;
  3. Base64 编码:将加密 bytes 转为 base64 字符串(如"zXy...abc==");
  4. URL 安全化:将+替换为-,/替换为_,=去掉(即 base64url 编码)。

对应代码段(已简化注释):

# core/crypto.py def encrypted_request(text): # text 是原始 JSON 字符串,如 '{"rid":"R_SO_4_186016",...}' secret_key = create_secret_key(16) # 生成 16 字节随机 key iv = b'0102030405060708' # 步骤2:AES 加密 cipher = AES.new(secret_key, AES.MODE_CBC, iv) # PKCS7 填充(长度必须是 16 的倍数) pad = 16 - len(text) % 16 text += chr(pad) * pad encrypted_text = cipher.encrypt(text.encode()) # 步骤3+4:base64url 编码 encoded = base64.b64encode(encrypted_text).decode().replace('+', '-').replace('/', '_').rstrip('=') # 步骤5:RSA 加密 secret_key 得到 encSecKey enc_sec_key = rsa_encrypt(secret_key, public_key) return encoded, enc_sec_key

参数说明:create_secret_key(16)并非真随机,而是用固定种子b'0CoJUm6Qyw8W8jud'+ 时间戳 MD5 后取前 16 字节——这与网易云 Web 端 JS 中window.createSecretKey行为完全一致。若你发现params解密失败,大概率是secret_key生成逻辑不匹配。

3.3 encSecKey 生成:RSA 公钥硬编码与填充细节

encSecKey是secret_key经 RSA 加密后的 base64 字符串。关键点在于:

  • 公钥是硬编码在crypto.py中的 PEM 格式字符串(1024 bit),非动态获取;
  • 加密使用PKCS1_v1_5填充,而非更现代的 OAEP;
  • 输出需 base64 编码,并做 URL 安全化(+→-,/→_,=→'')。

代码片段:

from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 def rsa_encrypt(data, public_key_pem): key = RSA.import_key(public_key_pem) cipher = PKCS1_v1_5.new(key) # data 必须是 bytes,且长度 ≤ 117 字节(1024bit RSA 最大明文长度) encrypted = cipher.encrypt(data) return base64.b64encode(encrypted).decode().replace('+', '-').replace('/', '_').rstrip('=')

血泪经验:曾有开发者误用RSA-OAEP或RSA-PKCS1(无_v1_5后缀),导致服务端无法解密encSecKey,返回{"code":-460,"msg":"illegal request"}。务必确认PKCS1_v1_5类型。


4. 避坑指南:五个高频翻车现场与当场修复方案

4.1 现象:运行后报错ModuleNotFoundError: No module named 'Crypto'

原因:pycryptodome安装后,Python 导入路径为from Crypto.Cipher import AES,但部分旧环境或 IDE 会优先找到系统自带的pycrypto(已废弃),其模块名为Crypto但 API 不兼容。
解决:彻底卸载pycrypto,并确认pycryptodome安装正确:

pip uninstall pycrypto -y pip install --force-reinstall pycryptodome==3.18.0 python -c "from Crypto.Cipher import AES; print('OK')"

4.2 现象:spider.py运行后卡住,日志显示GET https://music.163.com/robots.txt超时

原因:脚本启动时会先请求https://music.163.com/robots.txt检测域名连通性,但网易云该文件实际返回 404 且响应极慢(约 15s),timeout=15导致阻塞。
解决:修改spider.py开头的check_domain_health()函数,将robots.txt检查替换为轻量 HEAD 请求:

# 替换原 check_domain_health() 中的 requests.get(...) 为: try: resp = requests.head("https://music.163.com", timeout=5, allow_redirects=True) return resp.status_code in [200, 301, 302] except Exception: return False

4.3 现象:获取评论时返回{"code":200,"comments":[],"total":0},但网页上明明有评论

原因:cookie中缺失__csrf字段,或csrf_token参数未随请求体传入。网易云评论接口强制校验__csrfCookie 与请求体中csrf_token一致性。
解决:确保config.json中cookie字段包含__csrf=xxx;并在core/session.py的post()方法中,自动从 Cookie 提取__csrf值,注入到请求体:

# 在 post() 方法中添加: if 'csrf_token' in data and '__csrf' in self.cookies: data['csrf_token'] = self.cookies['__csrf']

4.4 现象:多线程采集时部分请求返回{"code":-460,"msg":"illegal request"}

原因:params和encSecKey生成函数encrypted_request()未加锁,多线程并发调用时secret_key生成逻辑(基于时间戳)可能冲突,导致一对参数不匹配。
解决:在core/crypto.py中为encrypted_request()添加线程锁:

import threading _crypto_lock = threading.Lock() def encrypted_request(text): with _crypto_lock: secret_key = create_secret_key(16) # ...后续加密逻辑不变

4.5 现象:导出的 JSON 文件中comments字段为空,但日志显示200 OK

原因:网易云对评论接口做了 User-Agent 强校验,若config.json中user_agent与session.py实际发送的 UA 不一致(如被中间件覆盖),会静默返回空数组。
解决:在core/session.py的post()方法中,强制覆盖 headers:

def post(self, url, **kwargs): headers = kwargs.pop('headers', {}) headers.update({ 'User-Agent': self.user_agent, 'Referer': 'https://music.163.com/', 'Origin': 'https://music.163.com' }) kwargs['headers'] = headers return super().post(url, **kwargs)

5. 数据落地与结构验证:如何确认你拿到的是“真数据”而非缓存幻觉?

5.1 输出文件结构与字段完整性检查

脚本默认将数据按类型分目录存储:

  • ./output/songs/186016.json:歌曲详情(含名称、艺人、专辑、时长、播放量)
  • ./output/playlists/7912345678.json:歌单详情(含标题、描述、创建者、收藏数、trackIds 数组)
  • ./output/users/123456789.json:用户主页(含昵称、签名、关注数、粉丝数、动态列表)

每个 JSON 文件均包含meta字段,记录采集时间、URL、HTTP 状态码、响应耗时,例如:

{ "meta": { "url": "https://music.163.com/weapi/v1/resource/comments/get/", "status_code": 200, "elapsed_ms": 1245, "timestamp": "2023-12-05T14:22:33.892Z" }, "data": { "comments": [ { "commentId": 1234567890, "content": "这首歌循环了一百遍...", "time": 1698537600000, "likedCount": 2456, "user": {"userId": 987654321, "nickname": "听歌不睡觉"} } ], "total": 12456 } }

验证技巧:用jq快速检查字段存在性与数量:

# 检查 songs 目录下所有文件是否含 comments 字段且非空 find ./output/songs/ -name "*.json" -exec jq -e '.data.comments | length > 0' {} \; -print # 检查 playlists 中 trackIds 是否为数组且长度 > 0 jq -r 'select(.data.trackIds | type == "array" and length > 0) | .data.name' ./output/playlists/*.json

5.2 评论数据去重与时间排序:避免同一评论被多次采集

网易云评论接口支持offset分页,但total:true时返回总数,offset从 0 开始递增。脚本默认采集前 500 条(limit=20× 25 页),但存在重复风险(如用户快速刷新导致 offset 错位)。utils/deduper.py提供了基于commentId的去重工具:

# utils/deduper.py def dedupe_comments(json_path: str): with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) comments = data.get('data', {}).get('comments', []) seen_ids = set() unique_comments = [] for c in comments: if c.get('commentId') not in seen_ids: seen_ids.add(c['commentId']) unique_comments.append(c) # 按 time 降序(最新在前) unique_comments.sort(key=lambda x: x.get('time', 0), reverse=True) data['data']['comments'] = unique_comments with open(json_path, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=2) print(f"Deduped {len(comments)} → {len(unique_comments)} comments in {json_path}") # 批量去重 for f in Path('./output/songs/').glob('*.json'): dedupe_comments(str(f))

5.3 用 Postman 复现请求:当 Python 脚本失效时的终极排查法

当脚本突然大量 403,怀疑加密逻辑过期时,最可靠的方式是用 Postman 手动构造请求,比对参数:

  1. 在浏览器打开歌单页,F12 → Network → 找到weapi/v1/playlist/detail请求;
  2. 右键 → Copy → Copy as cURL (bash);
  3. 将 cURL 粘贴到 Postman 的 Import → Paste Raw Text;
  4. 查看 Body 中params和encSecKey值;
  5. 在 Python 中打印encrypted_request()的输出,与 Postman 中的值逐字符比对(注意 base64url 编码差异)。

玄学技巧:若发现params前几位字符总不一致,大概率是create_secret_key()中的时间戳精度问题。将int(time.time() * 1000)改为int(time.time() * 1000) // 10 * 10(截断到 10ms 级),可大幅提升匹配率。

从那以后我每次更新crypto.py,都强制走一遍 Postman 对比流程,哪怕只是改了一个空格。因为网易云的加密逻辑就像个黑匣子,你永远不知道哪次提交悄悄动了时间戳生成规则。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询