【python爬虫】酷狗音乐爬取练习

news2025/3/11 14:07:28

注意:本次爬取的音乐仅有1分钟试听,仅作学习爬虫的原理,完整音乐需要自行下载客户端。

一、 初步分析

登陆酷狗音乐后随机选取一首歌,在请求里发现一段mp3文件,复制网址,确实是我们需要的url。

复制音频的名字,搜索找到发起请求的网址,发现是在songinfo里

查看参数和请求头,刷新一次,查看是否有哪些参数是变化的。可以发现图中两次请求的这些参数都不同,接下来就寻找这些参数的生成方式,参数clienttime为时间戳,那么就只需要找到signature的生成方式就可以了:

二、分析参数signature

1. 分析过程

搜索参数signature,并在可能生成的位置打上断点,然后刷新网页

网页断在了此处,可以看见参数signature跟函数d与数组s有关。

补充:如果s的长度不为13,需要放行一下,点击这个按钮

查看函数d的定义

发现函数内部没有wordsToBytes函数和函数i的相关定义,那么打下断点,查看具体函数的位置

获得wordsToBytes函数的具体定义

获得i函数的具体定义

i函数里没有r.stringToBytes(t)的相关定义,继续打下断点

找到r.stringToBytes(t)的相关定义

接着查看s的内容:在console里查看s的内容,发现s的值跟之前请求的参数类似。而且下标为0和下标为12的值跟u的值相同

往上查找u的定义,发现u的值是固定的

2. 代码实现

