网易云音乐、将某个歌单的所有音乐迁移(复制)到另一个歌单 ,以“我喜欢的音乐”为例

网易云音乐、复制(迁移)某个歌单,将所有音乐粘贴至另一个歌单,以“我喜欢的音乐”为例

我的需求

“我喜欢的音乐”,总共5522首。创了个新号,想把5522首音乐重新添加一遍,部分歌曲无版权(不能播放也不能添加进歌单)

image-20231021013801597
PS P:\NodeJS\NeteaseCloudTrans> node main.js
共计 5522 首歌曲, 3 秒后开始执行
5522 | "添加成功" | 17227545 | Where The Hood At
5521 | "添加成功" | 16441608 | Queen of Chinatown
5520 | "添加成功" | 27108022 | Shooting Stars
5519 | "无版权" | 35032211 | China
5518 | "添加成功" | 455500439 | Revolution
5517 | "添加成功" | 857896 | A Little Story
5516 | "添加成功" | 33916563 | Nyan Cat
5515 | "添加成功" | 29724671 | Merry Go
5514 | "添加成功" | 428375575 | 不要怕
5513 | "添加成功" | 422463456 | Desert Fire (沙漠之焰)
17227545,16441608,27108022,455500439,857896,33916563,29724671,428375575,422463456
SUCCESS | 等待下一批
5512 | "添加成功" | 481697663 | 达拉崩吧(Cover 洛天依 / 言和)
5511 | "添加成功" | 29307600 | シロクマ
...

如何迁移

网易云音乐 API

本地部署 Binaryify/NeteaseCloudMusicApi: 网易云音乐 Node.js API service (github.com)

Win 系统安装 gitNodeJS ,然后开个 powershell 执行如下命令,即可部署

git clone https://github.com/Binaryify/NeteaseCloudMusicApi.git
cd NeteaseCloudMusicApi
npm config set registry https://registry.npmmirror.com # 使用清华镜像
npm install
node app.js

新建项目

新建一个 NodeJS 项目,随便起个项目名,全部回车,然后装网络请求库 axios

npm init new
#...
npm install axios

修改 package.json ,添加末尾的 type

{
  "name": "neteasecloudtrans",
  "version": "1.0.0",
  "description": "",
  "main": "work.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^1.5.1"
  },
  "type": "module" 
}

完整代码 ⭐

运行代码

  • 当前目录新建文件 main.js ,粘贴下面代码
  • 修改常量 playlistIDphonepassword
  • 控制台输入 node main 运行

注意事项

  • 如果账号没有开黑胶会员,SVIP的歌曲均无版权(自动跳过、不添加)
// 网易云音乐 | 将某个歌单的所有音乐迁移至另一个歌单 
// 2023年10月21日 By 小能喵喵喵 
// https://www.cnblogs.com/linxiaoxu/

import axios from 'axios'
import process from 'process'

// 测试歌单 https://music.163.com/playlist?id=809030968

const fromID = `809030968` // 复制的歌单ID
const toID = `7051184685` // 粘贴至歌单ID
const phone = `123456` // 手机号
const password = `123456` // 密码

const How_long_did_the_cat_sleep = 1000 // 猫猫要睡觉,每批间隔1000毫秒,防操作频繁
const How_many_cookies_did_the_dog_eat = 100 // 狗狗吃曲奇,每批处理100首音乐

// sleep 函数 Promise 版本
function sleep(time) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, time);
  });
}

