网易云音乐批量下载工具编写细节
最近编写了一个 网易云音乐下载工具,感觉效果不错,就造成了这期博客的诞生。详细的源代码和可执行程序在github上面下载: https://github.com/Lmh-java/163MusicDownloaderV1.1 喜欢的star fork 一下。总的来说技术难点分为两部分:1.爬虫 2.下载IO操作
part 1 软件的分层以及准备工作
软件分为四个package 分别是 main(用来存储主要的类) view (存储GUI类)util(工具类的存放) model(存储内部使用的模型类)
大致可以分为4层。
上图
调用到的外部类库:
commons.jar
dom4j.jar
fastjson.jar(因为涉及到json和xml数据结构所以都要用)
httpClient.jar
httpcore.jar
httpmime.jar
Jsoup.jar
…(还有一些无关紧要的jar包)
直接上图
有一些包已经弃用了,以后会优化代码并且删除。
part 2 软件核心工作流程
先上流程图,后面会阐述关键API1、2和关键代码。
part3 API 1、2介绍
本软件最重要的部分就是网络API部分,大家可以自己调用这两个api。
API 1
http://music.163.com/api/playlist/detail?id=
功能:通过歌单id解析获取歌曲id
参数:id=歌单id
调用方法:get 就可以了
上代码
public static ArrayList<Music> crawl(String listId,String cookie) throws ParseException, IOException {
CloseableHttpClient httpclient = HttpClients.createDefault();
CloseableHttpResponse response = null;
HttpGet httpGet = new HttpGet("http://music.163.com/api/playlist/detail?id=" + listId);
// httpGet.addHeader("Referer", "http://music.163.com/");
httpGet.addHeader("User-Agent",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36");
try {
response = httpclient.execute(httpGet);
} catch (IOException e) {
//处理异常
}
HttpEntity entity = response.getEntity();
if (entity != null) {
String json = EntityUtils.toString(entity, "utf-8").toString();
JSONObject jsStr = JSON.parseObject(json);
JSONObject results = jsStr.getJSONObject("result");
JSONArray tracks = results.getJSONArray("tracks");
for(int x=0;x<tracks.size();x++){
String name = (String)tracks.getJSONObject(x).getString("name");
String id = (String)tracks.getJSONObject(x).getString("id");
result.add(new Music(id,name));
}
}
return result;
}
主要就是get这个API然后通过返回的json来解析id
这里使用了一个json格式化校验小工具,向大家推荐一下:https://www.json.cn/
由于代码太长,无法所见,大家自己点进去看一下:http://music.163.com/api/playlist/detail?id=519471484
在result的tracks下面储存的全部都是歌曲信息,包括我们需要的id信息。
API 2
地址:http://music.163.com/weapi/song/enhance/player/url?csrf_token=7c8c959657fc0ee7f906a43143bf124
提交方法:POST
参数:cookie (可以抓包去自己获取) encSecKey params …
功能:获取歌曲对应的真实下载地址
//a为歌曲id
a = a.split(":::")[0];
CloseableHttpClient httpclient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String first_param = "{ids:\"[" + a + "]\", br: \"320000\", csrf_token:\"\"}";
String secKey = "FFFFFFFFFFFFFFFF";
// 两遍ASE加密
String encText = aesEncrypt(aesEncrypt(first_param, "0CoJUm6Qyw8W8jud"), secKey);
String encSecKey = rsaEncrypt();
HttpPost httpPost = new HttpPost(
"http://music.163.com/weapi/song/enhance/player/url?csrf_token=7c8c959657fc0ee7f906a43143bf124c");
httpPost.addHeader("Referer", "http://music.163.com/");
httpPost.addHeader("User-Agent",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36");
httpPost.addHeader("Cookie",cookie);
List<NameValuePair> ls = new ArrayList<NameValuePair>();
ls.add(new BasicNameValuePair("params", encText));
ls.add(new BasicNameValuePair("encSecKey", encSecKey));
UrlEncodedFormEntity paramEntity = new UrlEncodedFormEntity(ls, "utf-8");
httpPost.setEntity(paramEntity);
response = httpclient.execute(httpPost);
HttpEntity entity = response.getEntity();
if (entity != null) {
String json = EntityUtils.toString(entity, "utf-8").toString();
JSONObject jsStr = JSON.parseObject(json);
String json1 = jsStr.getString("data").replace("[", "").replace("]", "");
JSONObject jsStr1 = JSON.parseObject(json1);
result = jsStr1.getString("url");
}
response.close();
httpclient.close();
return result;
}
public static String aesEncrypt(String src, String key) throws Exception {
String encodingFormat = "UTF-8";
String iv = "0102030405060708";
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] raw = key.getBytes();
SecretKeySpec secretKeySpec = new SecretKeySpec(raw, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes());
// 使用CBC模式,需要一个向量vi,增加加密算法强度
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] encrypted = cipher.doFinal(src.getBytes(encodingFormat));
return new BASE64Encoder().encode(encrypted);
}
public static String rsaEncrypt() {
String secKey = "257348aecb5e556c066de214e531faadd1c55d814f9be95fd06d6bff9f4c7a41f831f6394d5a3fd2e3881736d94a02ca919d952872e7d0a50ebfa1769a7a62d512f5f1ca21aec60bc3819a9c3ffca5eca9a0dba6d6f7249b06f5965ecfff3695b54e1c28f3f624750ed39e7de08fc8493242e26dbc4484a01c76f739e135637c";
return secKey;
}
public static ArrayList<Music> doPost(ArrayList<Music> name,String cookie) throws Exception{
for(int x=0;x<name.size();x++){
String url =api(name.get(x).getId(),cookie);
name.get(x).setUrl(url);
}
return name;
}
大概的原理就是通过api获取的参数进行几次加密,具体的过程我只会分析enctype这个参数,params参数是我看了一个大佬的文章做出来的:http://element-ui.cn/news_show_25945.shtml
这次的博客大概就这样了吧,有bug或者提问可以在github https://github.com/Lmh-java/163MusicDownloaderV1.1 或者本文评论里面留言,谢谢,希望有更多大佬来指点迷津。