那么就开始实现生成signature的代码(以下为JavaScript代码

function bytesToWords(t) {
    for (var n = [], r = 0, e = 0; r < t.length; r++,
        e += 8)
        n[e >>> 5] |= t[r] << 24 - e % 32;
    return n
}

function rotl(t, n) {
    return t << n | t >>> 32 - n
}

function endian(t) {
    if (t.constructor == Number)
        return 16711935 & rotl(t, 8) | 4278255360 & rotl(t, 24);
    for (var n = 0; n < t.length; n++)
        t[n] = endian(t[n]);
    return t
}

function i(t, c) {
    var l = {
        utf8: {
            stringToBytes: function (t) {
                return l.bin.stringToBytes(unescape(encodeURIComponent(t)))
            },
            bytesToString: function (t) {
                return decodeURIComponent(escape(l.bin.bytesToString(t)))
            }
        },
        bin: {
            stringToBytes: function (t) {
                for (var n = [], r = 0; r < t.length; r++)
                    n.push(255 & t.charCodeAt(r));
                return n
            },
            bytesToString: function (t) {
                for (var n = [], r = 0; r < t.length; r++)
                    n.push(String.fromCharCode(t[r]));
                return n.join("")
            }
        }
    };
    i._ff = function (t, n, r, e, o, i, c) {
        var s = t + (n & r | ~n & e) + (o >>> 0) + c;
        return (s << i | s >>> 32 - i) + n
    },
        i._gg = function (t, n, r, e, o, i, c) {
            var s = t + (n & e | r & ~e) + (o >>> 0) + c;
            return (s << i | s >>> 32 - i) + n
        },
        i._hh = function (t, n, r, e, o, i, c) {
            var s = t + (n ^ r ^ e) + (o >>> 0) + c;
            return (s << i | s >>> 32 - i) + n
        },
        i._ii = function (t, n, r, e, o, i, c) {
            var s = t + (r ^ (n | ~e)) + (o >>> 0) + c;
            return (s << i | s >>> 32 - i) + n
        };
    t.constructor == String ? t = c && "binary" === c.encoding ? o.stringToBytes(t) : l.utf8.stringToBytes(t) : e(t) ? t = Array.prototype.slice.call(t, 0) : Array.isArray(t) || (t = t.toString());
    for (var s = bytesToWords(t), a = 8 * t.length, l = 1732584193, u = -271733879, f = -1732584194, d = 271733878, g = 0; g < s.length; g++)
        s[g] = 16711935 & (s[g] << 8 | s[g] >>> 24) | 4278255360 & (s[g] << 24 | s[g] >>> 8);
    s[a >>> 5] |= 128 << a % 32,
        s[14 + (a + 64 >>> 9 << 4)] = a;
    for (var b = i._ff, p = i._gg, h = i._hh, m = i._ii, g = 0; g < s.length; g += 16) {
        var y = l
            , j = u
            , S = f
            , v = d;
        u = m(u = m(u = m(u = m(u = h(u = h(u = h(u = h(u = p(u = p(u = p(u = p(u = b(u = b(u = b(u = b(u, f = b(f, d = b(d, l = b(l, u, f, d, s[g + 0], 7, -680876936), u, f, s[g + 1], 12, -389564586), l, u, s[g + 2], 17, 606105819), d, l, s[g + 3], 22, -1044525330), f = b(f, d = b(d, l = b(l, u, f, d, s[g + 4], 7, -176418897), u, f, s[g + 5], 12, 1200080426), l, u, s[g + 6], 17, -1473231341), d, l, s[g + 7], 22, -45705983), f = b(f, d = b(d, l = b(l, u, f, d, s[g + 8], 7, 1770035416), u, f, s[g + 9], 12, -1958414417), l, u, s[g + 10], 17, -42063), d, l, s[g + 11], 22, -1990404162), f = b(f, d = b(d, l = b(l, u, f, d, s[g + 12], 7, 1804603682), u, f, s[g + 13], 12, -40341101), l, u, s[g + 14], 17, -1502002290), d, l, s[g + 15], 22, 1236535329), f = p(f, d = p(d, l = p(l, u, f, d, s[g + 1], 5, -165796510), u, f, s[g + 6], 9, -1069501632), l, u, s[g + 11], 14, 643717713), d, l, s[g + 0], 20, -373897302), f = p(f, d = p(d, l = p(l, u, f, d, s[g + 5], 5, -701558691), u, f, s[g + 10], 9, 38016083), l, u, s[g + 15], 14, -660478335), d, l, s[g + 4], 20, -405537848), f = p(f, d = p(d, l = p(l, u, f, d, s[g + 9], 5, 568446438), u, f, s[g + 14], 9, -1019803690), l, u, s[g + 3], 14, -187363961), d, l, s[g + 8], 20, 1163531501), f = p(f, d = p(d, l = p(l, u, f, d, s[g + 13], 5, -1444681467), u, f, s[g + 2], 9, -51403784), l, u, s[g + 7], 14, 1735328473), d, l, s[g + 12], 20, -1926607734), f = h(f, d = h(d, l = h(l, u, f, d, s[g + 5], 4, -378558), u, f, s[g + 8], 11, -2022574463), l, u, s[g + 11], 16, 1839030562), d, l, s[g + 14], 23, -35309556), f = h(f, d = h(d, l = h(l, u, f, d, s[g + 1], 4, -1530992060), u, f, s[g + 4], 11, 1272893353), l, u, s[g + 7], 16, -155497632), d, l, s[g + 10], 23, -1094730640), f = h(f, d = h(d, l = h(l, u, f, d, s[g + 13], 4, 681279174), u, f, s[g + 0], 11, -358537222), l, u, s[g + 3], 16, -722521979), d, l, s[g + 6], 23, 76029189), f = h(f, d = h(d, l = h(l, u, f, d, s[g + 9], 4, -640364487), u, f, s[g + 12], 11, -421815835), l, u, s[g + 15], 16, 530742520), d, l, s[g + 2], 23, -995338651), f = m(f, d = m(d, l = m(l, u, f, d, s[g + 0], 6, -198630844), u, f, s[g + 7], 10, 1126891415), l, u, s[g + 14], 15, -1416354905), d, l, s[g + 5], 21, -57434055), f = m(f, d = m(d, l = m(l, u, f, d, s[g + 12], 6, 1700485571), u, f, s[g + 3], 10, -1894986606), l, u, s[g + 10], 15, -1051523), d, l, s[g + 1], 21, -2054922799), f = m(f, d = m(d, l = m(l, u, f, d, s[g + 8], 6, 1873313359), u, f, s[g + 15], 10, -30611744), l, u, s[g + 6], 15, -1560198380), d, l, s[g + 13], 21, 1309151649), f = m(f, d = m(d, l = m(l, u, f, d, s[g + 4], 6, -145523070), u, f, s[g + 11], 10, -1120210379), l, u, s[g + 2], 15, 718787259), d, l, s[g + 9], 21, -343485551),
            l = l + y >>> 0,
            u = u + j >>> 0,
            f = f + S >>> 0,
            d = d + v >>> 0
    }
    return endian([l, u, f, d])
}

function wordsToBytes(t) {
    for (var n = [], r = 0; r < 32 * t.length; r += 8)
        n.push(t[r >>> 5] >>> 24 - r % 32 & 255);
    return n
}

function bytesToHex(t) {
    for (var n = [], r = 0; r < t.length; r++)
        n.push((t[r] >>> 4).toString(16)),
            n.push((15 & t[r]).toString(16));
    return n.join("")
}

function d(t, r) {
    if (void 0 === t || null === t)
        throw new Error("Illegal argument " + t);
    var e = wordsToBytes(i(t, r));
    return r && r.asBytes ? e : r && r.asString ? o.bytesToString(e) : bytesToHex(e)
}

function getsianature() {
    var s = [
        "NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt",
        "appid=1014",
        "clienttime=1741411989613",
        "clientver=20000",
        "dfid=3Mm61k0WDxvm033Epz2worRG",
        "encode_album_audio_id=j410q60",
        "mid=70789bebe63fb74c52e4a911853f5450",
        "platid=4",
        "srcappid=2919",
        "token=cbfe2e174e4b97fd6aca35682cdba3d2b431c4ed95e2dbd1779e37a7975b672c",
        "userid=2307902397",
        "uuid=70789bebe63fb74c52e4a911853f5450",
        "NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt"
    ];
    return d(s.join(""))
}

console.log(getsianature());

经过一系列调试,可以发现生成的结果与浏览器生成的值一样,那么生成signature的代码就没问题了。

将一些会变的值改为变量,可以看到参数s的值里只有clienttime的值是会变的,因此修改上述代码中getsianature函数,将参数s的值放在python代码中,getsianature函数改完如下图所示:

function getsianature(s) {
    return d(s.join(""))
}

三、获取多首歌

1. 分析过程

点击不同的歌,可以发现每首歌的参数encode_album_audio_id都不同,因此需要获取encode_album_audio_id

搜索歌名,找到歌曲的id

接着查看请求参数,同样有一个signature参数,刷新多次网页发现signature参数会变化,那么重复之前分析signature的步骤

打下断点,查看中断的位置,参数s有所变化

打印s,查看s的内容,除此之外没有变化,那么就沿用先前的代码

2. 代码实现

完整代码如下,注意这里调用了JavaScript代码,需要安装PyExecJS模块:pip install PyExecJS -i https://pypi.tuna.tsinghua.edu.cn/simple。

本代码中JavaScript文件名为kugou.js,JavaScript代码在参数signature的分析中有写到,以下为python代码:

import json
import re
import time
import requests
import execjs


class kugou_music:
    def __init__(self):
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
            'Referer': 'https://www.kugou.com/',
        }

    def get_signature(self, s):
        with open("kugou.js", "r", encoding="utf-8") as f:
            js = f.read()
        ctx = execjs.compile(js)
        signature = ctx.call("getsianature", s)
        return signature

    def get_one_song_url(self, audio_id):
        timestamp = str(int(time.time() * 1000))
        s = [
            "NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt",
            "appid=1014",
            f"clienttime={timestamp}",
            "clientver=20000",
            "dfid=3Mm61k0WDxvm033Epz2worRG",
            f"encode_album_audio_id={audio_id}",
            "mid=70789bebe63fb74c52e4a911853f5450",
            "platid=4",
            "srcappid=2919",
            "token=cbfe2e174e4b97fd6aca35682cdba3d2b431c4ed95e2dbd1779e37a7975b672c",
            "userid=2307902397",
            "uuid=70789bebe63fb74c52e4a911853f5450",
            "NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt"
        ]
        signature = self.get_signature(s)
        params = {
            'srcappid': '2919',
            'clientver': '20000',
            'clienttime': timestamp,
            'mid': '70789bebe63fb74c52e4a911853f5450',
            'uuid': '70789bebe63fb74c52e4a911853f5450',
            'dfid': '3Mm61k0WDxvm033Epz2worRG',
            'appid': '1014',
            'platid': '4',
            'encode_album_audio_id': audio_id,
            'token': 'cbfe2e174e4b97fd6aca35682cdba3d2b431c4ed95e2dbd1779e37a7975b672c',
            'userid': '2307902397',
            'signature': signature
        }
        one_song_url = 'https://wwwapi.kugou.com/play/songinfo'
        response = requests.get(one_song_url, headers=self.headers, params=params)

        song_url = response.json()['data']['play_url']
        return song_url

    def get_signal_music(self, audio_id, audio_name):
        song_url = self.get_one_song_url(audio_id)
        response = requests.get(song_url, headers=self.headers)
        with open(f'{audio_name}.mp3', 'wb') as f:
            f.write(response.content)
            print(f'{audio_name}.mp3下载完成')

    def get_song_id(self,keyword):
        timestamp = str(int(time.time() * 1000))
        s = [
            "NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt",
            "appid=1014",
            "bitrate=0",
            "callback=callback123",
            f"clienttime={timestamp}",
            "clientver=1000",
            "dfid=3Mm61k0WDxvm033Epz2worRG",
            "filter=10",
            "inputtype=0",
            "iscorrection=1",
            "isfuzzy=0",
            f"keyword={keyword}",
            "mid=70789bebe63fb74c52e4a911853f5450",
            "page=1",
            "pagesize=30",
            "platform=WebFilter",
            "privilege_filter=0",
            "srcappid=2919",
            "token=cbfe2e174e4b97fd6aca35682cdba3d2b431c4ed95e2dbd1779e37a7975b672c",
            "userid=2307902397",
            "uuid=70789bebe63fb74c52e4a911853f5450",
            "NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt"
        ]
        signature = self.get_signature(s)
        params = {
            'callback': 'callback123',
            'srcappid': '2919',
            'clientver': '1000',
            'clienttime': timestamp,
            'mid': '70789bebe63fb74c52e4a911853f5450',
            'uuid': '70789bebe63fb74c52e4a911853f5450',
            'dfid': '3Mm61k0WDxvm033Epz2worRG',
            'keyword': keyword,
            'page': '1',
            'pagesize': '30',
            'bitrate': '0',
            'isfuzzy': '0',
            'inputtype': '0',
            'platform': 'WebFilter',
            'userid': '2307902397',
            'iscorrection': '1',
            'privilege_filter': '0',
            'filter': '10',
            'token': 'cbfe2e174e4b97fd6aca35682cdba3d2b431c4ed95e2dbd1779e37a7975b672c',
            'appid': '1014',
            'signature': signature
        }
        song_id_url = 'https://complexsearchretry.kugou.com/v2/search/song'
        response = requests.get(song_id_url, headers=self.headers, params=params, verify=False)
        temp = re.findall(r'callback123(.*)', response.text)[0][1:-1]
        temp = json.loads(temp)
        song = temp['data']['lists']
        return song

    def get_all_song(self,keyword):
        song = self.get_song_id(keyword)
        for i in song:
            song_id = i.get('EMixSongID')
            song_name = i.get('FileName')
            # print(song_name, song_id)
            try:
                self.get_signal_music(song_id, song_name)
            except Exception as e:
                print(f'{song_name}下载失败:', e)


