python爬虫实战之逆向分析酷狗音乐

导读:本篇文章讲解 python爬虫实战之逆向分析酷狗音乐,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com


前言

声明:本文章只是用于学习逆向知识,仅供学习,未经作者同意禁止转载

对于爬虫而言,不管是什么类型的都会遵循这几个步骤

  1. 获取目标url
  2. 分析请求数据
  3. 逆向解密数据
  4. 伪造请求
  5. 清洗数据
  6. 保存数据

这是对于逆向爬虫中的步骤。

本文会使用谷歌浏览器自带的开发者工具,分析网页端的酷狗音乐的请求进行逆向。

当然对于手机端也是可以进行爬虫和逆向的,不过会比网页端复杂多,需要一些工具以及反编译手段,需要java部分基础,这个我们将留到后面讲解学习。

一、请求分析

首先我们先进入酷狗音乐的搜索页面:
https://www.kugou.com/yy/html/search.html#searchType=song&searchKeyWord=快乐崇拜
点击第一首歌进去听一下,然后我们看一下当前页面的url如下:
https://www.kugou.com/mixsong/1rkjpr6a.html?fromsearch=快乐崇拜#hash=CA937F42CE9AACC3CDDB2516DAEE55D4&album_id=8437324&album_audio_id=106774479
我们点击f12打开开发者工具进行抓包找到如图:

在这里插入图片描述
在这里插入图片描述

发现了这里请求是GET并且有我们想要的结果,所以我们可以去分析第一张图的参数

二、逆向思路

经过测试发现该接口只需要midalbum_audio_id两个参数就可以获取到接口了。

那么这个album_audio_id好猜,顾名思义就是这个音乐的id嘛,那么这个mid是个什么呢?

我们可以发现mid是32位的,非常的像md5加密,所以我们去看源代码如图:

在这里插入图片描述
通过搜索mid发现了mid调用了一个getBaseInfo函数,那么我们可以往上看找打在哪里引入(
可以抓包找到也可以往上看引用地址
)

如下:

在这里插入图片描述

可以看到mid参数确实是个md5,所以我们可以把这两个代码单独拿出来

getBaseInfo.min.js如下:

