返回 Skill 列表
extension
分类: 数据与分析无需 API Key

网易云歌单整理技能

网易云音乐歌单自动整理/分类技能——读取用户的网易云歌单(如「我喜欢的音乐」), 按「曲风」或「情绪/表达主旨」重新分类,并自动创建独立歌单。

person作者: user_90897aafhubcommunity

网易云音乐歌单整理 Skill

概述

把用户的网易云歌单(典型场景:我喜欢的音乐,可能有上千首)按指定维度重新分类,并自动创建独立歌单。

两种分类维度(本 skill 均已跑通):

  1. 曲风:华语流行 / 粤语 / 摇滚 / 民谣 / R&B / 说唱 / 电子 / 国风古风 / 纯音乐 / 日韩 / 欧美。
  2. 情绪主旨:伤感失恋 / 甜蜜心动 / 治愈温暖 / 欢快律动 / 深情守候 / 释然洒脱 / 励志热血 / 怀旧思念 / 人生感悟 / 孤独落寞 / 江湖意境。

核心能力:

  • 免扫码登录:直接从运行中的网易云桌面客户端(Electron)本地数据里解密出登录 Cookie。
  • 匿名/登录态读歌单、歌曲详情、歌词、热评。
  • weapi 加密直连官方接口,建歌单、加歌。

环境准备

  • Python 3.13(venv 隔离),依赖:pycryptodomerequestsqrcodepillow
  • 安装(国内用清华镜像更快):
    python -m venv <venv_path>
    <venv>/Scripts/python -m pip install pycryptodome requests qrcode pillow -i https://pypi.tuna.tsinghua.edu.cn/simple
    

工作流程

Phase 1:获取登录 Cookie(免扫码,推荐)

网易云桌面客户端是 Electron 应用,登录后的 Cookie 存在 Chromium 的 SQLite 里、用 Windows DPAPI 加密。

位置(Windows,用户 hyl 示例):

  • Cookie 库:C:\Users\<user>\AppData\Local\Netease\CloudMusic\webapp91x64\Cookies(SQLite,表 cookies
  • 解密密钥:同目录 LocalPrefs.json 里的 os_crypt.encrypted_key

解密三步

  1. LocalPrefs.jsonos_crypt.encrypted_key(base64),去掉前 5 字节 DPAPI 前缀。
  2. CryptUnprotectData(crypt32.dll,当前用户上下文)解出 32 字节 AES-256 密钥。
  3. cookies 表里每个 encrypted_valuev10 前缀 = 3 字节 + 12 字节 nonce + 密文 + 16 字节 GCM tag),用 AES-GCM 解密。

核心代码(extract_cookie.py):

import sqlite3, base64, json, ctypes
from ctypes import wintypes
from Crypto.Cipher import AES

class DATA_BLOB(ctypes.Structure):
    _fields_ = [('cbData', wintypes.DWORD), ('pbData', ctypes.POINTER(ctypes.c_char))]

def dpapi_decrypt(data: bytes) -> bytes:
    b_in = DATA_BLOB(len(data), ctypes.cast(ctypes.create_string_buffer(data), ctypes.POINTER(ctypes.c_char)))
    b_out = DATA_BLOB()
    ctypes.windll.crypt32.CryptUnprotectData(ctypes.byref(b_in), None, None, None, None, 0, ctypes.byref(b_out))
    r = ctypes.string_at(b_out.pbData, b_out.cbData)
    ctypes.windll.kernel32.LocalFree(b_out.pbData)
    return r

def get_aes_key(prefs_path):
    enc = base64.b64decode(json.load(open(prefs_path, encoding='utf-8'))['os_crypt']['encrypted_key'])
    return dpapi_decrypt(enc[5:])  # 去 'DPAPI' 前缀

def decrypt_cookie(enc_value: bytes, key: bytes) -> str:
    d = enc_value[3:]                      # 去 'v10'
    nonce, ct = d[:12], d[12:]
    return AES.new(key, AES.MODE_GCM, nonce=nonce).decrypt_and_verify(ct[:-16], ct[-16:]).decode('utf-8', 'replace')

需要的 Cookie 字段:MUSIC_U(主凭证)、__csrf,可附带 MUSIC_Aos=pc 等。

若不想/无法读本地文件,可退回二维码登录,但注意接口已变更(见「踩坑」)。

Phase 2:读取歌单 + 歌曲详情(可匿名)

  • 用户歌单列表:GET https://music.163.com/api/user/playlist?uid=<uid>&limit=1000&offset=0
    • 「我喜欢的音乐」通常是第一个 trackCount 最大的歌单;用户 UID 可从客户端 info 文件或登录态 /weapi/w/nuser/account/get 拿到。
  • 歌单歌曲 ID:GET https://music.163.com/api/v6/playlist/detail?id=<playlistId>&n=2000&s=0playlist.trackIds
  • 歌曲详情(批量 ≤200/次):POST https://music.163.com/api/song/detail,body ids=[1,2,3](form-urlencoded)

这些「旧版」公开接口目前仍可用、且无需加密

Phase 3:抓歌词 + 热评(登录态 weapi)

情绪分类必须拿到歌词和热评:

  • 歌词:POST /weapi/song/lyric,body {id, lv:1, kv:1, tv:-1}lrc.lyric(需清洗 [mm:ss.xx] 时间戳与「作词/作曲」元信息行)
  • 热评:POST /weapi/v1/resource/comments/R_SO_4_{id},body {rid, limit:12, offset:0, orderType:1}hotComments + comments

Phase 4:分类

