python实现搜索爬取酷狗音乐(歌曲、歌词、图片)

本文详细介绍了如何通过Python爬虫技术,分析酷狗音乐的音频播放链接,包括数据来源分析、抓包技巧、MD5加密参数破解以及搜索功能的实现,提供了完整的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、爬虫流程

(一)、数据来源分析

        1、明确需求

        2、抓包分析(使用开发者工具)

                (1)、找到音频播放链接

                (2)、找到并分析音频链接信息数据包

(二)、代码实现步骤

        1、发送请求

        2、获取数据

        3、解析数据

        4、保存数据

二、网站分析

1、音频播放链接

打开酷狗音乐网页版,随便点击一首歌曲,然后打开开发者工具,点击媒体(Media),刷新浏览器就可以看到歌曲的数据包,红色方框圈起来的就是歌曲的播放链接,通过复制绿色方框的字符串,使用开发者工具的搜索功能去找到歌曲链接的来源数据包。

 2、歌曲链接的来源数据包

 

 这个数据包的请求URL问号(?)之前是请求链接,问号(?)之后的字符串是请求参数拼接起来的,在同一首歌曲中会发生变化的参数有两个,分别是clienttime(时间戳)、signature(加密参数),其中时间戳可以导入time时间模块进行处理,加密参数需要另外进行分析进行处理。不同的歌曲,歌曲id也会发生变化,所以要实现搜索爬取歌曲,时间戳、歌曲id、加密模块都需要设置变量去接收字符串。这时候对url发送请求,对参数进行处理,解析并保存数据(图片/音频/视频/特定格式文件获得链接,对于链接发送请求,获得二进制数据进行保存,但是这里面歌词并不是链接,可以使用正则表达式去进行匹配替换),就可以获得需要的歌曲名字(album_name)、歌曲id(encode_ablum_audio_id)、图片链接(img)、歌词(lyrics)、播放链接(play_url)。

3、分析加密参数

3.1、单曲加密参数

 

 

 经过观察,这好像是一个MD5的加密,那么我们可以通过在源面板中打断点继续进行分析。

 

 单曲加密参数对比:

 对比发现两首歌曲的参数时间戳和歌曲id是不一样的,其他参数都是不变的。

通过打断点,我们可以得到d(s.join(“”))传出来的字符串,我们可以复制s.join(“”)的参数在网上找一个MD5工具去进行验证,然后得到的结果是一样的,这时候就可以确定是MD5的加密。观察后不难发现,这个加密参数就是使用join()函数对s的参数进行处理,再进行MD5加密。MD5就是设置字符编码为UTF-8,32位 MD5就是32个十六进制数,即 16 个字节。所以我们可以模仿它去进行处理,首先使用join()函数去s进行处理,用string去接受,再使用解密模块hashlib:MD5 = hashlib.md5()接着将得到的字符串设置为UTF-8更新到字典MD5中,再调用hexdigest()函数将它转化为16进制数,这样就可以得到单曲的加密参数了。

 3.2、搜索加密参数

 

 过搜索就可以发现搜索的关键字和加密参数是会变的,其他参数是不变的,所以要实现搜索功能就要对它们进行处理。搜索加密参数的处理与单曲加密参数的处理是一样的,处理完之后就可以对url发送请求,对参数进行处理,解析数据,得到这个数据包里面我们想要的内容。最后再把搜索得到的歌曲id、时间戳调用单曲加密参数处理模块进行处理,得到新的加密参数,这样搜索爬取歌曲下载就可以实现了。

三、完整代码

# 导入数据请求模块
import requests
# 导入时间模块
import time
# 导入解密模块
import hashlib
# 导入正则表达式模块
import re
# 导入json模块
import json
# 导入制表模块
import prettytable as pt

# 模拟浏览器
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 '
                  'Safari/537.36 Edg/119.0.0.0',
    'Cookie': 'kg_mid=3a8e2eda6b55afd434ed43d762bae621; kg_dfid=4XSJ8z0tMH343y3JOZ2ZluzO; '
              'kg_dfid_collect=d41d8cd98f00b204e9800998ecf8427e; Hm_lvt_aedee6983d4cfc62f509129360d6bb3d=1701158152,'
              '1701163020,1701180349,1701337969; kg_mid_temp=3a8e2eda6b55afd434ed43d762bae621; '
              'Hm_lpvt_aedee6983d4cfc62f509129360d6bb3d=1701337996'
}


def Hash_md5(audio_id, date_time):
    # audio_id = '9gaecb60'
    s = [

        "NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt",
        "appid=1014",
        # 时间戳
        f"clienttime={date_time}",
        "clientver=20000",
        "dfid=4XSJ8z0tMH343y3JOZ2ZluzO",
        # 歌曲id
        f"encode_album_audio_id={audio_id}",
        "mid=3a8e2eda6b55afd434ed43d762bae621",
        "platid=4",
        "srcappid=2919",
        "token=",
        "userid=0",
        "uuid=3a8e2eda6b55afd434ed43d762bae621",
        "NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt"
    ]
    # 把列表拼接成字符串
    string = ''.join(s)
    MD5 = hashlib.md5()
    MD5.update(string.encode('utf-8'))
    signature = MD5.hexdigest()
    # print(signature)
    return signature


