ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

网易云音乐爬虫失败复盘:接口加密与风控实战经验

网易云音乐爬虫失败复盘:接口加密与风控实战经验

一直想做点跟音乐数据分析相关的项目,当时把眼光投到了网易云音乐上。理由很简单:它有丰富歌单、评论和用户行为数据,而且Web端有公开接口,理论上只要能模拟浏览器请求就能拿到数据。于是我开始写一个爬虫,目标是自动采集指定歌单的歌曲信息、评论内容以及热门评论,做成一个小的情感分析Demo。然而这个项目最终以失败告终,而且失败得相当彻底——不是代码写不出来,而是数据链路根本走不通。如果你也打算做类似的事情,这篇复盘应该能帮你省下大量试错时间。

先交代结果:项目做到一半就停掉了,原因集中在三块——接口加密、风控拦截、以及数据内容本身的可获取性。这三块任何一个都能单独搞垮一个爬虫项目,而它们叠加在一起时,基本就是劝退级别的难度。下面我会把每个环节拆开讲,记录我实际踩过坑,也聊聊为什么失败的根源不是“技术不行”,而是“对抗成本高到没有意义”。

1. 项目目标与整体设计拆解

1.1 最初我想做什么:从歌单到评论的数据链路

整个目标很朴素:输入一个歌单ID,自动拉取这个歌单下的所有歌曲信息,包括歌名、歌手、专辑、时长、热度,再对每首歌拉取热门评论,按情感倾向做一个简单分类。

从数据流看,整个链路是这样:

  1. 获取歌单详情接口,拿到歌曲ID列表。
  2. 通过歌曲ID批量获取歌曲详细信息。
  3. 通过歌曲ID获取热门评论。

看起来就是三个HTTP请求的事。但在实际执行时,从第一步开始就出问题了。网易云音乐的Web端接口并非完全开放的,它有动态加密参数、登录态校验和风控策略,普通爬虫会过得很痛苦。

1.2 技术选型:requests + 模拟浏览器 + 逆向加密,为什么这样选

初版方案用的是 Python + requests。为什么不用 Selenium 或 Playwright?因为纯 HTTP 请求效率高、开销小、容易控制节奏。当时预估要采集几万条评论,如果全部用浏览器渲染,不仅速度慢,还容易被检测到自动化操作。

所以我的整体设计如下:

  • 请求层:requests.Session 保持 Cookie 和 CSRF Token。
  • 解密层:模拟网易云音乐的 Web 端加密逻辑,生成params和encSecKey参数。
  • 解析层:通过 JSON 解析接口响应,提取字段。
  • 存储层:写入 SQLite,方便后续分析。

这套设计本身没问题,问题在于“解密层”几乎耗尽了我所有时间。加密不是起保护作用吗?没错,但真正的难点不是加密算法本身,而是它的更新频率和风控组合拳。

1.3 第一版架构设想:请求层、解密层、解析层

具体到代码架构,我是这样规划的:

fetch_songlist() -> parse_song_ids() -> fetch_song_detail(song_id) -> fetch_comments(song_id) -> save_to_db()

其中每个 fetch 都封装了加密参数生成、请求头处理、异常重试。当时照着网上的旧教程写了一个可用版本,能跑通单次请求。可一旦循环超过几十次,就开始出现验证码和接口错误。这说明“能跑通”和“能稳定跑”之间隔着一整座大山。

2. 核心难点解析:为什么网易云音乐这么难爬

2.1 接口层:公开的API其实并不是真正的“公开”

网易云音乐 Web 端确实有接口,比如:

POST /weapi/song/v3?csrf_token=xxx

它可以批量查询歌曲信息。但这里的weapi并不是普通 API,而是一种类似 GateWay 的接口集合,它要求必须携带经过加密的请求体。也就是说,即使你拿到了接口地址,没有正确的加密参数,服务端也会返回一个类似{"msg":"invalid-request"}的响应。

这里有一个认知误区:很多人以为爬虫最难的是反爬虫封IP,其实比封IP更前置的关卡是“请求参数不合法”。即便你用最快的代理池,如果参数不对,也会在业务层被拒掉。

2.2 加密层:params和encSecKey是怎么来的

这是整个项目最大的坑。

Web端请求体里有两个关键字段:params和encSecKey。它们分别代表 AES 加密后的请求数据,以及 RSA 加密后的 AES 密钥。整个加密流程大致是:

  1. 先生成一个随机 16 字节的 AES Key(不是固定的)。
  2. 用 AES-CBC 模式加密原始 JSON,填充方式为 PKCS7。
  3. 把 AES Key 再用 RSA 公钥加密,得到 encSecKey。
  4. 将加密后的密文和加密后的 Key 一起作为表单参数提交。