if __name__ == '__main__':
    kugou = kugou_music()
    # kugou.get_signal_music('j410q60')
    keyword='周杰伦'
    kugou.get_all_song(keyword)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2313252.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机视觉cv2入门之图像空域滤波(待补充)

空域滤波 空域滤波是指利用像素及像素领域组成的空间进行图像增强的方法。这里之所以用滤波这个词,是因为借助了频域里的概念。事实上空域滤波技术的效果与频域滤波技术的效果可以是等价的&#xff0c;而且有些原理和方法也常借助频域概念来解释。 原理和分类 空域滤波是在图…

游戏引擎学习第149天

今日回顾与计划 在今天的直播中&#xff0c;我们将继续进行游戏的开发工作&#xff0c;目标是完成资产文件&#xff08;pack file&#xff09;的测试版本。目前&#xff0c;游戏的资源&#xff08;如位图和声音文件&#xff09;是直接从磁盘加载的&#xff0c;而我们正在将其转…

PyCharm 接入 DeepSeek、OpenAI、Gemini、Mistral等大模型完整版教程(通用)!

PyCharm 接入 DeepSeek、OpenAI、Gemini、Mistral等大模型完整版教程&#xff08;通用&#xff09;&#xff01; 当我们成功接入大模型时&#xff0c;可以选中任意代码区域进行解答&#xff0c;共分为三个区域&#xff0c;分别是选中区域、提问区域以及回答区域&#xff0c;我…

