最近在Github上看到一个项目,使用Vue搭建了一个Web播放器,可以播放一些需要vip的歌曲——NetEasyMusic.

想从上面下载一些杰伦的歌😁。无奈,杰伦的歌太多了,所以利用它所使用的QQ音乐Api,自己写了一个Java程序来批量下载歌曲。

本项目仅供学习使用,请尊重版权,请勿利用此项目从事商业行为! 本项目仅供学习使用,请尊重版权,请勿利用此项目从事商业行为! 本项目仅供学习使用,请尊重版权,请勿利用此项目从事商业行为!

所需技术:

  1. JSON对象的处理
  2. HttpURLConnection获取网络资源

1. QQ音乐Api

请大家移步QQ音乐Api

因为我这里的目的是下载歌曲,所以关系的接口是

  1. 搜索

  1. 下载链接

注意这里的完整链接是https://api.qq.jsososo.com/search?key=周杰伦

2. 流程

3. 实现

3.1 获取关键字

新建BootStrap类作为程序入口

1
2
3
4
5
6
7
8
9
10
11
import java.util.Scanner;

public class BootStrap {
public static void main(String[] args) throws IOException{
Scanner sc = new Scanner(System.in);
System.out.println("请输入歌名或歌手关键字:");
String keyword = sc.next();
// Crawl类下面会说到。
new Crawl().crawlMusic(keyword);
}
}

3.2 根据关键字构造URL

使用字符串拼接即可,代码和3.3放在一起。

3.3 从返回的JSON数据中获取歌曲的下载ID

新建Crawl类,并新建crawlMusic方法

引入jsoup包,maven设置为

1
2
3
4
5
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.2</version>
</dependency>

这里使用jsoup获取网页响应(注意不是DOM对象),需要忽略格式,并执行execute方法,再获取response的body

1
2
3
4
5
public void crawlMusic(String keyword) throws IOException{
String url = String url = "https://api.qq.jsososo.com/search?key=" + key;
Response res = jsoup.connect(url).ignoreContentType(true).execute();
String body = res.body();
}

接下来需要对JSON对象进行操作,引入fastjson,maven设置为

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.28</version>
</dependency>

将字符串转换为JSON对象,使用JSONObject静态方法parseObject

1
2
3
4
5
6
7
8
public void crawlMusic(String keyword) throws IOException{
String url = String url = "https://api.qq.jsososo.com/search?key=" + key;
Response res = jsoup.connect(url).ignoreContentType(true).execute();
String body = res.body();

// 处理JSON对象
JSONObject json = JSONObject.parseObject(body);
}

到这里,先分析一下JSON数据的格式。

发现提交搜索请求之后,返回的JSON数据中,只要取data的值,再取list值就可以得到歌曲信息的JSON列表。

然后分析列表中的元素

这里先达到目的就先提取下载ID的值就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void crawlMusic(String keyword) throws IOException{
String url = String url = "https://api.qq.jsososo.com/search?key=" + key;
Response res = jsoup.connect(url).ignoreContentType(true).execute();
String body = res.body();

// 处理JSON对象
JSONObject json = JSONObject.parseObject(body);
JSONObject data = json.getJSONObject("data");
JSONArray list = data.getJSONArray("list");
// 获取下载ID (注意这里不要用增强for)
for (int i = 0; i < list.size(); i++) {
String downloadApi = list.getJSONObject(i).getString("songmid");
String downloadURL = downloadURL(downloadApi);
downloadMusic(downloadURL);
}
}

3.4 根据下载ID构造URL下载歌曲

现在来完成downloadURL和downloadMusic两个方法。

downloadURL通过下载ID构造链接,请求到歌曲的真正下载链接。

data的值即是真正的下载链接。

1
2
3
4
5
6
7
public String downloadURL(String downloadApi) {
String downloadApi = "https://api.qq.jsososo.com/song/url?id=" + downloadApi;
Response res = jsoup.connect(downloadApi).ignoreContentType(true).execute();
String body = res.body();
JSONObject json = JSONObject.parseObject(body);
return json.getString("data");
}

根据真正下载链接下载歌曲,这里的流程是这样的

  1. 新建URL对象。
  2. 建立连接HttpURLConnection。
  3. 获取网络输入流。
  4. 通过缓存流写入到输出流当中。
  5. 关闭流,关闭连接。
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
public void downloadMusic(String url) {
String path = "D:\\";
FileOutputStream fileOut = null;
HttpURLConnection con = null;
InputStream inputStream = null;

try {
// 建立URL对象
URL httpUrl = new URL(url);
// 建立连接
con = (HttpURLConnection) httpUrl.openConnection();
con.connect();
// 获取网络输入流
inputStream = con.getInputStream();
// 建立缓存流写入文件
BufferedInputStream bis = new BufferedInputStream(inputStream);
fileOut = new FileOutputStream(path+"song.mp3");
BufferedOutputStream bos = new BufferedOutputStream(fileOut);
// 新建缓存字节数组用来传递数据
byte[] buf = new byte[4096];
int length = bis.read(buf);
while (length != -1) {
bos.write(buf, 0, length);
length = bis.read(buf);
}
// 关闭流和连接
bos.close();
bis.close();
con.disconnect();
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("下载完毕!");
}
}

到此,整个根据关键字下载歌曲的程序就基本写完了,可以运行BootStrap来下载歌曲啦😀

4. 优化

以上完成了下载歌曲的程序,但没有在控制台输出歌曲信息,而且我把下载歌曲的名字写死了,这样很不方便。另外也没有跳转下一页的功能,所以我优化了一下,完整代码如下。

