VB.NET | 在线获取歌词方法的小小 总结
前言
上次的项目 LRCEditor
告一段落之后,最近又想到对其进行优化,其实也就是添加一个常用的功能:在线搜索歌词
在 LRCEditor
中的 “帮助” 选项里面有提到如何在网易云音乐的在线界面利用开发者模式获取歌词文件和翻译,但是此方法操作起来会有一定难度,不利于将来准备做的 MusicPlayer 的功能集成,因此想到通过程序自己访问网络获取歌词并显示
在我们平常浏览的播放器网页里面,通常都可以看到歌词,歌曲名称,作者信息和封面信息等等,然而要让程序从网页中像人一样提取出这些信息无疑是非常难的,如何让这些信息以程序能够理解或者解析的格式直接呈现呢,这就要依网页API来实现了
实现(获取歌词)
Step1
使用网易云提供的 API,根据给定的歌曲名称(设为 Faded)搜索相应信息:http://music.163.com/api/search/get?s=Faded&limit=20&type=1&offset=0
,必需参数说明:
http://music.163.com/api/search/get
API接口网址s
:歌曲名称,中文要进行字符编码limit
:返回结果的数量type
:搜索类型,1为单曲,10为专辑,100为歌手,1000歌单,1002用户offset
:偏移量,用于结果分页返回
在浏览器地址栏输入这个网址后,可以看到返回了一坨信息,大多都是如下格式的 JSON 代码:
1 | {"id":36990266,"name":"Faded","artists":[{"id":1045123,"name":"Alan Walker","picUrl":null,"alias":[],"albumSize":0,"picId":0,"img1v1Url":"http://p2.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg","img1v1":0,"trans":null},{"id":1078390,"name":"Iselin Solheim","picUrl":null,"alias":[],"albumSize":0,"picId":0,"img1v1Url":"http://p2.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg","img1v1":0,"trans":null}],"album":{"id":3406843,"name":"Faded","artist":{"id":0,"name":"","picUrl":null,"alias":[],"albumSize":0,"picId":0,"img1v1Url":"http://p2.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg","img1v1":0,"trans":null},"publishTime":1448380800007,"size":4,"copyrightId":7001,"status":3,"picId":18277181788626198,"mark":0},"duration":212626,"copyrightId":7001,"status":0,"alias":[],"rtype":0,"ftype":0,"mvid":524116,"fee":8,"rUrl":null,"mark":8192} |
其中,第一个 "id"
就是该歌曲在网易云音乐的编号,过后的信息如英文所示,"name"
表示歌曲名,"artists"
中列出了参与创作的艺术家名称(因为可能不止一个,所以是以数组的形式返回的),"duration"
表示歌曲持续的时间(单位为毫秒),这一步的作用只是通过API搜索出歌曲ID,下一步将再次通过API得到ID对应歌曲的歌词信息
Step2
使用此网址得到歌曲的歌词信息:http://music.163.com/api/song/lyric?os=pc&id=36990266&lv=-1&kv=-1&tv=-1
地址中除了 id=
后面接上上一步得到的 ID 外,其他部分不用管,然后可以看到以下 JSON 信息:
1 | {"sgc":false,"sfy":false,"qfy":false,"transUser":{"id":36990266,"status":99,"demand":1,"userid":1569973972,"nickname":"……ye","uptime":1548668567698},"lyricUser":{"id":36990266,"status":99,"demand":0,"userid":1569973972,"nickname":"……ye","uptime":1547805089718},"lrc":{"version":16,"lyric":"……"},"klyric":{"version":0,"lyric":null},"tlyric":{"version":10,"lyric":"……"},"code":200} |
详细的歌词部分用 “……” 代替了,返回信息中 lrc
是原版歌词,若歌曲非中文且网易云有翻译版,则 tlyric
为最新一的翻译版歌词,nickname
和 userid
是翻译贡献者的用户名和用户ID
到这一步,我们就已经取得了关于歌词的较为完整的信息,但是目前为止,上述步骤都是都是在浏览器中完成的,要让程序做到够像在浏览器地址栏中输入网址然后获取返回信息的话,需要额外的代码
并且,返回字符串也并不是原原本本的歌词,而是以 JSON 格式编码的返回信息,为了处理 JSON 格式的返回信息,需要用到 Newtonsoft.JSON
Step3
以下代码用于从给定网址读取信息,以 String
格式返回,原理不多做解释了
1 | Imports System.Security.Cryptography.X509Certificates |
调用例子:
1 | Dim ResPUrl As String = "http://music.163.com/api/search/get?&limit=20&type=1&offset=0&s=" + Given 'Given 给定歌曲的名称 |
此时,WebRet
存储的就是 Step1 里面看到的一大坨东西了,接下来使用 Newtonsoft.JSON
框架里的函数解析这个字符串,获得我们想要的歌曲信息
Step4
先从 NuGet 导入 Newtonsoft
的包,然后 Imports Newtonsoft.JSON
这一步使用 JsonConvert.DeserializeObject
对字符串进行反序列化并存储对应标签的键值
先看一下函数声明:JsonConvert.DeserializeObject(Of T As Type)(Value As String) As Object
,是一个很奇怪的函数,可以把它和 C++ 里面的 Template<Typename T>
类比,即前面一个括号里面写的 (Of T As Type)
限定此函数返回的类型,后面一个括号里面是我们要进行反序列化操作的 JSON 字符串
JsonConvert.DeserializeObject
函数本身返回的是 Object
类型,这是 VB.NET
里面的最基本类型,可以派生出任意的类型,拥有他们的结构,但是 Object
本身不具有任何特定的成员变量或者函数,需要将特定的结构体传入到 Object
才能让它像其他类或者结构体一样使用
JsonConvert.DeserializeObject
函数返回的是一个特定的类或者结构体,其组成和要操作的 JSON 字符串一致,即拥有相同的结构和变量名,例如以下 JSON 字符串:
1 | { |
从此字符串返回的特定结构体或类应具有如下组成形式,才能让函数正确返回:
1 | Public Structure JRES |
需要说明的是,特定结构体不一定要有全部 JSON 里面的变量名称,但是不能多出 JSON 里没有的变量名称,例如删去成员 name
,函数仍然正确返回,但是若加上成员 age
就会出现错误(不管是什么类型),对于 JSON 返回值中带方括号声明的数组类型,需要用同名且拥有一致的结构的集合类型进行接收,如 List 或 数组
现在,分析一下 Step1 中JSON返回值的结构:
整个结构非常的繁琐而且层次很深,因此我们只提取想要的几个信息,可以用如下的特定结构体:
1 | Public Structure JRES |
然后:
1 | Public Res As JRES |
现在,结构体 Res
里面存储的就是我们希望得到的歌词信息了,如果用户还限制了艺术家名称,我们可以将不符合的项从 List 当中去掉,然后选取一个控件呈现得到的信息吧!
实现(获取专辑封面)
Step 1
同样是网易云的 API 网址:http://music.163.com/api/song/detail/?id={歌曲id}&ids=[{歌曲id}]
获取到如下返回值:
1 | {"songs":[{"name":"Umbrella (Matte Remix)","id":518904426,"position":1,"alias":[],"status":0,"fee":0,"copyrightId":0,"disc":"01","no":1,"artists":[{"name":"Matte","id":12335355,"picId":0,"img1v1Id":0,"briefDesc":"","picUrl":"http://p1.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg","img1v1Url":"http://p1.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg","albumSize":0,"alias":[],"trans":"","musicSize":0,"topicPerson":0},{"name":"Ember Island","id":1100001,"picId":0,"img1v1Id":0,"briefDesc":"","picUrl":"http://p1.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg","img1v1Url":"http://p1.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg","albumSize":0,"alias":[],"trans":"","musicSize":0,"topicPerson":0}],"album":{"name":"Umbrella (Matte Remix)","id":36812124,"type":"EP/Single","size":1,"picId":109951163063843501,"blurPicUrl":"http://p1.music.126.net/1LrtvH8EpKb5iHKR9qEU0Q==/109951163063843501.jpg","companyId":0,"pic":109951163063843501,"picUrl":"http://p1.music.126.net/1LrtvH8EpKb5iHKR9qEU0Q==/109951163063843501.jpg","publishTime":1510502400007,"description":"","tags":"","company":"Self-Release","briefDesc":"","artist":{"name":"","id":0,"picId":0,"img1v1Id":0,"briefDesc":"","picUrl":"http://p1.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg","img1v1Url":"http://p1.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg","albumSize":0,"alias":[],"trans":"","musicSize":0,"topicPerson":0},"songs":[],"alias":[],"status":0,"copyrightId":0,"commentThreadId":"R_AL_3_36812124","artists":[{"name":"Matte","id":12335355,"picId":0,"img1v1Id":0,"briefDesc":"","picUrl":"http://p1.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg","img1v1Url":"http://p1.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg","albumSize":0,"alias":[],"trans":"","musicSize":0,"topicPerson":0}],"subType":"混音版","transName":null,"mark":0,"picId_str":"109951163063843501"},"starred":false,"popularity":100.0,"score":100,"starredNum":0,"duration":285701,"playedNum":0,"dayPlays":0,"hearTime":0,"ringtone":null,"crbt":null,"audition":null,"copyFrom":"","commentThreadId":"R_SO_4_518904426","rtUrl":null,"ftype":0,"rtUrls":[],"copyright":0,"transName":null,"sign":null,"mark":0,"hMusic":{"name":null,"id":1387911675,"size":11430182,"extension":"mp3","sr":44100,"dfsId":0,"bitrate":320000,"playTime":285701,"volumeDelta":-19400.0},"mMusic":{"name":null,"id":1387911676,"size":6858127,"extension":"mp3","sr":44100,"dfsId":0,"bitrate":192000,"playTime":285701,"volumeDelta":-16600.0},"lMusic":{"name":null,"id":1387911677,"size":4572099,"extension":"mp3","sr":44100,"dfsId":0,"bitrate":128000,"playTime":285701,"volumeDelta":-14700.0},"bMusic":{"name":null,"id":1387911677,"size":4572099,"extension":"mp3","sr":44100,"dfsId":0,"bitrate":128000,"playTime":285701,"volumeDelta":-14700.0},"mvid":0,"rtype":0,"rurl":null,"mp3Url":null}],"equalizers":{},"code":200} |
这个 JSON 返回值的结构和上面提到的获取歌词时候的返回值结构相似,但是没有最外层的 result
,所以还是需要再写一个结构体接收,如果用上述获取歌词的结构体接收,其 songs
会为 nothing
成功接收数据后,songs(0).album.picurl
就是专辑封面图片的网址
Step 2
得到网址后,注意到这次的网址返回的不是纯字符串而是文件,所以不能再用像获取歌词的 GetResponse
的方法得到图片了,此时我们可以用 .NET 自带的 HTTP 下载器 WebClint
完成操作
WebClint
的下载操作都是默认异步执行的,不会造成主窗体死锁的现象,但是 WebClint
不在控件箱里,不能可视化对其触发事件进行函数绑定,因此我们用 AddHandler
手动绑定几个函数,首先,请求下载的操作为:
1 | Dim wc As WebClint |
其中 TargetFileName
是下载文件保存到本地时的文件名,然后我们给两个触发事件绑定函数
1 | AddHandler wc.DownloadProgressChanged, AddressOf ShowDownProcess |
其中 DownloadProcessChanged
在下载进度改变时触发,DownloadFileCompleted
在文件下载完成后触发,在对应的函数 ShowDownProcess
里可以使用 ProcessBar
控件直观显示下载进度,下载完成后 Process.Start
启动文件
1 | Public Sub ShowDownProcess(ByVal sender As Object, ByVal e As System.Net.DownloadProgressChangedEventArgs) |
这一步可以单独用一个窗体来解决,使得程序更直观
实现(获取mp3文件)
Step1
API网址为:http://music.163.com/song/media/outer/url?id={歌曲id}
打开这个网址会得到一个可以直接下载的 mp3 文件,然后直接使用上面提到的 WebClint
下载即可
附:窗体代码(下载)
1 | Option Explicit On |