升级到碳纤维齿轮是否值得?

引言&#xff1a;当齿轮开始“减肥” 在F1赛车的变速箱里&#xff0c;一个齿轮的重量减轻100克&#xff0c;就能让圈速提升0.1秒&#xff1b; 在无人机旋翼传动系统中&#xff0c;轻量化齿轮可延长续航时间15%&#xff1b; 甚至在高端机械腕表中&#xff0c;碳纤维齿轮的引入…

基于SpringBoot+Vue的瑜伽课体验课预约系统【附源码】

基于SpringBootVue的瑜伽课体验课预约系统 一、系统技术说明二、运行说明三、系统的演示四、系统的核心代码演示 一、系统技术说明 框架&#xff1a;SpringbootVue 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软…

文章被检测出是AI写的怎么办?

随着人工智能技术的飞速发展&#xff0c;AI辅助写作工具逐渐普及&#xff0c;为学生、科研人员以及创作者带来了诸多便利。然而&#xff0c;随之而来的是对学术诚信和内容原创性的担忧。当文章被检测出是AI写作时&#xff0c;应该如何应对&#xff1f;本文将探讨这一问题&#…

DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加列宽调整功能,示例Table14基础固定表头示例

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…

【英伟达AI论文】多模态大型语言模型的高效长视频理解

