先看最终效果图:
前提请下载:
(1)jsoup-1.11.3.jar
(2)JMF 2.1.1e
工作原理如下:输入一个网易云音乐URL,如:http://music.163.com/#/song?id=5048563
然后使用开源的JSoup分析框架源代码得到如下:
<script type="application/ld+json">
{
"@context": "https://ziyuan.baidu.com/contexts/cambrian.jsonld",
"@id": "http://music.163.com/song?id=5048563",
"appid": "1582028769404989",
"title": "I'll Be There For You",
"images": ["http://p1.music.126.net/DOjgC0ig0QrOD67aJM5kGg==/2532175279901155.jpg"],
"description": "歌手:The Rembrandts。所属专辑:Friends。",
"pubDate": "2005-10-01T00:00:00"
}
</script>
以及:
<a title="播放mv" href="/mv?id=5361246"><i class="icn u-icn u-icn-2"></i></a>
这样,就获得了该音乐的基础信息和MV地址(如果有MV的话)
比较重要的网址如下(外链MP3):
http://music.163.com/song/media/outer/url?id=5048563
用Java下载文件即可。同理下载专辑封面和MV也是如此分析。
获取歌词网址如下:
http://music.163.com/api/song/media?id=5048563
这样就获得了歌词。如果有LRC歌词,则默认显示LRC,否则是普通歌词,否则没有歌词(暂无或纯音乐)。
实例歌词如下:
{"songStatus":3,"lyricVersion":9,"lyric":"[00:11.780]So no one told you life was gonna be this way:\n[00:15.920]your jobs a joke, you're broke, your love life's D.O.A.\n[00:20.880]It's like you're always stuck in second gear,\n[00:24.980]And it hasn't been your day, your week, your month, or even your year, but\n[00:30.660]I'll be there for you, when the rain starts to pour\n[00:35.590]I'll be there for you, like I've been there before\n[00:40.690]I'll be there for you, 'cause you're there for me too.\n[00:48.260]You're still in bed at ten and work began at eight\n[00:53.220]You've burned your breakfast, so far things are goin' great.\n[00:59.120]Your mother warned you there'd be days like these\n[01:03.450]Oh but she didn't tell you when the world has brought you down to your knees that\n[01:09.160]I'll be there for you, when the rain starts to pour\n[01:14.200]I'll be there for you, like I've been there before\n[01:18.300]I'll be there for you, 'cause you're there for me too.\n[01:28.350]No one could ever know me, no one could ever see me\n[01:35.190]Seems you're the only one who knows what it's like to be me.\n[01:40.250]Someone to face the day with, make it through all the rest with\n[01:46.960]Someone I'll always laugh with, even at my worst I'm best with you, yeah.\n[02:12.100]It's like you're always stuck in second year\n[02:16.300]And it hasn't been your day, your week, your month, or even your year.\n[02:23.340]I'll be there for you, when the rain starts to pour\n[02:28.340]I'll be there for you, like I've been there before\n[02:32.260]I'll be there for you, 'cause you're there for me too.\n[02:41.010]I'll be there for you, I'll be there for you,\n[02:50.870]I'll be there for you 'cause you're there for me too...\n[02:57.580]How you doin'?\n[02:58.580]\n","code":200}
需要注意的是,歌词里可能会显示“\n”或者"\r",此时就需要String.format()转换格式或将"\\r\\n"转换成"\r\n",以便保存到文件里。(庆祝微软记事本重大升级)
适当修改格式即可正常显示。效果如下:
选择适当选项即可下载。下载后:
剩下附加的诸多功能就不说了。关键代码如下:
HTTPAnalyzer.java
// Coding starts here
// Version 3.0 专用
import java.io.IOException;
import org.jsoup.Jsoup; // jsoup-1.11.3
/**
* HTTPAnalyzer类。分析网页框架,获得有用信息
* @author 赵利昂
* @since 0.5
*/
public class HTTPAnalyzer
{
private String myURL = ""; // 用户输入的URL地址
private String URL_id = ""; // 解析得到的ID
private String frameURL = ""; // 网页框架地址
private String bodyCode = ""; // 框架源代码
private String essentialPart = ""; // 用于分析数据的部分
private String info[]; // 歌曲信息
private String lrc; // LRC歌词(带时间标签,可能无歌词,需要去MainFrame判断)
private String lrcUrl; // LRC歌词地址
private boolean hasMV = false;
private String mvUrl = "NULL"; // MV地址(若无则返回“NULL”)
/**
* 获取URL框架中有用的信息
* @param myURL 要分析的URL
* @throws IOException
*/
public HTTPAnalyzer(String myURL) throws IOException
{
MainFrame.logEvent("尝试解析网页框架地址");
if(myURL.toLowerCase().startsWith("music.163"))
this.myURL = "http://" + myURL;
else
this.myURL = myURL;
this.URL_id = myURL.substring(myURL.lastIndexOf("song?id=") + 8);
this.frameURL = "http://music.163.com/song?id=" + this.URL_id;
this.bodyCode = Jsoup.connect(this.frameURL).timeout(3000).execute().body();
this.essentialPart = bodyCode.substring(
bodyCode.indexOf("<script type=\"application/ld+json\">"),
bodyCode.indexOf("<script type=\"text/javascript\">"));
this.lrcUrl = "http://music.163.com/api/song/media?id=" + URL_id;
int[] pos = new int[7];
pos[0] = this.essentialPart.indexOf("\"@context\": \"");
pos[1] = this.essentialPart.indexOf("\"appid\": \"");
pos[2] = this.essentialPart.indexOf("\"title\": \"");
pos[3] = this.essentialPart.indexOf("\"images\": [\"");
pos[4] = this.essentialPart.indexOf("\"description\": \"歌手:");
pos[5] = this.essentialPart.indexOf("。所属专辑:");
pos[6] = this.essentialPart.indexOf("\"pubDate\": \"");
String[] substrings = new String[7];
substrings[0] = this.essentialPart.substring(pos[0]);
substrings[1] = this.essentialPart.substring(pos[1]);
substrings[2] = this.essentialPart.substring(pos[2]);
substrings[3] = this.essentialPart.substring(pos[3]);
substrings[4] = this.essentialPart.substring(pos[4]);
substrings[5] = this.essentialPart.substring(pos[5]);
substrings[6] = this.essentialPart.substring(pos[6]);
/*
* info[0]: context <!-- @URL -->
* info[1]: appid
* info[2]: title
* info[3]: images <!-- @URL -->
* info[4]: artist
* info[5]: album
* info[6]: pubDate
*/
MainFrame.logEvent("尝试返回解析内容到本地");
this.info = new String[7];
this.info[0] = substrings[0].substring(13, substrings[0].indexOf("\","));
this.info[1] = substrings[1].substring(10, substrings[1].indexOf("\","));
this.info[2] = substrings[2].substring(10, substrings[2].indexOf("\","));
this.info[3] = substrings[3].substring(12, substrings[3].indexOf("\"],"));
this.info[4] = substrings[4].substring(19, substrings[4].indexOf("。"));
this.info[5] = substrings[5].substring(6, substrings[5].indexOf("。\","));
this.info[6] = substrings[6].substring(12, substrings[6].length() - 14);
int mvStart = bodyCode.indexOf("<a title=\"播放mv\" href=\"/mv?id=");
if(mvStart != -1)
{
hasMV = true;
String mvEssential = bodyCode.substring(mvStart,
bodyCode.indexOf("\"><i class=\"icn u-icn u-icn-2\"></i>", mvStart));
this.mvUrl = "http://music.163.com/mv?id=" + mvEssential.substring(29);
String downloadMvEssential = Jsoup.connect(this.mvUrl).timeout(100000).execute().body();
String actualMvUrl = downloadMvEssential.substring(
downloadMvEssential.indexOf("http%3A%2F%2Fv4.music.126.net"),
downloadMvEssential.indexOf(".mp4\""));
MainFrame.actualDownloadMVURL = actualMvUrl.replace("%3A", ":").replace("%2F", "/").replace("%3D", "=") + ".mp4";
}
else
{
hasMV = false;
this.mvUrl = "NULL";
MainFrame.actualDownloadMVURL = "NULL";
}
MainFrame.logEvent("尝试获取歌词主体");
this.lrc = Jsoup.connect(this.lrcUrl).timeout(3000).execute().body();
}
public String getEssentialPart() { return this.essentialPart; }
public String getMyURL() { return myURL; }
public String getURL_id() { return URL_id; }
public String getFrameURL() { return frameURL; }
public String getBodyCode() { return bodyCode; }
public String[] getInfoArray() { return info; }
public String getLrc() { return lrc; }
public String getLrcUrl() { return lrcUrl; }
public boolean hasMusicVideo() { return hasMV; }
public String getMvUrl() { return mvUrl; }
@Override
public String toString()
{
return "[Analytic result]\r\nContext: " + info[0] + "\r\n"
+ "AppID: " + info[1] + "\r\n"
+ "Title: " + info[2] + "\r\n"
+ "Album_img: " + info[3] + "\r\n"
+ "Artist: " + info[4] + "\r\n"
+ "Album: " + info[5] + "\r\n"
+ "PublishedDate:" + info[6] + "\r\n"
+ "HasMV: " + hasMV + "\r\n"
+ "MVURL: " + mvUrl;
}
}
这是整个软件很小的一部分,但却是最重要的一部分(MainFrame.logEvent()只是我用于记录软件活动的方法)。软件需要继续维护和升级,还要加入很多新的功能(比如暂时设想的:获取高清或其他分辨率的MV地址,生成歌手信息,获取同一张专辑中其他歌曲等),其余代码就不公开了。
注:带有版权问题的歌曲是不能下载的,毕竟又不是百度网盘(哦呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵呵)
在进阶的路上,欢迎各位大侠指正。