曲风维度(无歌词也可做):语言检测 + 歌手→流派映射 + 标题关键词 + 纯音乐兜底。

  • 语言:日文假名/韩文谚文/纯拉丁/汉字分别判为日/韩/欧美/华语。
  • 流派:对 Top 歌手建映射表(Beyond→摇滚、陶喆→R&B、赵雷→民谣…),覆盖绝大多数歌曲;长尾歌手回落到「流行」。

情绪维度(更准,但更费劲):强烈建议逐首人工通读判定

  • 关键词打分对「爱情类」歌(甜蜜 vs 伤感 vs 深情)误判率极高——「我爱你」既出现在甜歌也出现在苦情歌里;「快乐」会被「你不是真正的快乐」这种否定句骗到。
  • 可靠做法:批量抓歌词+热评 → 生成复核清单(每首一行:歌名/歌手/歌词摘要/热评摘要)→ 逐首人工判定(热评是极好的情绪佐证,如"意难平/听哭了"→伤感,"太甜了/恋爱了"→甜蜜)。
  • 纯音乐/影视配乐统一归「治愈温暖」。

Phase 5:建歌单 + 批量加歌(weapi,登录态)

  • 建歌单:POST /weapi/playlist/create,body {name, privacy:0, type:'NORMAL'}id
    • 注意是 /weapi/...,不是 /api/.../api/playlist/create 会返回 400「请求参数错误」)。
  • 加歌:POST /weapi/playlist/manipulate/tracks,body {op:'add', pid, trackIds:'["id1","id2"]', imme:'true'}
    • 若返回 code 512,降级到 /api/playlist/manipulate/trackstrackIds 每个 id 重复一次。
  • 删歌单:POST /weapi/playlist/remove,body {ids:'[pid]', pid}

关键技术:weapi 加密(Python 实现)

网易云 weapi 是「AES-128-CBC 双层加密 + RSA 封装密钥」,用 pycryptodome 实现:

import json, os, base64
from Crypto.Cipher import AES
from Crypto.PublicKey import RSA
from Crypto.Util.Padding import pad

PRESET_KEY = '0CoJUm6Qyw8W8jud'
IV = '0102030405060708'
BASE62 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
PUBLIC_KEY = ('-----BEGIN PUBLIC KEY-----\n'
    'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37'
    'BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV'
    '8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44on'
    'caTWz7OBGLbCiK45wIDAQAB\n-----END PUBLIC KEY-----')

def _aes(text: str, key: str, iv: str) -> bytes:
    return AES.new(key.encode(), AES.MODE_CBC, iv.encode()).encrypt(pad(text.encode(), 16))

def weapi_encrypt(obj: dict) -> dict:
    text = json.dumps(obj, ensure_ascii=False)
    secret = ''.join(BASE62[os.urandom(1)[0] % 62] for _ in range(16))
    inner = base64.b64encode(_aes(text, PRESET_KEY, IV)).decode()
    params = base64.b64encode(_aes(inner, secret, IV)).decode()
    k = RSA.import_key(PUBLIC_KEY)
    m = int.from_bytes(b'\x00' * (128 - 16) + secret[::-1].encode(), 'big')  # RSA_NO_PADDING,左补零到 128 字节
    enc_sec_key = format(pow(m, k.e, k.n), 'x')
    return {'params': params, 'encSecKey': enc_sec_key}

请求方式:POST https://music.163.com<path>,body 为上述两个字段(form-urlencoded),Header 带 Cookie + Referer: https://music.163.com/


踩坑记录(重点!)

  1. 二维码登录接口已变更:轮询端点从 /weapi/login/qrcode/clientlogin 改为 /weapi/login/qrcode/client/login(多一个 /);且 type=1 会返回 8821「请切换其他登录方式或升级新版本再试」(旧客户端被弃用)。首选直接读桌面客户端 Cookie,别走扫码。
  2. 建歌单接口/weapi/playlist/create(不是 /api/...)。
  3. 频率限制:批量建歌单/加歌会触发 code 405「操作过于频繁」,需加 6–8 秒间隔 + 重试;偶发空响应(JSONDecodeError)也要 try/except 重试,不能只判断 code。
  4. 关键词分类偷懒会导致大量误判:情绪分类务必逐首通读歌词+热评;热评是情绪佐证金矿。
  5. 下架歌曲:个别歌返回 401「下架歌曲无法收藏」,属正常,跳过即可。

脚本清单

完整可复用脚本见本 skill 的 scripts/ 目录:

| 文件 | 作用 | |---|---| | netease_client.py | weapi 加密 + NeteaseClient(_weapi / qr_unikey / qr_check) | | extract_cookie.py | DPAPI 解密桌面客户端 Cookie → cookie.txt | | fetch_songs.py | 拉歌单 + 歌曲详情 → songs.json | | fetch_lyrics.py | 批量拉歌词 → lyrics.json | | fetch_comments.py | 批量拉热评 → comments.json | | make_review.py | 生成人工复核清单 → review.txt | | classify.py | 曲风分类(歌手映射 + 语言检测) | | classify_emotion.py | 情绪分类(关键词基线,供参考;最终以人工复核为准) | | create_playlists.py | 建歌单 + 加歌(含频率限制重试) |

脚本内路径为硬编码,复用时替换为实际工作目录即可。

交付与收尾

  • 分类结果用 present_files 或内联图表展示分布。
  • 敏感提示:cookie.txt 是登录凭证,用完提醒用户删除。
  • 若用户对某分类不满意,直接「删旧歌单 → 重建」是最干净的做法(/weapi/playlist/remove 逐条删除,再重建)。