摘要&#xff1a;近年来&#xff0c;基于视频的多模态大型语言模型&#xff08;Video-LLMs&#xff09;通过将视频处理为图像帧序列&#xff0c;显著提升了视频理解能力。然而&#xff0c;许多现有方法在视觉主干网络中独立处理各帧&#xff0c;缺乏显式的时序建模&#xff0c;…

[Lc10_hash] 总结 | 两数之和 | 字符重排 | 存在重复元素 i ii | 字母异位词分组

目录 1.介绍 2.两数之和 题解 3.面试题 01.02. 判定是否互为字符重排 题解 4.存在重复元素 题解 5.存在重复元素 II 题解 ⭕6.字母异位词分组 题解 1.介绍 哈希表是什么? 存储数据的容器前文&#xff1a;[C_] set | map | unordered_map 有什么用呢&#xff1f;…

缓存之美:Guava Cache 相比于 Caffeine 差在哪里?

大家好&#xff0c;我是 方圆。本文将结合 Guava Cache 的源码来分析它的实现原理&#xff0c;并阐述它相比于 Caffeine Cache 在性能上的劣势。为了让大家对 Guava Cache 理解起来更容易&#xff0c;我们还是在开篇介绍它的原理&#xff1a; Guava Cache 通过分段&#xff08;…

小组件适配屏幕主题色