const Guid = function () {
    function e() {
        return (65536 * (1 + Math.random()) | 0).toString(16).substring(1)
    }

    return e() + e() + "-" + e() + "-" + e() + "-" + e() + "-" + e() + e() + e()
}
const MD5 = function (e) {
    function n(e, n) {
        e[n >> 5] |= 128 << n % 32,
            e[14 + (n + 64 >>> 9 << 4)] = n;
        for (var t = 1732584193, s = -271733879, c = -1732584194, u = 271733878, d = 0; d < e.length; d += 16) {
            var f = t
                , m = s
                , g = c
                , p = u;
            s = a(s = a(s = a(s = a(s = o(s = o(s = o(s = o(s = r(s = r(s = r(s = r(s = i(s = i(s = i(s = i(s, c = i(c, u = i(u, t = i(t, s, c, u, e[d + 0], 7, -680876936), s, c, e[d + 1], 12, -389564586), t, s, e[d + 2], 17, 606105819), u, t, e[d + 3], 22, -1044525330), c = i(c, u = i(u, t = i(t, s, c, u, e[d + 4], 7, -176418897), s, c, e[d + 5], 12, 1200080426), t, s, e[d + 6], 17, -1473231341), u, t, e[d + 7], 22, -45705983), c = i(c, u = i(u, t = i(t, s, c, u, e[d + 8], 7, 1770035416), s, c, e[d + 9], 12, -1958414417), t, s, e[d + 10], 17, -42063), u, t, e[d + 11], 22, -1990404162), c = i(c, u = i(u, t = i(t, s, c, u, e[d + 12], 7, 1804603682), s, c, e[d + 13], 12, -40341101), t, s, e[d + 14], 17, -1502002290), u, t, e[d + 15], 22, 1236535329), c = r(c, u = r(u, t = r(t, s, c, u, e[d + 1], 5, -165796510), s, c, e[d + 6], 9, -1069501632), t, s, e[d + 11], 14, 643717713), u, t, e[d + 0], 20, -373897302), c = r(c, u = r(u, t = r(t, s, c, u, e[d + 5], 5, -701558691), s, c, e[d + 10], 9, 38016083), t, s, e[d + 15], 14, -660478335), u, t, e[d + 4], 20, -405537848), c = r(c, u = r(u, t = r(t, s, c, u, e[d + 9], 5, 568446438), s, c, e[d + 14], 9, -1019803690), t, s, e[d + 3], 14, -187363961), u, t, e[d + 8], 20, 1163531501), c = r(c, u = r(u, t = r(t, s, c, u, e[d + 13], 5, -1444681467), s, c, e[d + 2], 9, -51403784), t, s, e[d + 7], 14, 1735328473), u, t, e[d + 12], 20, -1926607734), c = o(c, u = o(u, t = o(t, s, c, u, e[d + 5], 4, -378558), s, c, e[d + 8], 11, -2022574463), t, s, e[d + 11], 16, 1839030562), u, t, e[d + 14], 23, -35309556), c = o(c, u = o(u, t = o(t, s, c, u, e[d + 1], 4, -1530992060), s, c, e[d + 4], 11, 1272893353), t, s, e[d + 7], 16, -155497632), u, t, e[d + 10], 23, -1094730640), c = o(c, u = o(u, t = o(t, s, c, u, e[d + 13], 4, 681279174), s, c, e[d + 0], 11, -358537222), t, s, e[d + 3], 16, -722521979), u, t, e[d + 6], 23, 76029189), c = o(c, u = o(u, t = o(t, s, c, u, e[d + 9], 4, -640364487), s, c, e[d + 12], 11, -421815835), t, s, e[d + 15], 16, 530742520), u, t, e[d + 2], 23, -995338651), c = a(c, u = a(u, t = a(t, s, c, u, e[d + 0], 6, -198630844), s, c, e[d + 7], 10, 1126891415), t, s, e[d + 14], 15, -1416354905), u, t, e[d + 5], 21, -57434055), c = a(c, u = a(u, t = a(t, s, c, u, e[d + 12], 6, 1700485571), s, c, e[d + 3], 10, -1894986606), t, s, e[d + 10], 15, -1051523), u, t, e[d + 1], 21, -2054922799), c = a(c, u = a(u, t = a(t, s, c, u, e[d + 8], 6, 1873313359), s, c, e[d + 15], 10, -30611744), t, s, e[d + 6], 15, -1560198380), u, t, e[d + 13], 21, 1309151649), c = a(c, u = a(u, t = a(t, s, c, u, e[d + 4], 6, -145523070), s, c, e[d + 11], 10, -1120210379), t, s, e[d + 2], 15, 718787259), u, t, e[d + 9], 21, -343485551),
                t = l(t, f),
                s = l(s, m),
                c = l(c, g),
                u = l(u, p)
        }
        return Array(t, s, c, u)
    }

    function t(e, n, t, i, r, o) {
        return l(s(l(l(n, e), l(i, o)), r), t)
    }

    function i(e, n, i, r, o, a, l) {
        return t(n & i | ~n & r, e, n, o, a, l)
    }

    function r(e, n, i, r, o, a, l) {
        return t(n & r | i & ~r, e, n, o, a, l)
    }

    function o(e, n, i, r, o, a, l) {
        return t(n ^ i ^ r, e, n, o, a, l)
    }

    function a(e, n, i, r, o, a, l) {
        return t(i ^ (n | ~r), e, n, o, a, l)
    }

    function l(e, n) {
        var t = (65535 & e) + (65535 & n);
        return (e >> 16) + (n >> 16) + (t >> 16) << 16 | 65535 & t
    }

    function s(e, n) {
        return e << n | e >>> 32 - n
    }

    function c(e) {
        for (var n = Array(), t = (1 << d) - 1, i = 0; i < e.length * d; i += d)
            n[i >> 5] |= (e.charCodeAt(i / d) & t) << i % 32;
        return n
    }

    function u(e) {
        for (var n = "", t = 0; t < 4 * e.length; t++)
            n += "0123456789abcdef".charAt(e[t >> 2] >> t % 4 * 8 + 4 & 15) + "0123456789abcdef".charAt(e[t >> 2] >> t % 4 * 8 & 15);
        return n
    }

    var d = 8;
    return e = e ? function (e) {
        return u(n(c(e), e.length * d))
    }(e) : ""
}

function get_mid() {
    const uid = Guid()
    return MD5(uid)
}

自此mid就完成了,那么现在我们就需要去找album_audio_id了,对于逆向我们有时候也需要正向的去分析,对于这种音乐id,一般应该是其他页面传过来给我们,我们才能去获取的,所以回到一开始的搜索页面,打开f12开发者工具抓包。

如下:
在这里插入图片描述
在这里插入图片描述
可以发现在搜索页面抓到了这个请求,还是一样我们开始分析这个参数。

我们可以观察参数cienttime=1670213288633很明显是一个时间戳,而mid和uuid是相同的,并且我们前面已经解析过了不需要分析,经过测试发现dfid是不变参数keyword是我们搜索的名字,其他参数基本是不变的。

