使用golang调用网易云音乐api
发布于: 2017-11-06 21:07
此文章久未修订,请自行甄别内容准确性。
2018-01-26 迁移更新
现已将所有已实现api都使用新版加密请求方式,部分调用url及参数参考自 NeteaseCloudMusicApi 。
本文将展示笔者经过肤浅的golang学习后,使用golang对网易云音乐api进行分析的经历,最终实现一个基本的api,可以做到代为向网易云音乐的api请求数据。笔者使用的是martini web框架,并参考了网上现有的各种网易云音乐api的python实现,在此一并谢过。 目前实现的api只有以下几个:
- 歌曲搜索
- 歌曲详情
- 含实际mp3地址的下载信息
其中第三个下载能力是最磨人的,不过毕竟是可以直接获取下载链接的接口,还算值得。
一、搜索能力
这里使用的是 http://music.163.com/api/search/get/
这个api地址,发送的是POST请求,需要注意的点就是必须带上的几个头部以及参数的格式,实现难度相对中等。在golang下的请求核心代码如下:
/**
* 执行搜索
* params: 关键词 类型 页码 数量
* return: 字符串形式的请求结果
*/
func Search(words string, stype string, page int, limit int) string {
// 创建客户端
client := &http.Client{}
// 格式化参数
_o, _l := formatParams(page, limit)
// 设置body
form := url.Values{}
form.Set("s", words)
form.Set("type", stype)
form.Set("limit", _l)
form.Set("offset", _o)
body := strings.NewReader(form.Encode())
// 创建请求
request, _ := http.NewRequest("POST", "http://music.163.com/api/search/get/", body)
//设置头部
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
request.Header.Set("Cookie", "appver=2.0.2")
request.Header.Set("Referer", "http://music.163.com")
request.Header.Set("Content-Length", (string)(body.Len()))
// 发起请求
response, reqErr := client.Do(request)
// 错误处理
if reqErr!= nil {
fmt.Println("Fatal error ", reqErr.Error())
return `{"data": null, "state": false, "msg": "请求失败"}`
}
defer response.Body.Close()
resBody, _ := ioutil.ReadAll(response.Body)
return string(resBody)
}
/**
* 传入 搜索类型 页码 数量
* 返回 搜索类型 偏移 数量
*/
func formatParams(page int, limit int) (string, string) {
if page < 1 {
page = 1
}
if limit < 1 {
limit = 0
}
return strconv.Itoa((page - 1) * limit), strconv.Itoa(limit)
}
传入的参数即 关键词,搜索类型,数量,偏移量
四个。最终效果像这样:
二、歌曲详情
歌曲详情接口比搜索接口要简单很多,因为是更开放的GET请求,不需要加复杂的头部:
func SongInfo(id string) string {
res, err := http.Get("http://music.163.com/api/song/detail/?id=" + id + "&ids=[" + id + "]")
// 错误处理
if err != nil {
fmt.Println("Fatal error ", err)
return `{code: 0}`
}
defer res.Body.Close()
rs, _ := ioutil.ReadAll(res.Body)
return string(rs)
}
请求结果像这样:
相比搜索接口的结果,详情接口能拿到具体的封面信息这应该是最有用的,因为搜索得到的结果中图片都是假的,可惜的是详情结果中的 mp3Url
字段是空的,说明想要真正听到歌曲还得使用别的接口才行。
三、歌曲下载
歌曲下载接口应该是变动最多的接口了,经测试很多网上现有的方式都没有用,最靠谱的应该是使用其官方的请求方式,接口为 http://music.163.com/weapi/song/enhance/player/url?csrf_token=
,参数为两个很长的加密的字符串,要做的斗争就是如何得到这两个字符串。
参考网上现有的前辈大佬的分析,个人描述的加密过程如下:
- 将请求参数使用一个固定的标识字符串进行 AES加密并进行base64编码
- 从
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/
中随机取出16次字符组成一个秘钥 - 将1得到的结果使用2得到的秘钥串再进行一次 AES加密并base64编码,至此得到第一个参数
params
- 将2中的秘钥倒序后转ascii码,然后转16进制字符串得到一个中间字符串
- 将4得到的中间字符串转为十进制大整数1,将另两个固定的16进制字符串也都转为十进制大整数2、3
- 5中得到了三个大整数,现在执行 pow(大整数1, 大整数2, 大整数3) ,即1的2次幂取模3,得到新的大整数4
- 将6得到的大整数4转为16进制字符串,并在左边补满0补满256位,最终得到第二个参数
encSecKey
封装好的函数如下:
func EncParams(param string) (string, string, error) {
// 创建 key
secKey := createSecretKey(16);
// 第一次加密 使用固定的 nonce
if aes1, err1 := aesEncrypt(param, nonce); err1 != nil {
return "", "", err1
} else {
// 第二次加密 使用创建的 key
if aes2, err2 := aesEncrypt(aes1, secKey); err2 != nil {
return "", "", err2
} else {
// 得到 加密好的 param 以及 加密好的key
return aes2, rsaEncrypt(secKey, pubKey, modulus), nil
}
}
}
具体的实现有个小几十行,详见 github 吧。
得到两个参数后,发的是POST请求,顺利得到结果:
收尾总结
第三个接口中对笔者来说磨人的两点在于:
- 对golang经验不够导致数据类型转来转去花了不少力气,以及还涉及到了大数运算,数据的格式化真的是虐惨了
- 上文提到的
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/
这个字符串中, 最后的+/
很关键不能少