网易云音乐、将某个歌单的所有音乐迁移(复制)到另一个歌单 ,以“我喜欢的音乐”为例
网易云音乐、复制(迁移)某个歌单,将所有音乐粘贴至另一个歌单,以“我喜欢的音乐”为例
我的需求
“我喜欢的音乐”,总共5522首。创了个新号,想把5522首音乐重新添加一遍,部分歌曲无版权(不能播放也不能添加进歌单)
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
系统安装 git
、NodeJS
,然后开个 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
,粘贴下面代码 - 修改常量
playlistID
、phone
、password
- 控制台输入
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);
});
}
获取 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 秒后开始执行`)
批次歌曲获取
获取当前批次的歌曲列表
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)
一个坑
foreach 不能用异步编程
Array.prototype.forEach 不适用于异步编程,不能用 async-await
解决方案
- 使用
for
语法 - 使用
for of object
语法