前言

项目地址:Github

前一段时间将我写的歌词编辑器 LRCEditor 完善了一下,添加了一个搜索歌词的功能,但是仅限于网易云(因为网易云音乐的API是最好搞的),有一些网易云上面没有版权的歌曲自然就搜不到歌词,非常影响体验

于是就想到将另外两家音乐软件:QQ音乐和酷狗音乐的 API 也搞过来,添加到搜索功能里面去,这样基本上就不用担心搜不到歌词了:)

然后就花了整整一天时间研究他们的 API……

这里将 QQ 音乐和酷狗音乐的 API 接口使用整理一下,看到这篇文章并且有需要的小伙伴就可以直接拿去用了:)

P.S.1 整理的时间是 2020 年 2 月,有些接口过了一段时间可能就被更换了,所以看完还请自己先验证一波……

P.S.2 本文中接口网址返回的数据均为 JSON 字符串

P.S.3 有关网易云接口的整理在另一篇博文里,链接:VB.NET | 在线获取歌词方法的小小总结

1. QQ 音乐部分

搜索歌曲

网址格式:https://c.y.qq.com/soso/fcgi-bin/client_search_cp?aggr=1&cr=1&flag_qc=0&p={0}&n=20&w={1}

参数说明:

  1. p={0} 表示返回搜索结果中的第几页,和 n=20 一起用
  2. n=20 每页返回的结果数量
  3. w={1} 要搜索的关键字

调用例子:https://c.y.qq.com/soso/fcgi-bin/client_search_cp?aggr=1&cr=1&flag_qc=0&p=1&n=20&w=Faded

这一步可以获取歌曲的一些基本信息(名字,艺术家,专辑,时长等),同时需要在返回的信息里面获取歌曲的 mid,接下来获取歌词,封面图片,播放的文件都要用到

获取歌词

这一个接口网址好像没有人介绍过???就是直接打开QQ音乐(网页版)的播放界面,然后F12,在Network 里面筛选XHR格式且含有 Lyric 关键词的文件,就会发现有一个文件是指向这个网址的

网址格式:https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg?-=MusicJsonCallback_lrc&songmid={0}&g_tk=758816886&format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq.json&needNewCode=0

参数说明:

  1. songmid={0} 表示上一步中获取到的歌曲 mid

调用例子:https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg?-=MusicJsonCallback_lrc&songmid=004QK3K60anL4V&g_tk=758816886&format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq.json&needNewCode=0

然后,你会发现返回的是:

1
{"retcode":-1310,"code":-1310,"subcode":-1310}

别慌,这是 QQ 音乐防止爬取数据而设置的障碍,我们只需要在 Request 里面设置两个东西:

  1. Referer = https://y.qq.com/portal/player.html
  2. skey = ......

第二个 skey 每次访问都是会变化的,但是服务器不会检测它的正确性,所以直接拿浏览器访问一次之后把 skey 放到代码中即可

然后就可以正常访问了,用 C# 实现的代码如下:

1
2
3
4
5
6
7
HttpWebRequest req = (HttpWebRequest)WebRequest.Create("https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg?-=MusicJsonCallback_lrc&songmid={0}&g_tk=758816886&format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq.json&needNewCode=0");
req.Headers.Add("Cookie", "skey=@Y3TD47qBo");
req.Referer = "https://y.qq.com/portal/player.html";
// 注意这里不能 req.Headers.Set("Referer","https://y.qq.com/portal/player.html");
Stream stream = req.GetResponse().GetResponseStream();
StreamReader sr = new StreamReader(Encoding.UTF8, stream);
string lyric = sr.ReadToEnd();

然而,你会发现,返回的字符串是这个样子的:

1
"W3RpOkxBU1QgU1RBUkRVU1QgKOOAikZhdGUvc3RheSBuaWdodCAoVW5saW1pdGVkIEJsYWRlIFdvcmtzKeOAi1RW5Yqo55S75o+S5puyKV0KW2FyOkFpbWVyICjjgqjjg6EpXQpbYWw6REFXTiAo6buO5piOKV0KW2J5Ol0KW29mZnNldDowXQpba2FuYToxMTExMeOBlzHjgY3jgofjgY8x44Go44GzMeOBquOBhDHjgb7jgZUx44Gy44KNMeOBpOOCiDHjgYLjgoEx44GI44GMMeOChjHjgoEx44GvMeOBteOCizHjgYvjgZ8x44GsMeOBguOCizHjgaTjgaUx44GZMeOBjTHjgaHjgYQx44GmMeOBmeOBjTHjgb4x44GGMeOBhOOCjTHjgY0x44GNMeOBiuOBjzHjgbLjgo0x44GC44GkMeOBhOOBqDHjg .....(后面很多很多就不放出来了)"

