Node.js 实现网易云歌单自动扩展:从单曲到整张专辑一键生成扩展歌单

💡 灵感来源

这个功能的灵感源于我平时使用网易云音乐时的体验:
我们常常会自己创建歌单,收集喜欢的单曲,但这些单曲背后往往对应整张专辑
每当想一次性沉浸在多张专辑的音乐中时,总需要手动去查找、切换,非常耗时。

于是我想到:如果能有一个功能,根据已有歌单自动生成包含单曲对应整张专辑的扩展歌单,那就舒服了。


🛠 实现过程

为了实现这个想法,我选择了使用开源的网易云 API:
https://github.com/nooblong/NeteaseCloudMusicApiBackup

  • 我编写了脚本,全程借助 ChatGPT 逐步实现功能。 (脚本在最后面
  • 在配置环境和调试过程中,遇到了一些问题,也不断碰壁,但通过 AI 的帮助逐步解决。
  • 最终,脚本成功实现了自动扩展歌单的功能,并支持生成备份文件。
  • 甚至下面的功能描述和部分使用过程,也是 AI 帮助我整理和美化的,但基础逻辑和核心实现都是我自己完成的。
    代码已上传github https://github.com/GNChuhuda/netease-expandplaylist

功能

🎵 全新“歌单扩展”功能上线!

从精选单曲到完整专辑,一次扩展,沉浸畅听,让音乐体验翻倍!

🔥 功能亮点

  • 🎯 自动扩展歌单:将每首单曲对应专辑的所有歌曲一键汇聚
  • ⏱️ 省时省力:无需手动整理,一次生成完整歌单
  • 🌟 轻松发现更多作品:探索整张专辑和更多相关歌曲

⚡ 使用步骤超简单

  1. 📂 读取原歌单:快速获取你的收藏单曲
  2. 💿 拉取专辑歌曲:智能抓取每首单曲对应专辑里的全部歌曲
  3. 🆕 创建新歌单:一键生成专属扩展歌单
  4. 批量添加歌曲:自动添加整张专辑,让歌单瞬间丰满
  5. 💾 生成备份文件:支持 JSON/TXT 导出,方便保存或分享

让你的歌单,从精选单曲升级为完整专辑盛宴!🎶


功能展示

下面歌单从 30 首扩展到了 416

原始歌单 扩展歌单

示意图

1️⃣ 环境准备

安装 Node.js

  • 建议版本:v22+(其实16+应该是ok的)
  • 检查版本:
node -v

安装 Git

  • 用于克隆仓库:
git --version

2️⃣ 克隆 API 仓库

使用 nooblong/NeteaseCloudMusicApiBackup
自己随便找个D盘创建一个新的文件夹,然后在该文件夹打开powershell(shift+右键),再运行下面的命令

git clone https://github.com/nooblong/NeteaseCloudMusicApiBackup.git
cd NeteaseCloudMusicApiBackup

3️⃣ 安装依赖并添加新脚本

清空旧依赖(可选,一般可以跳过这步):

rd /s /q node_modules
del package-lock.jsonr

安装依赖(还是同一个地方powershell运行指令)

npm config set registry https://registry.npmmirror.com 
npm config get registry
npm install --verbose

(npm config get registry只是检查是不是用国内这个镜像)

⚠️ 碰到的问题及解决方法:

  • ECONNRESET / CERT_HAS_EXPIRED → 使用国内镜像 --registry=https://registry.npmmirror.com
  • EPERM mkdir → 检查缓存目录权限或改为 D 盘可写目录
    把文件expandPlaylist添加到这个文件夹中(相当于和node.js同一个目录)

4️⃣ 启动 API 服务

node app.js
  • 默认监听 http://localhost:3000
  • 确认终端显示 server running at 3000

5️⃣ 浏览器扫码登录

  1. 访问:http://localhost:3000
    看到一个界面
示意图
  1. 点第四个功能, 二维码登录,用网易云APP扫描二维码并确认登录
    (这里我记得这里按理说应该是会弹到网易云那个界面去二维码登录的)如果没有弹出或者出现问题,就参考http://localhost:3000/docs/#/?id=neteasecloudmusicapi 的说明文档的二维码登录的分步骤走,然后访问完/login/qr/create?key=xxx这个接口后网页上会显示qrurl字段,把这个url对应网页打开就能弹到网易云扫码登录界面
  2. 检查登录状态:访问http://localhost:3000/login/status
    • 返回了一堆信息,不是null就行
    • 返回 JSON 中包含 profile.nickname 可以确认是不是自己的账号

6️⃣ 获取浏览器 cookie

  • 进入网易云音乐首页,打开浏览器开发者工具 → Application → Cookies → 找到 MUSIC_U 或 Network → 任意请求 → Request Headers → Cookie → 找到 MUSIC_U 这个字段的值,需要把这段值修改填充到expandPlaylist.js文件中
- 示例: ```js const COOKIE = "MUSIC_U=你的_cookie_值_here;"; ```

⚠️ 注意:

  • cookie 会过期,需要重新扫码
  • 确保复制时没有换行

7️⃣ 改写并运行扩展歌单脚本

①expandPlaylist.js需要改的部分:

//填充cookie
const COOKIE = ""; 
//改成你要扩展的原始歌单的 ID(这个手机版复制分享歌单链接中可以看到,例如13391617669)
const PLAYLIST_ID = 1;
//扩展新建的歌单名字也可以修改
 const newPlaylistName = "扩展歌单";
  • 脚本:expandPlaylist.js

②运行命令:注意由于有一个窗口已经在跑API:app.js;所以需要新开一个powershell窗口运行如下指令

node expandPlaylist.js
  • 终端显示:
    • 原歌单数量
    • 涉及专辑数量
    • 扩展后总歌曲数
    • 新歌单 ID
    • 批量添加进度

8️⃣ 输出结果

  • JSON 文件:expanded_playlist.json
  • 文本文件:expanded_playlist.txt
  • 新歌单已在网易云可见,并加入所有扩展歌曲

脚本源码(expandPlaylist.js)

// expandPlaylist.js
const axios = require("axios");
const fs = require("fs");

// —— 请改成你的浏览器扫码登录后的 cookie —— //
const COOKIE = ""; 

const API_BASE = "http://localhost:3000"; // 本地 API 服务

// 改成你要扩展的歌单 ID
const PLAYLIST_ID = 1;

async function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// 创建 axios 实例,带上 cookie
const client = axios.create({
  baseURL: API_BASE,
  headers: { Cookie: COOKIE }
});

async function main() {
  try {
    console.log("读取原歌单中...");
    const playlistRes = await client.get("/playlist/track/all", {
      params: { id: PLAYLIST_ID, limit: 1000 }
    });

    const tracks = playlistRes.data.songs;
    console.log(`原歌单歌曲数:${tracks.length}`);

    const albumIdSet = new Set();
    tracks.forEach(track => {
      if (track.al && track.al.id) albumIdSet.add(track.al.id);
    });
    console.log(`涉及专辑数:${albumIdSet.size}`);

    const allSongIdSet = new Set();
    let index = 1;
    for (const albumId of albumIdSet) {
      console.log(`[${index}/${albumIdSet.size}] 拉取专辑 ${albumId}`);
      const albumRes = await client.get("/album", { params: { id: albumId } });
      const songs = albumRes.data.songs || [];
      songs.forEach(song => allSongIdSet.add(song.id));
      index++;
      await sleep(300); // 防止请求太快
    }

    console.log(`扩展后歌曲总数:${allSongIdSet.size}`);

    // —— 创建新歌单 —— //
    const newPlaylistName = "扩展歌单";
    console.log(`创建新歌单:${newPlaylistName}`);
    const createRes = await client.post("/playlist/create", null, {
      params: { name: newPlaylistName, privacy: 0 } // privacy: 0 公开, 10 私密
    });

    const newPlaylistId = createRes.data.id;
    console.log(`新歌单 ID:${newPlaylistId}`);

    // —— 批量添加歌曲 —— //
    const songIds = Array.from(allSongIdSet);
    console.log(`添加 ${songIds.length} 首歌曲到新歌单...`);

    const chunkSize = 100; // 网易云接口每次添加不超过 100 首
    for (let i = 0; i < songIds.length; i += chunkSize) {
      const chunk = songIds.slice(i, i + chunkSize);
      await client.post("/playlist/tracks", null, {
        params: { op: "add", pid: newPlaylistId, tracks: chunk.join(",") }
      });
      console.log(`已添加 ${i + chunk.length} / ${songIds.length}`);
      await sleep(500); // 避免接口过快
    }

    // —— 保存备份文件 —— //
    const result = {
      sourcePlaylistId: PLAYLIST_ID,
      totalSongs: allSongIdSet.size,
      newPlaylistId,
      songIds
    };

    fs.writeFileSync("expanded_playlist.json", JSON.stringify(result, null, 2));
    fs.writeFileSync("expanded_playlist.txt", songIds.join("\n"));

    console.log("扩展完成!");
    console.log(" - expanded_playlist.json");
    console.log(" - expanded_playlist.txt");

  } catch (err) {
    console.error("脚本出错:", err.response?.data || err.message);
  }
}

main();

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