不过我们可以发现,此时还有一个参数signature=b1e228e8bbc9175422a38d2b70986d28,这很明显也是md5加密的,不过我们并不知道是怎么加密的,所以需要继续打断点进行解析测试。

我们使用全局搜索signature关键字

如图:

在这里插入图片描述
这个infSign.min.js看名字很像,我们点进去看看。

在这里插入图片描述
代码是丑化的,我们格式化一下。

在这里插入图片描述
我们可以发现这里很可以,明显这个s参数就是md5的盐,我们打开端点进行调试:

在这里插入图片描述
可以看到这就是s参数的全部,而我们就只需要把这22个参数使用join函数合并起来再进行md5加密,就完成了。

那么我们可以将现在我们所有的思路转化成python代码进行实现。

三、全部代码

main.py如下:

import json
import asyncio
import requests
import execjs
from hashlib import md5
import time
import threading


# 酷狗音乐主函数
class KuGoMusic():
    def __init__(self, input):
        self.new_md5 = md5()
        self.url = "https://complexsearch.kugou.com/v2/search/song"
        self.headers = {
            'user-agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"
        }
        mid = self.get_mid()
        self.child_url = "https://wwwapi.kugou.com/yy/index.php?r=play/getdata&mid={}".format(mid)
        self.query = ""
        self.params = ["NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt", "appid=1014", "bitrate=0", "callback=callback123",
                       "clienttime={}".format(int(time.time() * 1000)), "clientver=1000",
                       "dfid=4XSQkz1mmSGI2XV1Ud1xgR9V", "filter=10", "inputtype=0", "iscorrection=1", "isfuzzy=0",
                       "keyword={}".format(input), "mid={}".format(mid), "page=1", "pagesize=30", "platform=WebFilter",
                       "privilege_filter=0",
                       "srcappid=2919", "token=", "userid=0", "uuid={}".format(mid),
                       "NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt"]
        self.info = "".join(self.params)
        self.new_md5.update(self.info.encode(encoding='utf-8'))
        self.signature = self.new_md5.hexdigest()
        del self.params[0]
        del self.params[-1]
        self.params.append("signature={}".format(self.signature))

    def get_mid(self):
        with open('getBaseInfo.min.js', 'r', encoding='utf-8') as f:
            text = f.read()
        js_data = execjs.compile(text)
        mid = js_data.call('get_mid')
        return mid
        
	# 保存音频给你们当练习写(我没写 = =)
	def with_open(self,url):
		pass
    def get_query(self):
        # self.params.__iter__()
        for index, item in enumerate(self.params):
            if len(self.params) - 1 <= index:
                self.query += item
                break
            self.query += "{}&".format(item)

    def getData(self, url):
        res = requests.get(url, headers=self.headers).json()
        print(res['data']['audio_name'], res['data']['play_url'], res['data']['album_name'])

    async def getData2(self, url):
        loop = asyncio.get_event_loop()
        # requests模块默认不支持异步操作,所以就使用线程池来配合实现了。
        future = await loop.run_in_executor(None, requests.get, url, self.headers)
        res = future.json()
        print(res['data']['audio_name'], res['data']['play_url'], res['data']['album_name'])

    def getData_Threads(self, lists):
        threads = []

        for row in lists:
            url_child = self.child_url + "&album_audio_id={}".format(row["ID"])
            threads.append(threading.Thread(target=self.getData, args=(url_child,)))

        for thread in threads:
            # 执行
            thread.start()
        for thread in threads:
            # 结束
            thread.join()

    def getData_test(self, lists):
        for row in lists:
            url_child = self.child_url + "&album_audio_id={}".format(row["ID"])
            self.getData(url_child)

    def getData_async(self, lists):
        tasks = []
        for row in lists:
            url_child = self.child_url + "&album_audio_id={}".format(row["ID"])
            tasks.append(self.getData2(url_child))
        loop = asyncio.get_event_loop()
        loop.run_until_complete(asyncio.wait(tasks))

    def parse_text(self, text):
        lists = json.loads(text)['data']['lists']
        # 多线程
        # self.getData_Threads(lists)
        # 普通
        # self.getData_test(lists)
        # 协程
        self.getData_async(lists)

    def run(self):
        self.get_query()
        url = "{0}?{1}".format(self.url, self.query)
        res = requests.get(url, headers=self.headers)
        self.parse_text(res.text[12:-2])


if __name__ == '__main__':
    input = input("请输入歌名或歌手: ")
    kugo = KuGoMusic(input)
    kugo.run()

总结

不管是逆向还是非逆向,所有爬虫基本上都遵循着这几个步骤,路还漫长还需不断学习,多加练习。

最后

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/66838.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!