const demo = async () => {
  // ^ 获取 Cookie
  let cookie = ``
  await axios.get(`http://localhost:3000/login/cellphone?phone=${phone}&password=${password}`, {
    timeout: 1000
  }).then(res => {
    cookie = res.data.cookie
  }).catch(err => {
    console.log(err)
  })

  // ^ 获取歌曲数量
  let count = 0;
  await axios.get(`http://localhost:3000/playlist/detail?id=${fromID}`, {
    timeout: 1000
  }).then(res => {
    count = res.data.playlist.trackCount
  }).catch(err => {
    console.log(err)
  })
  console.log(`共计 ${count} 首歌曲, 3 秒后开始执行`)
  await sleep(3000)
  // ^ 倒序 (从旧到新) 按批次迁移歌曲 
  while (count > 0) {
    // @ 获取当前批次歌曲
    count = count > How_many_cookies_did_the_dog_eat ? count -= How_many_cookies_did_the_dog_eat : 0;
    let songs = [];
    await axios.get(`http://localhost:3000/playlist/track/all?id=${fromID}&limit=${How_many_cookies_did_the_dog_eat}&offset=${count}`, {
      timeout: 1000
    }).then(res => {
      songs = res.data.songs
    }).catch(err => {
      console.log(err)
    });
    // ^ 遍历每一首歌曲,检查版权
    songs = songs.reverse()
    let songString = ``
    for (let i = 0; i < songs.length; i++) {
      // @ 检查版权
      let success = false;
      let song = songs[i];
      await axios.get(`http://localhost:3000/check/music?id=${song.id}&cookie=${cookie}`, {
        timeout: 1000
      }).then(res => {
        success = res.data.success;
      }).catch(err => {
        console.log(err)
      })
      // @ 如果有版权则字符串拼接
      if (success) {
        songString = songString + song.id + ','
        console.log(`${count + songs.length - i} | "添加成功" | ${song.id} | ${song.name}`)
      }
      else
        console.log(`${count + songs.length - i} | "无版权" | ${song.id} | ${song.name}`)
    }
    // @ 删除最后一个 ,
    songString = songString.substring(0, songString.length - 1)
    console.log(songString)
    // @ 迁移音乐进目标歌单
    await axios.get(`http://localhost:3000/playlist/tracks?op=add&pid=${toID}&tracks=${songString}&cookie=${cookie}`, {
      timeout: 1000
    }).then(res => {
      console.log(`SUCCESS | 等待下一批`)
    }).catch(err => {
      console.log(err, "寄咯")
      process.exit()
    });
    // ^ 时间间隔过小连续调用 6 次直接操作频繁😅,睡1秒
    await sleep(How_long_did_the_cat_sleep)
  }
  console.log(`完成啦`)
}

demo()

代码思路

模拟 Sleep

模拟挂起线程,防操作频繁用

// sleep 函数 Promise 版本
function sleep(time) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, time);
  });
}

这里用的是账号密码登录,还可以使用邮箱登录、二维码登录,可看文档

let cookie = ``
await axios.get(`http://localhost:3000/login/cellphone?phone=${phone}&password=${password}`, {
timeout: 1000
}).then(res => {
cookie = res.data.cookie
}).catch(err => {
console.log(err)
})

获取歌曲数量

请求一遍歌单具体信息,为遍历做准备

let count = 0;
await axios.get(`http://localhost:3000/playlist/detail?id=${fromID}`, {
timeout: 1000
}).then(res => {
count = res.data.playlist.trackCount
}).catch(err => {
console.log(err)
})
console.log(`共计 ${count} 首歌曲, 3 秒后开始执行`)

批次歌曲获取

获取当前批次的歌曲列表

count = count > How_many_cookies_did_the_dog_eat ? count -= How_many_cookies_did_the_dog_eat : 0;
let songs = [];
await axios.get(`http://localhost:3000/playlist/track/all?id=${fromID}&limit=${How_many_cookies_did_the_dog_eat}&offset=${count}`, {
  timeout: 1000
}).then(res => {
  songs = res.data.songs
}).catch(err => {
  console.log(err)
});

批次歌曲处理

检查每一首歌曲是否有版权,如果有版权则将歌曲ID拼接进字符串

// ^ 遍历每一首歌曲,检查版权
songs = songs.reverse()
let songString = ``
for (let i = 0; i < songs.length; i++) {
  // @ 检查版权
  let success = false;
  let song = songs[i];
  await axios.get(`http://localhost:3000/check/music?id=${song.id}&cookie=${cookie}`, {
    timeout: 1000
  }).then(res => {
    success = res.data.success;
  }).catch(err => {
    console.log(err)
  })
  // @ 如果有版权则字符串拼接
  if (success) {
    songString = songString + song.id + ','
    console.log(`${count + songs.length - i} | "添加成功" | ${song.id} | ${song.name}`)
  }
  else
    console.log(`${count + songs.length - i} | "无版权" | ${song.id} | ${song.name}`)

迁移当前批次

如果提示操作频繁,默认直接报错退出

// @ 删除最后一个 ,
songString = songString.substring(0, songString.length - 1)
console.log(songString)
// @ 迁移音乐进目标歌单
await axios.get(`http://localhost:3000/playlist/tracks?op=add&pid=${toID}&tracks=${songString}&cookie=${cookie}`, {
  timeout: 1000
}).then(res => {
  console.log(`SUCCESS | 等待下一批`)
}).catch(err => {
  console.log(err, "寄咯")
  process.exit()
});

复制所有歌单的思路

获取一个账户的所有歌单,然后遍历歌单列表,调用迁移歌单的代码即可(需要调用新建歌单的API)

网易云音乐 NodeJS 版 API | 获取用户歌单


一个坑

foreach 不能用异步编程

Array.prototype.forEach 不适用于异步编程,不能用 async-await

解决方案

  • 使用for语法
  • 使用for of object语法
posted @ 2023-10-21 02:10  小能日记  阅读(430)  评论(0编辑  收藏  举报