def search_MD5(world, date_time):
    search_s = [
        "NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt",
        "appid=1014",
        "bitrate=0",
        "callback=callback123",
        f"clienttime={date_time}",
        "clientver=1000",
        "dfid=4XSJ8z0tMH343y3JOZ2ZluzO",
        "filter=10",
        "inputtype=0",
        "iscorrection=1",
        "isfuzzy=0",
        f"keyword={world}",
        "mid=3a8e2eda6b55afd434ed43d762bae621",
        "page=1",
        "pagesize=30",
        "platform=WebFilter",
        "privilege_filter=0",
        "srcappid=2919",
        "token=",
        "userid=0",
        "uuid=3a8e2eda6b55afd434ed43d762bae621",
        "NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt"
    ]
    search_string = ''.join(search_s)
    MD5 = hashlib.md5()
    MD5.update(search_string.encode('utf-8'))
    search_signature = MD5.hexdigest()
    # print(signature)
    return search_signature


# audio_id = '9gaecb60'
# 获取当前时间的毫秒数
# 时间戳13位,所以是到毫秒的,所以我们要*1000,因为我们获取当前时间只精确到秒
date_time = int(time.time() * 1000)
# signature = Hash_md5(audio_id, date_time)


# key = '周杰伦'
key = input('请输入歌名或歌手:')
# 获取加密参数
search_signature = search_MD5(key, date_time)

# 搜索链接
search_url = 'https://complexsearch.kugou.com/v2/search/song?'
# 搜索请求参数
search_data = {
    'callback': 'callback123',
    'srcappid': '2919',
    'clientver': '1000',
    'clienttime': date_time,
    'mid': '3a8e2eda6b55afd434ed43d762bae621',
    'uuid': '3a8e2eda6b55afd434ed43d762bae621',
    'dfid': '4XSJ8z0tMH343y3JOZ2ZluzO',
    'keyword': key,
    'page': '1',
    'pagesize': '30',
    'bitrate': '0',
    'isfuzzy': '0',
    'inputtype': '0',
    'platform': 'WebFilter',
    'userid': '0',
    'iscorrection': '1',
    'privilege_filter': '0',
    'filter': '10',
    'token': '',
    'appid': '1014',
    'signature': search_signature
}

# 发送请求
response = requests.get(url=search_url, params=search_data, headers=headers)
"""" 获取数据 """
search_data = response.text
html_data = re.findall('callback123\((.*)', search_data)[0].replace(')', '')
# 把json字符串,转成字典数据
json_data = json.loads(html_data)
# print(json_data)
tb = pt.PrettyTable()
tb.field_names = ['序号', '歌名', '歌手', '专辑', 'id']
lis = []
num = 1
# for循坏遍历
for index in json_data['data']['lists']:
    dit = {
        '歌名': index['SongName'],
        '歌手': index['SingerName'],
        '专辑': index['AlbumName'],
        'id': index['EMixSongID']
    }
    lis.append(dit)
    tb.add_row([num, index['SongName'], index['SingerName'], index['AlbumName'], index['EMixSongID']])
    num += 1
    # print(dit)
    # print(audio_id)
print(tb)


# audio_id = input('请输入歌曲id:')
# signature = Hash_md5(audio_id, date_time)

def save(audio_id):
    signature = Hash_md5(audio_id, date_time)
    """" 发送请求 """
    # 请求链接
    url = 'https://wwwapi.kugou.com/play/songinfo?'
    # 请求参数
    data = {
        'srcappid': '2919',
        'clientver': '20000',
        'clienttime': date_time,
        'mid': '3a8e2eda6b55afd434ed43d762bae621',
        'uuid': '3a8e2eda6b55afd434ed43d762bae621',
        'dfid': '4XSJ8z0tMH343y3JOZ2ZluzO',
        'appid': '1014',
        'platid': '4',
        'encode_album_audio_id': audio_id,
        'token': '',
        'userid': '0',
        'signature': signature
    }
    # 发送请求
    response = requests.get(url=url, params=data, headers=headers)
    """" 获取数据 """
    json_data = response.json()
    print(json_data)
    """" 解析数据 """
    # 歌名
    audio_name = json_data['data']['audio_name']
    # 音频链接
    play_url = json_data['data']['play_url']
    # 歌词
    lyrics = json_data['data']['lyrics']
    # 匹配歌曲信息中的列表部分并替换为空字符串
    song_info_cleaned = re.sub("\[(.*?)\]", "", lyrics)
    # 图片
    img = json_data['data']['img']
    music_img = requests.get(url=img, headers=headers).content
    #print(img)
    """
        保存数据
        图片/音频/视频/特定格式的文件 <获得链接>
        对于链接发送请求,获得二进制数据进行保存
    """
    print(audio_name,play_url)
    # 对于音频发送请求,获取二进制数据
    music_content = requests.get(url=play_url, headers=headers).content
    with open('酷狗音乐\\' + audio_name + '.mp3', mode='wb+') as f:
        # 写入保存数据
        f.write(music_content)
    # print(response.json())
    print(f'{audio_name}.mp3下载完成')
    # print('下载完成')
    # 歌词
    with open(f'酷狗音乐\\{audio_name}.txt', 'w+', encoding="utf-8") as f:
        f.write(song_info_cleaned)
    print(f'{audio_name}.txt下载完成')
    # 图片
    with open('酷狗音乐\\' + audio_name + '.jpg', mode='wb+') as f:
        # 写入保存数据
        f.write(music_img)
    # print(response.json())
    print(f'{audio_name}.jpg下载完成')