所以,你不能硬编码某一个encSecKey,因为每次请求的 AES Key 都不同。必须模拟出完整的加密过程。

最初我从旧文章里拿到了一个固定的encSecKey字符串和一版 JS 代码,刚开始能跑。但用了一段时间后发现部分接口开始返回错误码,比如-460。后来排查发现,平台升级了加密逻辑,旧的encSecKey值被服务端识别为无效或过期。

这意味着如果只靠“网上抄一个固定值”,项目一开始就注定短命。你必须自己逆向当前版本的 JS 文件,提取最新的公钥和加密逻辑。请注意,这已经涉及持续对抗了——不是一次性劳动,而是每一次平台更新你都要跟着改。

2.3 风控层:封IP、验证码、请求频率限制

如果说加密是门票,风控就是保安。

我在踩过加密的坑后,请求频率控制得很低,大约每秒 1 次。但当请求量累计到几百次后,服务端开始要求验证码。验证码不是简单的字符识别,而是滑块验证,自动化绕过的成本很高。

更麻烦的是 IP 封禁。我在同一个 IP 下尝试通过修改请求头、调整 User-Agent 来规避识别,但最终还是触发了风控。具体现象是:请求开始返回{"code":-460,"msg":"操作频繁"}。后面查资料才发现,这个错误不仅跟频率有关,还可能跟设备指纹、Cookie 关联性、HTTP/2 指纹等因素有关。

你很难通过简单加延时来解决,因为风控判断维度是综合性的。它会把你的 Cookie、请求头、行为模式和真实用户做对比。爬虫的请求模式(比如间隔固定,请求顺序固定)很容易暴露。

3. 实操过程与关键环节复盘

3.1 第一步:从浏览器抓包开始,找到接口与请求参数

任何爬虫项目的第一步都是抓包。

打开浏览器开发者工具,切换到播放一首歌曲的页面,观察 Network 面板,能够看到大量的接口请求。其中比较核心的是:

  • 歌单详情接口:拿到歌曲 ID 列表。
  • 歌曲详情接口:拿到歌曲元数据。
  • 评论接口:拿到热门评论和最新评论。

这些接口都是 POST 请求,请求体里都有params和encSecKey。同时还会看到请求头里带有Cookie和Referer。

当时我以为只要把请求头和参数复制下来就能跑通。实验结果确实可以,但只能跑一次。因为接口返回的数据中包含了加密的响应内容?不,响应内容本身是 JSON,没有加密。真正的难点还是请求参数。

3.2 第二步:尝试无加密请求,直接撞墙

我试过最原始的方法:不带任何加密参数发起请求,或者直接带空的params和encSecKey。

结果非常统一:返回{"msg":"invalid-request","code":400}。

这个响应说明服务端在业务层就把请求拦截了,根本没有走到数据查询阶段。我当时天真地以为只要构造正确的请求头就行,直到仔细对比了 curl 命令和浏览器请求才发现,请求体被加密后,普通人类根本看不懂里面传的是什么。

这个阶段让我深刻体会到:网易云音乐的 Web 端接口本质上是给自家前端用的,不是给第三方开发者用的。它的一切都不是“API 文档”意义上的开放接口,而是内部接口,只不过恰好被浏览器暴露出来而已。

3.3 第三步:破解加密过程中踩过的坑

为了模拟加密,我开始看网上已有的开源项目。

很多项目都会引用一段core.js,其中包含aesEncrypt和rsaEncrypt逻辑。我照着实现了一个 Python 版本,核心代码如下:

import base64 import random import json from Crypto.Cipher import AES from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 def aes_encrypt(text, aes_key): iv = '0102030405060708' cryptor = AES.new(aes_key.encode(), AES.MODE_CBC, iv.encode()) pad = 16 - len(text.encode()) % 16 text += chr(pad) * pad ciphertext = cryptor.encrypt(text.encode()) return base64.b64encode(ciphertext).decode() def rsa_encrypt(text, pub_key): key = RSA.importKey(pub_key) cipher = PKCS1_v1_5.new(key) return base64.b64encode(cipher.encrypt(text.encode())).decode()

这里最大的坑是“填充方式”。AES 加密需要填充,服务端用的是 PKCS7,但如果你习惯用 PKCS5,在大多数情况下两者等价,可一旦字符串长度刚好是块大小的整数倍,PKCS5 和 PKCS7 可能在某些实现下不一样,导致服务端解密失败。

另一个坑是 RSA 加密的输入是那 16 字节的 AES Key,而 Key 本身是随机生成的。如果你用一个固定的 Key,那每次提交的params虽然不同(因为请求内容不同),但encSecKey永远相同,这会成为风控识别的强特征。我当时就是这个原因被识别为机器人。

