使用golang研究网易云音乐API

本文将展示笔者经过肤浅的golang学习后,使用golang对网易云音乐api进行分析的经历,最终实现一个基本的api,可以做到代为向网易云音乐的api请求数据。笔者使用的是martini web框架,并参考了网上现有的各种网易云音乐api的python实现,在此一并谢过。
目前实现的api只有以下几个:

  1. 歌曲搜索
  2. 歌曲详情
  3. 含实际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)
}

传入的参数即 关键词,搜索类型,数量,偏移量 四个。最终效果像这样:

请求参数
使用golang研究网易云音乐API_第1张图片
请求结果

二、歌曲详情

歌曲详情接口比搜索接口要简单很多,因为是更开放的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)
}

请求结果像这样:

使用golang研究网易云音乐API_第2张图片
请求结果

相比搜索接口的结果,详情接口能拿到具体的封面信息这应该是最有用的,因为搜索得到的结果中图片都是假的,可惜的是详情结果中的 mp3Url 字段是空的,说明想要真正听到歌曲还得使用别的接口才行。

三、歌曲下载

歌曲下载接口应该是变动最多的接口了,经测试很多网上现有的方式都没有用,最靠谱的应该是使用其官方的请求方式,接口为 http://music.163.com/weapi/song/enhance/player/url?csrf_token= ,参数为两个很长的加密的字符串,要做的斗争就是如何得到这两个字符串。

使用golang研究网易云音乐API_第3张图片
官网中的请求

参考网上现有的前辈大佬的分析,个人描述的加密过程如下:

  1. 将请求参数使用一个固定的标识字符串进行 AES加密并进行base64编码
  2. abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/ 中随机取出16次字符组成一个秘钥
  3. 将1得到的结果使用2得到的秘钥串再进行一次 AES加密并base64编码,至此得到第一个参数 params
  4. 将2中的秘钥倒序后转ascii码,然后转16进制字符串得到一个中间字符串
  5. 将4得到的中间字符串转为十进制大整数1,将另两个固定的16进制字符串也都转为十进制大整数2、3
  6. 5中得到了三个大整数,现在执行 pow(大整数1, 大整数2, 大整数3) ,即1的2次幂取模3,得到新的大整数4
  7. 将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研究网易云音乐API_第4张图片
请求结果

收尾总结

第三个接口中对笔者来说磨人的两点在于:

  1. 对golang经验不够导致数据类型转来转去花了不少力气,以及还涉及到了大数运算,数据的格式化真的是虐惨了
  2. 上文提到的 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/ 这个字符串中, 最后的 +/ 很关键不能少
  • 笔者的小项目托管在 github ,还是个很小的小项目,没来得及养肥,关于上文三个接口的细节实现好歹是有了。
  • 笔者的 小api地址 在上面的图中已经出现过了,也还没来得及养肥 :)
  • 关于作为示例的歌曲名为 《再见二丁目》,讲的是林夕老爷在东京被明哥放了鸽子很彷徨 :)

你可能感兴趣的:(使用golang研究网易云音乐API)