if __name__ == '__main__':
    # save(audio_id)
    page = input('请输入你要下载的歌曲序号 / 全部下载<0>:')
    try:
        if page == '0':
            for li in lis:
                save(audio_id=li['id'])
        else:
            save(audio_id=lis[int(page) - 1]['id'])
    except Exception as e:
        print('你可能输入有误', e)



<think>好的,用户问的是“灵动岛是什么”,我需要先弄清楚这个术语的具体含义。首先,灵动岛听起来像是一个科技产品或功能的名字,可能跟智能手机有关。我记得苹果在iPhone 14 Pro系列中推出了一个叫做Dynamic Island的功能,中文翻译可能就是“灵动岛”。我需要确认这一点。 接下来,我应该解释灵动岛的基本概念。它位于屏幕顶部的药丸形区域,原本是为了容纳前置摄像头和Face ID传感器而设计的。苹果通过软件将其转化为一个交互式的区域,显示实时活动和通知。这和之前的刘海屏设计不同,更具互动性。 然后需要详细说明它的主要功能,比如显示系统提醒(来电、Face ID验证、充电状态等),实时活动(音乐播放、计时器、导航等),以及交互操作(长按、轻触、滑动等手势)。这些功能让用户无需离开当前应用就能快速访问信息,提升使用效率。 还要提到它的设计理念,即通过软硬件结合,将原本的硬件缺陷转化为实用功能,体现了苹果的创新思维。同时,灵动岛的UI设计会根据内容变化形状和大小,保持视觉上的统一。 最后,可能补充一下适用机型,主要是iPhone 14 Pro和Pro Max,以及后续的iPhone 15系列是否支持。另外,可以提及用户和市场的反响,比如是否受到好评,有没有什么缺点,比如适配问题或屏幕空间占用等。 检查是否有遗漏的信息,比如技术细节或与其他品牌的类似功能对比,但用户可能只需要基础介绍,所以保持简洁。确保使用中文,结构清晰,分点说明,同时遵循系统级指令中的格式要求,数学公式部分可能用不上,但要注意其他格式规则。</think>### 灵动岛是什么? **灵动岛**(Dynamic Island)是苹果公司为iPhone 14 Pro及后续高端机型设计的一项交互式界面功能,旨在将屏幕顶部的“药丸形”挖孔区域(用于前置摄像头和Face ID传感器)转化为一个动态显示与交互的核心区域。它通过软硬件结合的方式,将原本静态的硬件缺陷转化为动态的信息中心。 --- ### 核心功能与特点 1. **实时活动显示** 灵动岛可以动态展示当前后台任务的实时状态,例如: - 音乐播放(显示专辑封面、播放进度) - 通话计时或录音状态 - 导航指示、外卖配送进度 - 充电状态或设备连接提示 $$ \text{例:显示“正在播放:歌曲名 - 艺术家”} $$ 2. **交互式操作** 用户可以通过点击、长按或滑动直接与灵动岛互动,无需跳转到对应App: - 轻触展开详细信息(如查看完整歌词) - 长按快速控制功能(如暂停音乐) - 滑动关闭任务 3. **系统通知整合** 将传统弹窗通知转化为更柔和的“气泡”样式,从灵动岛展开,减少对屏幕内容的遮挡。 4. **自适应UI设计** 灵动岛的形态会随内容动态变化,例如: - 来电时扩展为长条形显示通话人信息 - Face ID验证时短暂缩小为圆点 --- ### 技术实现原理 - **软硬件协同**:通过OLED屏幕的局部亮度和色彩控制,隐藏挖孔区域的硬件边界。 - **动画过渡**:流畅的形变动画让视觉焦点集中在信息本身,而非硬件形态。 --- ### 适配机型 目前支持灵动岛的机型包括: - iPhone 14 Pro / Pro Max - iPhone 15全系列 --- ### 评价与意义 - **创新性**:将硬件限制转化为功能亮点,体现了“缺陷设计”的巧思。 - **争议点**:部分用户认为其占用屏幕空间,且第三方App适配需时间完善。 - **行业影响**:推动了安卓厂商对类似交互设计的探索(如“灵动胶囊”)。 如果需要进一步了解具体操作或场景示例,可以随时提问!
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

new不出对象来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值