正确的做法是每次请求都随机生成一个 AES Key。但我之前从网上复制的代码是硬编码的 Key,导致所有请求的encSecKey一致。后来才发现,某些开源项目或者教程为了省事,会把 Key 固定。这种固定 Key 在极低频率下可能没问题,但只要量一上来,必被封。

3.4 第四步:突破加密后遇到的验证码与风控

当我终于让加密请求稳定返回数据时,我看到了曙光。

我拉取了一个歌单下的 20 首歌曲,再逐一请求热门评论,前 30 次请求都非常顺利,返回的数据也很完整,包括评论内容、点赞数、评论时间等。然而到了第 50 次左右,一个页面突然弹出了验证码,不是直接返回 JSON,而是返回一个 HTML 页面,里面是一个滑块验证码。

滑块验证码的可怕之处在于:它不是一个简单的 Python 库能解的图片验证码,它需要模拟人的滑动轨迹。如果用 requests 直接维持 session,服务端会要求你完成验证后才能放行。我还试过把验证码的 URL 丢给浏览器手动完成,对接成本很高。

而且就算你通过了滑块验证,原来的 Cookie 也可能被标记,下一次请求仍然会触发验证。当时我的 IP 很快就进入了“观察模式”,表现为几乎所有接口都返回-460。

3.5 最后一步:数据解析和存储的意外麻烦

在还没被封之前,我其实已经拿到了部分评论数据。原以为接下来就是简单的 JSON 解析和数据入库,结果又碰到了两个问题。

第一个是数据结构嵌套很深。评论数据里包含了用户信息、评论内容、点赞列表、回复评论等对象,想提取纯文本评论需要写不少解析逻辑。

第二个是评论内容并不全在“热门评论”里。网易云音乐的评论接口区分“热门评论”和“最新评论”,热门评论只有 20 条左右,最新评论需要通过参数滚动翻页。而翻页时又需要携带一个cursor参数,这个参数的值通常是上一次返回的最后一笔时间戳排序信息,不是简单的 page=2 就能搞定的。

我好不容易理清了游标逻辑,风控就开始限流了。于是整个项目陷入了“调通接口 -> 拉取少量数据 -> 触发风控 -> 想尽办法解封 -> 调通接口”的死循环。

4. 常见问题与排查技巧实录

4.1 问题速查表:加密参数错误、Cookie失效、请求被拒绝

我整理了一张我实际遇到的问题对照表,希望能给你一些排查思路。

现象可能原因排查方向
返回code:400 invalid-requestparams和encSecKey缺失或格式错误检查加密逻辑,确认 AES/RSA 是否生成正确
返回code:-460操作频繁请求频率过高或设备指纹异常降低频率,检查 Cookie 与请求头的一致性
返回完整 HTML 页面且包含滑块验证被风控识别为机器人考虑改用浏览器渲染,或更换 IP,但这种方法不持久
接口能访问但数据少评论接口有热门和最新之分确认是否使用正确游标参数,并检查翻页逻辑
encSecKey固定不变AES Key 没有随机生成每次请求动态生成新的 AES Key
Cookie 过期登录态失效重新获取 Cookie,或避免访问需要登录才能查看的高权限内容
请求被 RST 或超时IP 被临时屏蔽更换网络环境或等待封禁解除,但根本还是要减少指纹暴露

4.2 为什么你的请求总是返回 “-460” 等错误码

这是整个项目里最令人头疼的问题。

-460的错误信息是“操作频繁”,但它不是简单的频率限制。我自己测试后发现,即使用同一个 IP 每 10 秒发一次请求,依然可能触发。这说明判断维度包含“请求指纹”,包括:

  • Cookie 是否包含特定的标记;
  • encSecKey是否每次变化;
  • 请求体结构是否和浏览器生成的一致;
  • 请求头顺序是否符合常规浏览器;
  • 是否使用 HTTP/2(如果只支持 HTTP/1.1 反而可疑);
  • 是否有大量请求间的时间间隔高度一致。

要模拟这一切,成本已经接近重写一个浏览器内核了。我当时试着用curl_cffi模拟 Chrome 的指纹,稍微缓解了一点,但没有根本解决问题。

4.3 真实遭遇:从正常请求到被限制的全过程

记录一次印象深刻的封禁过程:

第一次请求,一切正常。第二次到第十五次,正常。从第十六次开始,响应时间明显变慢,由原来的 100 毫秒变成 800 毫秒。到第二十次,返回了code:-460。我换了一个 IP,重新运行,能正常请求 30 次左右,随后再次触发验证码。

