webjs - 酷狗音乐搜索下载

    科技2025-09-24  63

    在线接单:网页、小程序、app爬虫、数据挖掘、API接口定制、Django网站开发 

     

    分析问题

    需要下载音乐,首先最重要的是得到音乐的下载url,发现在听歌页面,会发送一个get请求,得到歌曲的下载mp3地址。而这个地址,是需要HASH参数,其他参数测试可以忽略。

    那新的问题就是如何得到一系列我要的歌曲的HASH?

    发现在酷狗搜索页面,搜索某个歌手,就可以在搜索请求中,发现GET请求,会返回搜索歌手的歌曲,其中参数FileHash即上文歌曲hash。

    实现搜索页面结果

    当在表单搜索“许嵩”,会发现页面发起了4个请求。其中

    https://complexsearch.kugou.com/v2/search/song?callback=callback123&keyword=%E8%AE%B8%E5%B5%A9&page=1&pagesize=30&bitrate=0&isfuzzy=0&tag=em&inputtype=0&platform=WebFilter&userid=-1&clientver=2000&iscorrection=1&privilege_filter=0&srcappid=2919&clienttime=1600304102390&mid=1600304102390&uuid=1600304102390&dfid=-&signature=99B1C89A2402FFD00BA141EF34811A87

    请求的输出结果是歌曲的列表信息。

    而链接中我尝试减少各别参数,譬如:callback、platform等看起来不那么重要的参数,均无法再次请求得到数据。

    根据经验:这些必要字段都参与数据加密,而这个加密结果,想必就是signature的值,signature就在其中充当数据校验的作用(如果开发过API就应该清楚)

    接下来就要观察,这个signature是由哪些参数,以哪种形式,以哪种加密方式得到的

    全局搜索signature。

    不属意外,就一个搜索结果。

    o.unshift(p), o.push(p), h.signature = faultylabs.MD5(o.join("")),

    我们可以通过以上代码发现,变量o实际上就是数组,因为o有unshift和push方法。然后再把o以字符串方式拼接。通过 faultylabs.MD5 函数加密后得到signature值。

    那就把o这个值加入watch列表,尝试再次下断点,再次搜索“林俊杰”,观察断点情况。

    接下来从两个方向解决问题:1.得到参数;2.得到加密。

    得到参数: 上文提到,参数即o数组转为字符串。 NVPh5oo715z5DIWAeQlhMDsWXXQV4hwtbitrate=0callback=callback123clienttime=1600305065609clientver=2000dfid=-inputtype=0iscorrection=1isfuzzy=0keyword=林俊杰mid=1600305065609page=1pagesize=30platform=WebFilterprivilege_filter=0srcappid=2919tag=emuserid=-1uuid=1600305065609NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt

    其中发现"NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt"首尾是固定不变的。这也是signature中常见的手法。

    而其他的一些诸如clienttime、uuid、mid他们都应该是13位时间戳。

    得到加密:  

    "undefined" == typeof faultylabs && (faultylabs = {}), faultylabs.MD5 = function(a) { function b(a) { var b = (a >>> 0).toString(16); return "00000000".substr(0, 8 - b.length) + b } function c(a) { for (var b = [], c = 0; c < a.length; c++) b = b.concat(k(a[c])); return b } function d(a) { for (var b = [], c = 0; 8 > c; c++) b.push(255 & a), a >>>= 8; return b } function e(a, b) { return a << b & 4294967295 | a >>> 32 - b } function f(a, b, c) { return a & b | ~a & c } function g(a, b, c) { return c & a | ~c & b } function h(a, b, c) { return a ^ b ^ c } function i(a, b, c) { return b ^ (a | ~c) } function j(a, b) { return a[b + 3] << 24 | a[b + 2] << 16 | a[b + 1] << 8 | a[b] } function k(a) { for (var b = [], c = 0; c < a.length; c++) if (a.charCodeAt(c) <= 127) b.push(a.charCodeAt(c)); else for (var d = encodeURIComponent(a.charAt(c)).substr(1).split("%"), e = 0; e < d.length; e++) b.push(parseInt(d[e], 16)); return b } function l() { for (var a = "", c = 0, d = 0, e = 3; e >= 0; e--) d = arguments[e], c = 255 & d, d >>>= 8, c <<= 8, c |= 255 & d, d >>>= 8, c <<= 8, c |= 255 & d, d >>>= 8, c <<= 8, c |= d, a += b(c); return a } function m(a) { for (var b = new Array(a.length), c = 0; c < a.length; c++) b[c] = a[c]; return b } function n(a, b) { return 4294967295 & a + b } function o() { function a(a, b, c, d) { var f = v; v = u, u = t, t = n(t, e(n(s, n(a, n(b, c))), d)), s = f } var b = p.length; p.push(128); var c = p.length % 64; if (c > 56) { for (var k = 0; 64 - c > k; k++) p.push(0); c = p.length % 64 } for (k = 0; 56 - c > k; k++) p.push(0); p = p.concat(d(8 * b)); var m = 1732584193 , o = 4023233417 , q = 2562383102 , r = 271733878 , s = 0 , t = 0 , u = 0 , v = 0; for (k = 0; k < p.length / 64; k++) { s = m, t = o, u = q, v = r; var w = 64 * k; a(f(t, u, v), 3614090360, j(p, w), 7), a(f(t, u, v), 3905402710, j(p, w + 4), 12), a(f(t, u, v), 606105819, j(p, w + 8), 17), a(f(t, u, v), 3250441966, j(p, w + 12), 22), a(f(t, u, v), 4118548399, j(p, w + 16), 7), a(f(t, u, v), 1200080426, j(p, w + 20), 12), a(f(t, u, v), 2821735955, j(p, w + 24), 17), a(f(t, u, v), 4249261313, j(p, w + 28), 22), a(f(t, u, v), 1770035416, j(p, w + 32), 7), a(f(t, u, v), 2336552879, j(p, w + 36), 12), a(f(t, u, v), 4294925233, j(p, w + 40), 17), a(f(t, u, v), 2304563134, j(p, w + 44), 22), a(f(t, u, v), 1804603682, j(p, w + 48), 7), a(f(t, u, v), 4254626195, j(p, w + 52), 12), a(f(t, u, v), 2792965006, j(p, w + 56), 17), a(f(t, u, v), 1236535329, j(p, w + 60), 22), a(g(t, u, v), 4129170786, j(p, w + 4), 5), a(g(t, u, v), 3225465664, j(p, w + 24), 9), a(g(t, u, v), 643717713, j(p, w + 44), 14), a(g(t, u, v), 3921069994, j(p, w), 20), a(g(t, u, v), 3593408605, j(p, w + 20), 5), a(g(t, u, v), 38016083, j(p, w + 40), 9), a(g(t, u, v), 3634488961, j(p, w + 60), 14), a(g(t, u, v), 3889429448, j(p, w + 16), 20), a(g(t, u, v), 568446438, j(p, w + 36), 5), a(g(t, u, v), 3275163606, j(p, w + 56), 9), a(g(t, u, v), 4107603335, j(p, w + 12), 14), a(g(t, u, v), 1163531501, j(p, w + 32), 20), a(g(t, u, v), 2850285829, j(p, w + 52), 5), a(g(t, u, v), 4243563512, j(p, w + 8), 9), a(g(t, u, v), 1735328473, j(p, w + 28), 14), a(g(t, u, v), 2368359562, j(p, w + 48), 20), a(h(t, u, v), 4294588738, j(p, w + 20), 4), a(h(t, u, v), 2272392833, j(p, w + 32), 11), a(h(t, u, v), 1839030562, j(p, w + 44), 16), a(h(t, u, v), 4259657740, j(p, w + 56), 23), a(h(t, u, v), 2763975236, j(p, w + 4), 4), a(h(t, u, v), 1272893353, j(p, w + 16), 11), a(h(t, u, v), 4139469664, j(p, w + 28), 16), a(h(t, u, v), 3200236656, j(p, w + 40), 23), a(h(t, u, v), 681279174, j(p, w + 52), 4), a(h(t, u, v), 3936430074, j(p, w), 11), a(h(t, u, v), 3572445317, j(p, w + 12), 16), a(h(t, u, v), 76029189, j(p, w + 24), 23), a(h(t, u, v), 3654602809, j(p, w + 36), 4), a(h(t, u, v), 3873151461, j(p, w + 48), 11), a(h(t, u, v), 530742520, j(p, w + 60), 16), a(h(t, u, v), 3299628645, j(p, w + 8), 23), a(i(t, u, v), 4096336452, j(p, w), 6), a(i(t, u, v), 1126891415, j(p, w + 28), 10), a(i(t, u, v), 2878612391, j(p, w + 56), 15), a(i(t, u, v), 4237533241, j(p, w + 20), 21), a(i(t, u, v), 1700485571, j(p, w + 48), 6), a(i(t, u, v), 2399980690, j(p, w + 12), 10), a(i(t, u, v), 4293915773, j(p, w + 40), 15), a(i(t, u, v), 2240044497, j(p, w + 4), 21), a(i(t, u, v), 1873313359, j(p, w + 32), 6), a(i(t, u, v), 4264355552, j(p, w + 60), 10), a(i(t, u, v), 2734768916, j(p, w + 24), 15), a(i(t, u, v), 1309151649, j(p, w + 52), 21), a(i(t, u, v), 4149444226, j(p, w + 16), 6), a(i(t, u, v), 3174756917, j(p, w + 44), 10), a(i(t, u, v), 718787259, j(p, w + 8), 15), a(i(t, u, v), 3951481745, j(p, w + 36), 21), m = n(m, s), o = n(o, t), q = n(q, u), r = n(r, v) } return l(r, q, o, m).toUpperCase() } var p = null , q = null; return "string" == typeof a ? p = k(a) : a.constructor == Array ? 0 === a.length ? p = a : "string" == typeof a[0] ? p = c(a) : "number" == typeof a[0] ? p = a : q = typeof a[0] : "undefined" != typeof ArrayBuffer ? a instanceof ArrayBuffer ? p = m(new Uint8Array(a)) : a instanceof Uint8Array || a instanceof Int8Array ? p = m(a) : a instanceof Uint32Array || a instanceof Int32Array || a instanceof Uint16Array || a instanceof Int16Array || a instanceof Float32Array || a instanceof Float64Array ? p = m(new Uint8Array(a.buffer)) : q = typeof a : q = typeof a, q && alert("MD5 type mismatch, cannot process " + q), o() }

     

    通过函数跳转,得到了这个MD5加密的JS方法。

    在工具中尝试加密,看看结果是否与网页一致。

    import time import execjs KEY_CODE = "NVPh5oo715z5DIWAeQlhMDsWXXQV4hwtbitrate=0callback=callback123clienttime={time}clientver=2000dfid=-inputtype=0iscorrection=1isfuzzy=0keyword={keyword}mid={time}page=1pagesize=30platform=WebFilterprivilege_filter=0srcappid=2919tag=emuserid=-1uuid={time}NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt" URL_SEARCH = "https://complexsearch.kugou.com/v2/search/song?callback=callback123&keyword={keyword}&page=1&pagesize=30&bitrate=0&isfuzzy=0&tag=em&inputtype=0&platform=WebFilter&userid=-1&clientver=2000&iscorrection=1&privilege_filter=0&srcappid=2919&clienttime={time}&mid={time}&uuid={time}&dfid=-&signature={signature}" # 生成加密 def getSignature(text): with open("kugou.js", "r", encoding='utf-8') as f: js_str = f.read() if js_str is not None: js_obj = execjs.compile(js_str) return js_obj.call('getMD5', text) return None # 返回搜索url def search(keyword): # 获得13位时间戳 millis = str(round(time.time() * 1000)) p = KEY_CODE.format(time=millis, keyword=keyword) signature = getSignature(p) url = URL_SEARCH.format(keyword=keyword, time=millis, signature=signature) print(url)

    https://complexsearch.kugou.com/v2/search/song?callback=callback123&keyword=许嵩&page=1&pagesize=30&bitrate=0&isfuzzy=0&tag=em&inputtype=0&platform=WebFilter&userid=-1&clientver=2000&iscorrection=1&privilege_filter=0&srcappid=2919&clienttime=1600307100792&mid=1600307100792&uuid=1600307100792&dfid=-&signature=AD72928A4E0B85FCDC9A34FF1C238AAB

    访问得到的url,可以成功输出结果,O(∩_∩)O~~

    实现歌曲下载地址

    访问:

    https://wwwapi.kugou.com/yy/index.php?r=play/getdata&callback=jQuery19106972086837400218_1600304199645&hash=2FFF4D0CAD2DADC043B6F0ABA6F7EFC1&album_id=973251&dfid=3dGOvT21OnXb0sdCOD1Z9wUo&mid=dbf31ed5384e98e7138f718097daf2b1&platid=4&_=1600304199647 

    即可获得歌曲下载地址:其中除hash和r参数外,其他参数都可以忽略~参数r表示请求的action

    即:

    https://wwwapi.kugou.com/yy/index.php?r=play/getdata&hash=2FFF4D0CAD2DADC043B6F0ABA6F7EFC1

     

    def get_download_url(hash): headers = { "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36 Edg/85.0.564.51", } cookies = "kg_mid=dbf31ed5384e98e7138f718097daf2b1; Hm_lvt_aedee6983d4cfc62f509129360d6bb3d=1600302507; kg_dfid=3dGOvT21OnXb0sdCOD1Z9wUo; kg_dfid_collect=d41d8cd98f00b204e9800998ecf8427e; Hm_lpvt_aedee6983d4cfc62f509129360d6bb3d=1600304200; kg_mid_temp=dbf31ed5384e98e7138f718097daf2b1; ACK_SERVER_10016=%7B%22list%22%3A%5B%5B%22gzreg-user.kugou.com%22%5D%5D%7D; ACK_SERVER_10015=%7B%22list%22%3A%5B%5B%22gzlogin-user.kugou.com%22%5D%5D%7D; ACK_SERVER_10017=%7B%22list%22%3A%5B%5B%22gzverifycode.service.kugou.com%22%5D%5D%7D" url = URL_DOWNLOAD.format(hash=hash) response = requests.get(url=url, headers=headers, cookies=extract_cookies(cookies)) try: json_dict = response.json() except Exception as e: json_dict = {} return json_dict.get("data", {}).get("play_url")

    python实现搜索和下载

    添加微信回复“酷我”,获得下载地址。

    Processed: 0.014, SQL: 9