也不要慌,这个一长串明显是 Base64 加密处理过的,我们只需要一下就能把它还原成本来的样子:

1
string new_lyric = Encoding.UTF8.GetString(Convert.FromBase64String(lyric));

获取专辑的封面

网址格式:http://imgcache.qq.com/music/photo/album_500/17/500_albumpic_{0}_0.jpg

参数说明:

  1. 500_albumpic_{0}_0.jpg{0} 处填上第一步获取的专辑 mid 值(不是歌曲 mid

调用例子:http://imgcache.qq.com/music/photo/album_500/17/500_albumpic_8217_0.jpg

这个网址打开了是 500×500 的封面图,如果想要自定义大小,把地址中两个 500 都改成对应的大小即可

获取可以播放的文件

这一步比较复杂,如果不是控制台程序建议弄成异步

获取文件,要先获取它的 Token(就是一种类似于通行证的东西):

网址#1 格式:https://c.y.qq.com/base/fcgi-bin/fcg_music_express_mobile3.fcg?format=json205361747&platform=yqq&cid=205361747&songmid={0}&filename={1}&guid=126548448

参数说明:

  1. songmid={0} 歌曲的 mid

  2. filename={1} 由歌曲的 mid 值拼接而来,filename = 'C400' + mid + '.m4a'

    例如 mid004QK3K60anL4V,文件名就是 C400004QK3K60anL4V.m4a

调用例子:https://c.y.qq.com/base/fcgi-bin/fcg_music_express_mobile3.fcg?format=json205361747&platform=yqq&cid=205361747&songmid=004QK3K60anL4V&filename=C400004QK3K60anL4V.m4a&guid=126548448

另外的参数例如 cidguid 都是可以写死的,所以就不用管了

在这一步的返回值里面可以看到 {"vkey" : "..."} 的字段,vkey 就是用来拼成获取播放文件的网址的东西

网址#2 格式:http://ws.stream.qqmusic.qq.com/{0}?fromtag=0&guid=126548448&vkey={1}

参数说明:

  1. {0} 上一步提到的 filename,用歌曲 mid 值加工而成
  2. vkey={1} 填刚刚得到的 vkey

调用例子:http://ws.stream.qqmusic.qq.com/C400004QK3K60anL4V.m4a?fromtag=0&guid=126548448&vkey=...(vkey很长就不写出来了)

打开网址就是一个 m4a 文件,大功告成!

2. 酷狗音乐部分

相比起 QQ 音乐,酷狗音乐的 API 接口的破解要简单一些

搜索歌曲

网址格式:http://mobilecdn.kugou.com/api/v3/search/song?format=json&keyword={1}&page={0}&pagesize=20&showtype=1

参数说明:

  1. pagepagesize 页数和每页返回的结果数量
  2. keyword 要搜索的关键字

调用例子:http://mobilecdn.kugou.com/api/v3/search/song?format=json&keyword=Faded&page=1&pagesize=20&showtype=1

这一步可以获取歌曲的一些基本信息(名字,艺术家,专辑,时长等),同时需要记录歌曲的 hash 值,接下来会用到

获取专辑封面,歌词,和播放文件

网址格式:https://www.kugou.com/yy/index.php?r=play/getdata&hash={0}

参数说明:

  1. hash={0} 上一步得到的歌曲 hash

调用例子:https://www.kugou.com/yy/index.php?r=play/getdata&hash=F139D898F1ED75CAD7B582BD1BAC0093

然后,你可能会发现,返回的是:

1
{"status":0,"err_code":20010,"data":[]}

如果不是这个结果,那么将浏览器的 Cookie 清除掉,刷新一下,就会是这个返回值

然而,如果用代码来直接访问的话,结果就一定会变成这样

但我们还是不慌,现在先输入酷狗官网的网址 www.kugou.com,随便点进去听一首歌,再把这个网址输进去,你就会发现,返回值变得正常了

再看这个网址,他有 www.kugou.com 的前缀,也就是说,访问这个网址时,和访问官网时用的是同一批数据,问题肯定就出在这批数据上了,经过反复尝试,我发现是 Cookie 中的 一个 kg_mid 字段决定了返回值正常与否

关于这个字段,网上有专门计算的方法和程序:酷狗音乐网站前端JS的逆向破解过程,但是实际上我们并不需要每次访问时都计算一遍,就像刚才的 skey,这个 kg_mid 也是属于有就行的那一类,服务器并不会检查它的正确性,所以还是浏览器访问一次,然后复制粘贴到代码当中即可:

1
2
3
4
5
6
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(new Uri(LInterface.KuGouMusic_Detail(QMid)));
CookieContainer cookie = new CookieContainer();
cookie.Add(new Cookie("kg_mid", "c8c679b1e10cd05ed4d0e55dc2da4b0a", "/", ".kugou.com"));
// 这里的 new 方法必须写四个参数,否则运行错误
req.CookieContainer = cookie;
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();

专辑封面,播放文件的网址和歌词都在返回值里面了,看英文就可以判断出来,于是酷狗的 API 接口破解成功!

但是,我们注意到酷狗 API 中返回的歌词是没有翻译的!!!不是中文歌搜没有翻译的歌词还有什么用,本着一条龙服务的原则,我决定再做一个 API 用来翻译……

3. Google翻译 API

这部分的大多数内容转载自:C#实现谷歌翻译API,不同的地方在于这篇文章用的是 www.translate.google.com 这个网址,现在已经访问不到了,不过我们还有 www.translate.google.cn 这个国内版的翻译网址,获取 API 的方式也基本一致

获取 TKK

打开 www.translate.google.cn ,随便输入点英文,然后 F12 打开看 Network,发现只有这个网址指向了翻译的结果:https://translate.google.cn/translate_a/single?client=webapp&sl=en&tl=zh-CN&hl=zh-CN&dt=at&dt=bd&dt=ex&dt=ld&dt=md&dt=qca&dt=rw&dt=rm&dt=ss&dt=t&source=bh&ssel=0&tsel=0&kc=1&tk=262549.177587&q=...

分析:注意到整个网址中只有 q=... 是我们输入的内容,sltl 分别是源语言和目标语言(sl 填 auto 就相当于自动检测语言了),其它的字段都是自动填好的

然后注意到一个重要的字段 tk=262549.177587,注意到每次打开这个页面后,附带的 tk 都是不一样的,而且我们看不出什么规律

tk 嘛,就相当于 ticket ,票的意思,应该就是 Google 防止随意调用翻译 API 设置的障碍了

这个 tk 其实和两个东西有关,一个是我们输入的内容,另一个是隐藏在页面 HTML 文档中的 TKK 值,可认为,两者都是浮点数类型,那么有可能 tk 就是由 TKK 计算得到的

计算 tk

监视发现,网站中有一个 JS 调用了这个 TKK 并通过某种算法生成了 tk ,网上已经有人破解了这段算法的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var b = function (a, b) {
for (var d = 0; d < b.length - 2; d += 3) {
var c = b.charAt(d + 2),
c = "a" <= c ? c.charCodeAt(0) - 87 : Number(c),
c = "+" == b.charAt(d + 1) ? a >>> c : a << c;
a = "+" == b.charAt(d) ? a + c & 4294967295 : a ^ c
}
return a
}

var tk = function (a,TKK) {
for (var e = TKK.split("."), h = Number(e[0]) || 0, g = [], d = 0, f = 0; f < a.length; f++) {
var c = a.charCodeAt(f);
128 > c ? g[d++] = c : (2048 > c ? g[d++] = c >> 6 | 192 : (55296 == (c & 64512) && f + 1 < a.length && 56320 == (a.charCodeAt(f + 1) & 64512) ? (c = 65536 + ((c & 1023) << 10) + (a.charCodeAt(++f) & 1023), g[d++] = c >> 18 | 240, g[d++] = c >> 12 & 63 | 128) : g[d++] = c >> 12 | 224, g[d++] = c >> 6 & 63 | 128), g[d++] = c & 63 | 128)
}
a = h;
for (d = 0; d < g.length; d++) a += g[d], a = b(a, "+-a^+6");
a = b(a, "+-3^+b+-f");
a ^= Number(e[1]) || 0;
0 > a && (a = (a & 2147483647) + 2147483648);
a %= 1E6;
return a.toString() + "." + (a ^ h)
}

代码中函数 tk(a,TKK) 的参数 a 是我们输入的内容,TKK 是刚才得到的值,直接调用 JS 代码就可以获得 tk

由于 ScriptControl ,C# 里面是可以直接调用 JS 代码的:

1
2
3
4
MSScriptControl.ScriptControl scriptControl = new MSScriptControl.ScriptControl();
scriptControl.Language = "JScript";
scriptControl.AddCode(JSCode);// 刚刚的代码
string tk = scriptControl.Eval("..."); // ...表示tk(a,TKK),a 和 TKK 要替换成得到的值

然后就可以正常的返回翻译结果了,注意重复调用时不用每次都先访问 www.translate.google.cn 获取 TKK,只用获取一次,然后动态根据输入的内容生成 tk 即可

歌词API代码:Ubuntu Pastebin
翻译API代码:Ubuntu Pastebin(感谢C#实现谷歌翻译API