期间我尝试了以下方法:

  1. 修改User-Agent为真实浏览器版本,无效。
  2. 修改Referer为歌曲详情页,无效。
  3. 增加随机延时(2~5秒),有效但只延长了寿命,最终仍被限制。
  4. 清除所有 Cookie,无登录态访问,依然被限制。
  5. 使用代理 IP,但因为目标接口对数据中心 IP 比较敏感,很多代理 IP 本身就是黑名单。

最终结论是:在不模拟完整浏览器行为的前提下,想要稳定大批量采集曲库数据,几乎不可能。这已经超出了普通爬虫的范畴。

5. 失败总结与合规反思

5.1 技术输在哪里:反爬对抗是持续军备竞赛

复盘整件事,我认为技术上输在三个层面:

第一个层面是加密参数。虽然我能模拟,但平台的 JS 代码更新后,旧模拟方案立刻失效。这就像是你好不容易配了一把钥匙,结果门锁说换就换。

第二个层面是风控行为分析。爬虫请求与真人请求之间存在行为模式差异,而平台有能力通过统计模型来识别这种差异。仅仅依赖低频率、换 IP,无法避开行为层的判断。

第三个层面是成本。要真正稳定地采集,可能需要维护一份即时更新的 JS 逆向方案、一个高质量代理池、一个验证码识别模块,以及一套浏览器指纹模拟技术。这些加在一起的开发和维护成本,已经远超个人开发者能接受的范围。

5.2 法律与版权层面的红线

这段是我在整个项目中最应该提前考虑的部分。

爬取公开数据用于学习,听起来合理,但“公开”并不等于“可以自由采集”。网易云音乐的曲库、评论、用户数据都受到相关法律的保护。大批量采集并保存用户评论,可能涉嫌对平台和用户权益的侵害。如果采集后用于商业分析、学术发表甚至出售,更可能在法律上构成违约或侵权风险。

我当时只想着技术验证,忽略了对数据来源合法性的评估。这也是项目失败后我反思最深刻的地方:技术道路上的失败反而帮我规避了更严重的法律后果。

如果你确实需要这些数据做分析,应该优先走正规途径,比如与平台合作,或者使用其官方开放 API,又或者只使用基于用户主动贡献或已明确开放许可的数据集。

5.3 从失败中收获的替代方案:官方API、公开数据集、离线分析

项目失败不等于分析做不成。我后续换了一个思路:不使用爬虫,而是寻找公开可下载的音乐评论数据集,或者通过平台开放的分享接口筛选有限数量的歌曲进行人工分析。

如果你只是想做情感分析Demo,可以考虑:

  • 使用公开的 Kaggle 音乐评论数据集;
  • 自己从本地音乐软件导出的个人听歌历史记录;
  • 通过用户自愿提供的评论文件进行文本分析。
  • 使用平台公开分享的 API 中明确允许调用的部分。

这些方式合规,也足够支撑算法模型验证。

6. 个人经验分享

6.1 如果再来一次,我会怎么做

如果让我重新启动类似项目,我不会从零开始逆向 JS 和对抗风控。我会先做两个事情:

第一,明确数据最小范围。不需要全站数据,不需要海量评论,只取几个歌单的少量样本就足够分析。

第二,优先尝试官方合作可能。虽然个人开发者很难直接获得授权,但可以通过某些公开数据平台间接获取。

技术层面,如果非要练手爬虫,我会选择一些反爬程度较低、开发者友好的网站。等熟悉了请求、解析、存储的完整流程,再逐步研究复杂加密机制。一开始就碰网易云音乐这种级别的目标,挫败感会非常强。

6.2 给新手爬虫学习者的建议

结合这次失败,说几句掏心窝的话。

爬虫的学习路径应该从“合法性”和“技术性”两条线同时走。技术上,先学 HTTP 基础、HTTPS 证书、请求头、Cookie、Session,再学 HTML 解析和 API 交互。当你觉得这些都很熟练了,再去研究加密和反爬。法律上,永远不要把你不会的网站的数据拿来练手。

我见过很多爬虫项目最后变成“为了破解而破解”,丢失了原始的数据分析目标。当你发现自己在加密和验证码上花的时间远远超过数据分析本身时,就应该停下来重新思考:这个方向还有没有别的路?

这次网易云音乐爬虫分析项目虽然失败了,但它让我学会了更重要的一件事:技术方案的可行性评估应该排在最前面。如果一开始我能冷静评估加密、风控和法律三座大山的难度,也许就不会白白消耗那么多周末。

最后再分享一个小技巧:在你准备对任何平台写爬虫之前,先用两周时间手动使用这个网站,把自己当成一个真实用户,感受一下它有哪些地方会触动风控。你会发现,很多平台的反爬策略,其实是在模仿真实用户的过程中慢慢被识破的。真正稳的方案,永远是遵守规则的那一种。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进