iOS 18 新增Home screen Tint Color&#xff08;色调&#xff09;选择&#xff0c;用户可以通过以下方式自定义主屏幕颜色&#xff0c;并且小组件&#xff0c;APP 图标也会跟随改颜色。 比如说意料之外的小组件&#xff08;不兼容&#xff09; 白色部分内部应该还有其他显示内…

IO学习---->线程

1.创建两个线程&#xff0c;分支线程1拷贝文件的前一部分&#xff0c;分支线程2拷贝文件的后一部分 #include <head.h> sem_t sem; long half_size 0; // 全局变量&#xff0c;供所有线程共享void* product(void *arg) {FILE *src fopen("IO.text", "…

个人记录,Unity资源解压和管理插件

就是经典的两个AssetStudio 和 Ripper 没有什么干货&#xff0c;就是记录一下&#xff0c;内容没有很详细 AssetStudio 说错了&#xff0c;AssetStudio比较出名&#xff08;曾经&#xff09;&#xff0c;但好像堕落了 这个工具有个好处就是分类选择&#xff0c;&#xff08;…

day19-前端Web——Vue3+TS+ElementPlus

目录 1. Vue工程化1.1 介绍1.2 环境准备1.2.1 NodeJS安装双击安装包选择安装目录验证NodeJS环境变量配置npm的全局安装路径 1.3 Vue项目-创建1.4 Vue项目开发流程1.5 API风格1.6 案例 2. TS2.1 概述2.2 快速入门2.3 常用类型2.3.1 基础类型2.3.2 联合类型2.3.3 函数类型2.3.4 对…

隐私保护在 Facebook 用户身份验证中的应用

在这个数字化的时代&#xff0c;个人隐私保护成为了公众关注的焦点。社交媒体巨头 Facebook 作为全球最大的社交平台之一&#xff0c;拥有数十亿用户&#xff0c;其在用户身份验证过程中对隐私保护的重视程度直接影响着用户的安全感和信任度。本文将探讨 Facebook 在用户身份验…

【JavaWeb学习Day23】

Maven高级 分模块设计与开发 分模块设计&#xff1a;将一个大项目分成若干个子模块&#xff0c;方便项目的维护、扩展&#xff0c;也方便模块间的相互引用&#xff0c;资源共享。 策略&#xff1a; 1.策略一&#xff1a;按照功能模块拆分&#xff0c;比如&#xff1a;公共组…

个人记录的一个插件,Unity-RuntimeMonitor

没有什么干货,仅仅是个人的记录 基于GUI做的一个工具:好处就是Monitor必须,Unity天然支持实时的Monitor;唯一不好处,就是默认字体太小了,layout居中,居右也是要自行设计的。 (下面文字是有一点点写错,但意思和功能就很牛逼了;并不是都按2 x shift,而是一个 shift 添…

【NexLM 开源系列】如何封装多个大模型 API 调用

&#x1f31f; 在这系列文章中&#xff0c;我们将一起探索如何搭建一个支持大模型集成项目 NexLM 的开发过程&#xff0c;从 架构设计 到 代码实战&#xff0c;逐步搭建一个支持 多种大模型&#xff08;GPT-4、DeepSeek 等&#xff09; 的 一站式大模型集成与管理平台&#xff…

Git和GitHub基础教学

文章目录 1. 前言2. 历史3. 下载安装Git3.1 下载Git3.2 安装Git3.3 验证安装是否成功 4. 配置Git5. Git基础使用5.1 通过Git Bash使用5.1.1 创建一个新的仓库。5.1.1.1 克隆别人的仓库5.1.1.2 自己创建一个本地仓库 5.1.2 管理存档 5.2 通过Visual Studio Code使用 6. Git完成远…

笔记六:单链表链表介绍与模拟实现

在他一生中&#xff0c;从来没有人能够像你们这样&#xff0c;以他的视角看待这个世界。 ---------《寻找天堂》 目录 文章目录 一、什么是链表&#xff1f; 二、为什么要使用链表&#xff1f; 三、 单链表介绍与使用 3.1 单链表 3.1.1 创建单链表节点 3.1.2 单链表的头插、…