前言

国庆了呢~ 祝祖国生日快乐~~

有时候难免有下载音乐的需求,但是网易云就算是开了会员,下载的歌曲也不是 mp3 格式的,一整个歌单下下来基本只剩半数左右了,但是这种小问题怎么能难住一个爱折腾的程序员呢?于是就有了本篇

当然声明一下,本文提供方法无法下载 VIP 音乐。同时下载的音乐如果你要拿去商用什么的,侵权了也请自己负责喽。

原理 & 准备

既然网易云客户端的音乐可以直接听,那么大概是有办法下载到原音频的,一些特殊网站也有这种服务,但是如果要批量下载音乐就不好办了。而且我喜欢的音乐别人的歌单离线无法查看或无法查看全部内容,所以得另寻他法了。

于是乎很容易就在 GayHub 上找到了解决的方法。

就是你了 —— NeteaseCloudMusicApi

据文档,NeteaseCloudMusicApi 可以 跨站请求伪造 (CSRF), 伪造请求头 , 调用官方 API ,从而实现登录、查看歌单和歌曲详情等操作。


不过这种方法下载的音乐很多没有 tag 信息,所以可以先用网易云音乐下载一遍,然后把没下载的单独拎出来用 API 去下载。

这里按照文档部署完毕后就可以开始愉快地写代码了。

Coding

准备 WebClient

显然我们需要以一名登录用户的身份去访问这个 API,那么我们就需要先拿到登录状态的 Cookie。
为免去代码获取 Cookie 的麻烦操作,就直接用浏览器获取到 Cookie 吧。(说白了就是不会

按文档说明登录后 F12 查看 Cookie。与爬其他网站类似,一般都需要我们在多个 Cookie 中找到有用的一个或几个。这里很容易根据过期时间和名称排除其余三个,于是复制下剩下的这个就 OK 了。

如下 new 一个 WebClient 出来,之后可以很方便的进行操作了。

创建实体类

现在 .NET 自带的 Json 解析已经很快了,就懒得用 Newtonsoft.Json 了。不过目前自带的解析似乎不太方便使用 dynamic 类型,那就来几个模型类吧。

这里据需要写出属性就行,没必要把整个 Json 完整地转成模型类。

根据返回的 Json 写出下面几个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class DetailRoot
{
public Playlist playlist {get; set; }
}

public class Playlist
{
public List<Track> tracks {get; set; }
}

public class Track
{
public string name {get; set; }
public int id {get; set; }
}

1
2
3
4
5
6
7
8
9
public class SongRoot
{
public List<DataItem> data { get; set; }
}

public class DataItem
{
public string url { get; set; }
}

Program.cs

其他的大概就没有什么好说了的吧,无法就是循环一下然后进行下载,代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using System.Net;
using System;
using System.Collections.Generic;
using System.Text.Json;

namespace Netease
{
public struct Song
{
public string Name { get; set; }
public string Authors { get; set; }
public string Url { get; set; }
}

class Program
{
static void Main(string[] args)
{
WebClient webClient = new WebClient();
webClient.Headers.Add(HttpRequestHeader.Cookie, "MUSIC_U=c67******8de");

List<Song> songs = new List<Song>();

string jsonString = webClient.DownloadString(@"http://localhost:3000/playlist/detail?id=***");
var detail = JsonSerializer.Deserialize<DetailRoot>(jsonString); // 解析歌单
foreach (var track in detail.playlist.tracks)
{
string songJson = webClient.DownloadString(@"http://localhost:3000/song/url?id=" + track.id);
var song = JsonSerializer.Deserialize<SongRoot>(songJson); // 解析音乐

// 处理歌手,达到与网易云下载的歌曲相同的命名格式
string authorsString = "";
foreach (var item in track.ar)
{
authorsString += item.name + ",";
}

songs.Add(new Song()
{
Name = track.name,
Url = song.data[0].url,
Authors = authorsString.TrimEnd(','),
});
}

// 下载音乐
foreach (var item in songs)
{
string path = $"Downloads/{item.Authors} - {item.Name}.mp3";
Console.WriteLine(item.Name + ": " + item.Url);
webClient.DownloadFile(item.Url, path.Replace("*", string.Empty).Replace("?", string.Empty).Replace(":", string.Empty));
}

}
}
}

结束语

国庆两天假,悲夫!