新增功能

  • 根据需要选择下载的歌曲。
  • 询问是否查看下一页歌曲。
  • 默认下载flac音质,没有flac源则下载mp3格式音乐。
  • 设置歌曲名为下载文件名。

BootStrap.java

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
package com.ssl;

import java.io.IOException;
import java.util.Scanner;

public class Bootstrap {

public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(System.in);
System.out.println("请输入歌名或歌手:");
String keyword = sc.next();
int pageNo = 1;
while (true) {
new Crawl().crawlMusic(keyword, pageNo);
System.out.println("是否查看下一页歌曲?(y/n): ");
String select = sc.next();
if ("y".equals(select)) {
pageNo++;
System.out.println("查看下一页歌曲~");
} else {
System.out.println("欢迎下次再会~");
break;
}
}

}

}

Crawl.java

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package com.ssl;


import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.jsoup.Connection.Response;
import org.jsoup.Jsoup;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;

public class Crawl {

public void crawlMusic(String key, int pageNo) throws IOException {
String url = "https://api.qq.jsososo.com/search?key=" + key + "&pageNo=" + pageNo;
Response res = Jsoup.connect(url).ignoreContentType(true).execute();
String body = res.body();
JSONObject json = JSONObject.parseObject(body);
JSONObject data = json.getJSONObject("data");
JSONArray list = data.getJSONArray("list");

// 创建字典储存歌曲
Map<Integer, List> getSong = new HashMap<>();

for (int i = 0; i < list.size(); i++) {
JSONObject songInfo = list.getJSONObject(i);
// 歌曲名+歌手
String songName = getSongName(songInfo);
String singer = getSinger(songInfo);
// 歌曲下载Api
String downloadUrl = getDownloadApi(songInfo);
// 歌曲下载类型
int sizeflac = getSizeFlac(songInfo);
// 先将歌曲和链接储存到列表中
List<String> getSongSub = saveSong(songName, singer, downloadUrl, String.valueOf(sizeflac));
// 放入字典
getSong.put(i+1, getSongSub);
}
// 选择歌曲下载
selectMusic(getSong);
}

public String getDownloadApi(JSONObject o) {
String downloadApi = "https://api.qq.jsososo.com/song/url?id=" + o.getString("songmid");
return downloadApi;
}

public String getSongName(JSONObject o) {
return o.getString("songname");
}

public int getSizeFlac(JSONObject o) {
return o.getInteger("sizeflac");
}

public String getSinger(JSONObject o) {
return o.getJSONArray("singer").getJSONObject(0).getString("name");
}

public void goThroughMap(Map map) {
// 遍历HashMap
Set<Integer> indexSet = map.keySet();
for (int index: indexSet) {
System.out.println(index + ", " + map.get(index).toString());
}
}

public List saveSong(String... s) {
List<String> getSongSub = new ArrayList<>();
for (String s1: s) {
getSongSub.add(s1);
}
return getSongSub;
}

public String getDownloadUrl(String downloadApi) throws IOException{
Response res = Jsoup.connect(downloadApi).ignoreContentType(true).execute();
String body = res.body();
JSONObject json = JSONObject.parseObject(body);
System.out.println(json.getString("data"));
return json.getString("data");
}

public void selectMusic(Map map) {
goThroughMap(map);
Scanner sc = new Scanner(System.in);
System.out.println("请输入想要下载音乐对应的序号:(如果输入0,则下载以上全部歌曲; 输入-1,则不下载当前页面歌曲。)");
int index = sc.nextInt();
if (0 < index) {
download(map, index);
} else if (0 == index) {
for (int i = 1; i <= map.size(); i++) {
download(map, i);
}
} else {
System.out.println("不下载当前页歌曲!");
}
}

public void downloadSong(String url, String songName, String format) {
String path = "D:\\Media\\Music\\";
FileOutputStream fileOut = null;
HttpURLConnection con = null;
InputStream inputStream = null;

try {
// 建立连接
URL httpUrl = new URL(url);
con = (HttpURLConnection) httpUrl.openConnection();
// 连接指定的资源
con.connect();
System.out.println("正在下载中...");
// 获取网络输入流
inputStream = con.getInputStream();
BufferedInputStream bis = new BufferedInputStream(inputStream);
// 写入到文件
fileOut = new FileOutputStream(path + songName + "." + format);
BufferedOutputStream bos = new BufferedOutputStream(fileOut);

byte[] buf = new byte[4096];
int length = bis.read(buf);
//保存文件
while(length != -1)
{
bos.write(buf, 0, length);
length = bis.read(buf);
}
bos.close();
bis.close();
con.disconnect();

} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("下载完毕!");
}
}

public void download(Map map, int index) {
List songInfo = (List) map.get(index);
String downloadApi = (String) songInfo.get(2);
String sizeFlac = (String) songInfo.get(3);
System.out.format("下载的歌曲是:%s \n", songInfo.get(0).toString());
try {
// 如果sizeFlac为0,则下载MP3格式文件; 否则下载flac格式文件
if ("0".equals(sizeFlac)) {
String downloadUrl = getDownloadUrl(downloadApi);
System.out.println("下载mp3歌曲");
downloadSong(downloadUrl, (String) songInfo.get(0), "mp3");
} else {
String downloadUrl = getDownloadUrl(downloadApi + "&type=flac");
System.out.println("下载flac歌曲");
downloadSong(downloadUrl, (String) songInfo.get(0), "flac");
}

} catch (IOException e) {
e.printStackTrace();
}
}

}

使用过程

没有采用多线程,怕大佬的服务器扛不住,也怕封我的IP😂,可以听杰伦就好啦,要什么自行车~
一定不要商用!
下载其他歌手的歌也行哈。