目标
主要目的为爬取酷狗音乐网站 mp3 任意音乐数据,并将爬取后的mp3数据保存为以.mp3后缀的音频文件
所用模块
hashlib => md5加密:提供了许多加密哈希算法,包括MD5、SHA-1、SHA-256等。 os => 提供了访问操作系统功能的方法,例如文件操作、目录操作、进程管理等。 time => 提供了处理时间的函数,包括获取当前时间、延时等功能。 requests=> 用于发送HTTP请求和处理响应。 re => 提供了正则表达式操作的函数,用于在字符串中搜索、匹配和替换文本。 json => 用于处理JSON数据,用于JSON格式转换。 tkinter => 提供了创建图形用户界面(GUI)的工具包,可以用于构建窗口、按钮、文本框等GUI元素。
基本思路
一 :音乐获取
打开浏览器,进入酷狗音乐网页,点击F12进入开发者模式
点击上面一栏的媒体,可以看到mp3音乐文件
可以发现这个文件就是当前音乐mp3媒体文件,但是我们需要找到它的来源处,可以在搜索出直接搜索横线那部分
对比一下我们可以发现,我们所需要的mp3文件来源于此处。且位于play_url参数中,因此我们现在需要的就是爬取当前此文件。
所以,我们先确定它的url。通过对比url与字符串参数发现,网页url只为“https://wwwapi.kugou.com/play/songinfo?”,其余都只是参数。
我们再取对比其他音乐的当前地址,发现他们只有“clienttime”,"encode","signature"这几个参数有变化。因此,我们可以知道,在爬取其他音乐时我们也只是需要改这几个参数便可以。
其中,发现“clienttime”为时间戳,"encode"为音乐ID,"signature"为md5加密的参数所以,我们只需要解密'signature'此参数,我们可以通过Ctrl+Swift+f打开全局搜索,搜索'signature',发现signature参数来与此js中,我们可以点击进入js查看
进入此处,我们在signature所需要的参数此行点击打上断电,并刷新页面。鼠标选中参数详细。会发现,此处就是我们所需要的参数。32位字母数字组合。再去查看它组合方式,就会发现它就是s的参数列表,转换成字符串直接用md5加密便可。而再通过对比一下s发现它的参数除了时间戳和音乐id完全一样,因此我们可以自己组合
而后组合加密得出signature参数,便可通过改变音乐id直接请求当前音乐的MP3信息。
二: 搜索指定获取
简单描述此操作 就是输入你想要的 音乐名 或 音乐名加作者名 搜索到音乐,获取 音乐ID 交给
第一步操作获得音乐mp3文件
我们现在需要模拟酷狗搜索栏操作,通过关键字搜索,并获取与关键字最相识的第一个音乐。
继续之前的操作,F12进入,并在左上角点击搜索按钮,搜索当前音乐名,点击进入。并进行对比发现song就是我们需要的数据
进入之后,我们会发现,搜索的所有的音乐内容都在lists中,我们只需要第一条的音乐内容,
并获取此音乐的ID与名字
再通过对比参数中signature发现和之前参数区别一样。因此,复制之前的操作便可
具体的步骤为同样搜索signature找到其js文件,打上断点调试,复制第一步的操作,得到signature参数,加入需要请求的url地址中“https://complexsearch.kugou.com/v2/search/song?”,便可得到搜索的音乐id "EAlbumID"。将其给到第一步id中运行,便可以得到想要的音乐mp3数据。
三: 保存为mp3文件
获取到此音频地址后,对地址发送请求,将请求得到的数据以二进制的方式保存到指定目录文件夹中
代码部分
用到的模块
import hashlib
import os
import time
import requests
import re
import json
from tkinter import *
判断文件是否存在。不存在则创建改文件。
此文件为获取后保存的文件
def directory_create():
"""判断文件是否存在。不存在则创建改文件"""
directory = "./music_files"
if not os.path.exists(directory):
os.makedirs(directory)
通过搜索栏参数(音乐名)获取 搜索第一个的 音乐名 和 音乐id。 也就是第二个步骤(搜索指定获取)操作。
def audio_id_list(music_name):
"""
通过搜索栏参数(音乐名)获取 搜索第一个的 音乐名 and ID
:param music_name: 搜索栏参数(音乐名) 例如: 苏星婕 - 把回忆拼好给你
:return: 音乐名 音乐ID(苏星婕 - 把回忆拼好给你 72jrv7fa)
"""
timestamp = int(time.time() * 1000)
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
}
sign = MD5_sign_search(timestamp, music_name)
datas = {
'callback': 'callback123',
'srcappid': '2919',
'clientver': '1000',
'clienttime': timestamp,
'mid': 'c4de83c1ebb2e73fc5ae95304a674918',
'uuid': 'c4de83c1ebb2e73fc5ae95304a674918',
'dfid': '3MmrUf3e5zpy3cStkN3Bn9oS',
'keyword': music_name,
'page': '1',
'pagesize': '30',
'bitrate': '0',
'isfuzzy': '0',
'inputtype': '0',
'platform': 'WebFilter',
'userid': '2078452878',
'iscorrection': '1',
'privilege_filter': '0',
'filter': '10',
'token': '483ef68936faa09268f3a42f7ab7ee31b584a3f155828a100c95fadf7c5ddd1e',
'appid': '1014',
'signature': sign,
}
response = requests.get(url='https://complexsearch.kugou.com/v2/search/song?', headers=headers, params=datas)
callback_dict = re.findall('callback123\((.*)\)', response.text)[0]
jsurl = json.loads(callback_dict)
fileName = jsurl['data']['lists'][0]['FileName']
eMixSongID = jsurl['data']['lists'][0]['EMixSongID']
return fileName, eMixSongID
通过音乐ID爬取当前音乐的md3地址。也就是第一个操作部分(音乐获取)
def fetch_url(audio_id):
"""
通过音乐ID爬取当前音乐的md3地址
:param audio_id: 音乐ID(72jrv7fa)
:return:音乐url(........mp3)
"""
timestamp = int(time.time() * 1000)
print('audio_id:', audio_id)
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
}
sign = MD5_sign(timestamp, audio_id)
datas = {
'srcappid': '2919',
'clientver': '20000',
'clienttime': timestamp,
'mid': 'c4de83c1ebb2e73fc5ae95304a674918',
'uuid': 'c4de83c1ebb2e73fc5ae95304a674918',
'dfid': '3MmrUf3e5zpy3cStkN3Bn9oS',
'appid': '1014',
'platid': '4',
'encode_album_audio_id': audio_id,
'token': '483ef68936faa09268f3a42f7ab7ee31b584a3f155828a100c95fadf7c5ddd1e',
'userid': '2078452878',
'signature': sign,
}
response = requests.get(url='https://wwwapi.kugou.com/play/songinfo?', headers=headers, params=datas)
jsurl = response.json()
# print('jsurl: ', jsurl)
play_url = jsurl['data']['play_url']
return play_url
获取到此音频地址后,对地址发送请求,将请求得到的数据以二进制的方式保存到指定目录文件夹中。
def download_url(file_name, url_mp3):
"""
通过已经获取的mp3文件保存到文件夹中
:param file_name: 音乐名
:param url_mp3: 音乐url(.....mp3)
:return: 无
"""
response = requests.get(url_mp3)
try:
with open(f"./music_files/{file_name}.mp3", "wb") as f:
f.write(response.content)
except:
with open(f"./music_files/{int(time.time() * 1000)}.mp3", "wb") as f:
f.write(response.content)
print(f'{file_name}-----下载成功')
搜索页面和播放页面的两个signature的解密操作:MD5加密
def MD5_sign(timestamp, audio_id):
"""
通过音乐id解密详情页单个音乐的signature参数
:param timestamp: 时间戳
:param audio_id: 音乐id(例如:72jrv7fa)
:return:
"""
signature_list = ['NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt',
'appid=1014',
f'clienttime={timestamp}',
'clientver=20000',
'dfid=3MmrUf3e5zpy3cStkN3Bn9oS',
f'encode_album_audio_id={audio_id}',
'mid=c4de83c1ebb2e73fc5ae95304a674918',
'platid=4',
'srcappid=2919',
'token=483ef68936faa09268f3a42f7ab7ee31b584a3f155828a100c95fadf7c5ddd1e',
'userid=2078452878',
'uuid=c4de83c1ebb2e73fc5ae95304a674918',
'NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt']
string = "".join(signature_list)
MD5 = hashlib.md5()
MD5.update(string.encode('utf-8'))
sign = MD5.hexdigest() # md5 32位加密内容
return sign
def MD5_sign_search(timestamp, music_name):
"""
通过音乐id解密搜索页的signature参数
:param timestamp:时间戳
:param music_name:搜索框音乐名(例如:把回忆拼好给你)
:return:加密后32位md5参数(例如:72181cc6baf76ee0404837d5d657dd5c)
"""
signature_list = ['NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt',
'appid=1014',
'bitrate=0',
'callback=callback123',
f'clienttime={timestamp}',
'clientver=1000',
'dfid=3MmrUf3e5zpy3cStkN3Bn9oS',
'filter=10',
'inputtype=0',
'iscorrection=1',
'isfuzzy=0',
f'keyword={music_name}',
'mid=c4de83c1ebb2e73fc5ae95304a674918',
'page=1',
'pagesize=30',
'platform=WebFilter',
'privilege_filter=0',
'srcappid=2919',
'token=483ef68936faa09268f3a42f7ab7ee31b584a3f155828a100c95fadf7c5ddd1e',
'userid=2078452878',
'uuid=c4de83c1ebb2e73fc5ae95304a674918',
'NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt']
string = "".join(signature_list)
MD5 = hashlib.md5()
MD5.update(string.encode('utf-8'))
sign_lis = MD5.hexdigest() # md5 32位加密内容
return sign_lis
总代码
总结
import hashlib
import os
import time
import requests
import re
import json
from tkinter import *
def MD5_sign(timestamp, audio_id):
"""
通过音乐id解密详情页单个音乐的signature参数
:param timestamp: 时间戳
:param audio_id: 音乐id(例如:72jrv7fa)
:return:
"""
signature_list = ['NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt',
'appid=1014',
f'clienttime={timestamp}',
'clientver=20000',
'dfid=3MmrUf3e5zpy3cStkN3Bn9oS',
f'encode_album_audio_id={audio_id}',
'mid=c4de83c1ebb2e73fc5ae95304a674918',
'platid=4',
'srcappid=2919',
'token=483ef68936faa09268f3a42f7ab7ee31b584a3f155828a100c95fadf7c5ddd1e',
'userid=2078452878',
'uuid=c4de83c1ebb2e73fc5ae95304a674918',
'NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt']
string = "".join(signature_list)
MD5 = hashlib.md5()
MD5.update(string.encode('utf-8'))
sign = MD5.hexdigest() # md5 32位加密内容
return sign
def MD5_sign_search(timestamp, music_name):
"""
通过音乐id解密搜索页的signature参数
:param timestamp:时间戳
:param music_name:搜索框音乐名(例如:把回忆拼好给你)
:return:加密后32位md5参数(例如:72181cc6baf76ee0404837d5d657dd5c)
"""
signature_list = ['NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt',
'appid=1014',
'bitrate=0',
'callback=callback123',
f'clienttime={timestamp}',
'clientver=1000',
'dfid=3MmrUf3e5zpy3cStkN3Bn9oS',
'filter=10',
'inputtype=0',
'iscorrection=1',
'isfuzzy=0',
f'keyword={music_name}',
'mid=c4de83c1ebb2e73fc5ae95304a674918',
'page=1',
'pagesize=30',
'platform=WebFilter',
'privilege_filter=0',
'srcappid=2919',
'token=483ef68936faa09268f3a42f7ab7ee31b584a3f155828a100c95fadf7c5ddd1e',
'userid=2078452878',
'uuid=c4de83c1ebb2e73fc5ae95304a674918',
'NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt']
string = "".join(signature_list)
MD5 = hashlib.md5()
MD5.update(string.encode('utf-8'))
sign_lis = MD5.hexdigest() # md5 32位加密内容
return sign_lis
def fetch_url(audio_id):
"""
通过音乐ID爬取当前音乐的md3地址
:param audio_id: 音乐ID(72jrv7fa)
:return:音乐url(........mp3)
"""
timestamp = int(time.time() * 1000)
print('audio_id:', audio_id)
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
}
sign = MD5_sign(timestamp, audio_id)
datas = {
'srcappid': '2919',
'clientver': '20000',
'clienttime': timestamp,
'mid': 'c4de83c1ebb2e73fc5ae95304a674918',
'uuid': 'c4de83c1ebb2e73fc5ae95304a674918',
'dfid': '3MmrUf3e5zpy3cStkN3Bn9oS',
'appid': '1014',
'platid': '4',
'encode_album_audio_id': audio_id,
'token': '483ef68936faa09268f3a42f7ab7ee31b584a3f155828a100c95fadf7c5ddd1e',
'userid': '2078452878',
'signature': sign,
}
response = requests.get(url='https://wwwapi.kugou.com/play/songinfo?', headers=headers, params=datas)
jsurl = response.json()
# print('jsurl: ', jsurl)
play_url = jsurl['data']['play_url']
return play_url
def audio_id_list(music_name):
"""
通过搜索栏参数(音乐名)获取 搜索第一个的 音乐名 and ID
:param music_name: 搜索栏参数(音乐名) 例如: 苏星婕 - 把回忆拼好给你
:return: 音乐名 音乐ID(苏星婕 - 把回忆拼好给你 72jrv7fa)
"""
timestamp = int(time.time() * 1000)
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
}
sign = MD5_sign_search(timestamp, music_name)
datas = {
'callback': 'callback123',
'srcappid': '2919',
'clientver': '1000',
'clienttime': timestamp,
'mid': 'c4de83c1ebb2e73fc5ae95304a674918',
'uuid': 'c4de83c1ebb2e73fc5ae95304a674918',
'dfid': '3MmrUf3e5zpy3cStkN3Bn9oS',
'keyword': music_name,
'page': '1',
'pagesize': '30',
'bitrate': '0',
'isfuzzy': '0',
'inputtype': '0',
'platform': 'WebFilter',
'userid': '2078452878',
'iscorrection': '1',
'privilege_filter': '0',
'filter': '10',
'token': '483ef68936faa09268f3a42f7ab7ee31b584a3f155828a100c95fadf7c5ddd1e',
'appid': '1014',
'signature': sign,
}
response = requests.get(url='https://complexsearch.kugou.com/v2/search/song?', headers=headers, params=datas)
callback_dict = re.findall('callback123\((.*)\)', response.text)[0]
jsurl = json.loads(callback_dict)
fileName = jsurl['data']['lists'][0]['FileName']
eMixSongID = jsurl['data']['lists'][0]['EMixSongID']
return fileName, eMixSongID
def download_url(file_name, url_mp3):
"""
通过已经获取的mp3文件保存到文件夹中
:param file_name: 音乐名
:param url_mp3: 音乐url(.....mp3)
:return: 无
"""
response = requests.get(url_mp3)
try:
with open(f"./music_files/{file_name}.mp3", "wb") as f:
f.write(response.content)
except:
with open(f"./music_files/{int(time.time() * 1000)}.mp3", "wb") as f:
f.write(response.content)
print(f'{file_name}-----下载成功')
def directory_create():
"""判断文件是否存在。不存在则创建改文件"""
directory = "./music_files"
if not os.path.exists(directory):
os.makedirs(directory)
if __name__ == '__main__':
directory_create() # 判断music_flie文件是否存在
music_name = '听说你'
audio_id = audio_id_list(music_name) # (苏星婕 - 把回忆拼好给你, 72jrv7fa)
file_name = audio_id[0] # 苏星婕 - 把回忆拼好给你
emixsong_id = audio_id[1] # 72jrv7fa
time.sleep(2)
url_mp3 = fetch_url(emixsong_id) # 获取 ......mp3
download_url(file_name, url_mp3) # 下载保存
本次爬取结束,如果有什么其他问题或不懂可以私信哦~
另外还有GUI渲染过的的exe便携版,想要了解的话可以看下一篇文章