求助:修复多平台音乐下载器
下列多平台音乐下载器,只有 网易云能勉强使用,酷我、QQ音乐、酷狗均无法运行,请高手赐教,万分感谢!!!(本人是新手,诚心求教,不喜勿喷!)
import os
import re
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
import webbrowser
import requests
from threading import Thread
import time
import random
import hashlib
import json
class MusicDownloader:
def __init__(self, width=1000, height=650):
self.root = tk.Tk()
self.root.title("四平台音乐下载器(最终修复版)")
self.root.geometry(f"{width}x{height}")
self.root.resizable(False, False)
self.keyword = tk.StringVar()
self.platform = tk.IntVar(value=1) # 1酷我 2网易云 3QQ 4酷狗
self.quality = tk.StringVar(value="128k")
self.tree = None
self.song_list = []
self.setup_ui()
self.center_window(width, height)
def center_window(self, w, h):
ws = self.root.winfo_screenwidth()
hs = self.root.winfo_screenheight()
x = (ws - w) // 2
y = (hs - h) // 2
self.root.geometry(f"{w}x{h}+{x}+{y}")
def setup_ui(self):
# ...(UI布局代码保持不变,与上一版本相同)...
# 菜单
menu = tk.Menu(self.root)
self.root.config(menu=menu)
help_menu = tk.Menu(menu, tearoff=0)
menu.add_cascade(label="菜单", menu=help_menu)
help_menu.add_command(label="使用说明", command=lambda: webbrowser.open("https://www.baidu.com"))
help_menu.add_command(label="退出", command=self.root.quit)
# 平台选择
frame1 = tk.Frame(self.root)
tk.Label(frame1, text="平台:").pack(side=tk.LEFT, padx=5)
tk.Radiobutton(frame1, text="酷我", variable=self.platform, value=1).pack(side=tk.LEFT, padx=5)
tk.Radiobutton(frame1, text="网易云", variable=self.platform, value=2).pack(side=tk.LEFT, padx=5)
tk.Radiobutton(frame1, text="QQ音乐", variable=self.platform, value=3).pack(side=tk.LEFT, padx=5)
tk.Radiobutton(frame1, text="酷狗", variable=self.platform, value=4).pack(side=tk.LEFT, padx=5)
frame1.pack(pady=5)
# 音质
frame_q = tk.Frame(self.root)
tk.Label(frame_q, text="音质:").pack(side=tk.LEFT, padx=5)
(frame_q, textvariable=self.quality, values=["128k", "320k", "flac"], width=10).pack(side=tk.LEFT)
frame_q.pack(pady=2)
# 搜索
frame2 = tk.Frame(self.root)
tk.Label(frame2, text="歌名/歌手:").pack(side=tk.LEFT, padx=5)
tk.Entry(frame2, textvariable=self.keyword, width=40).pack(side=tk.LEFT, padx=5)
tk.Button(frame2, text="搜索", command=self.start_search_thread, bg="#409eff", fg="white").pack(side=tk.LEFT, padx=5)
frame2.pack(pady=5)
# 表格
frame3 = tk.Frame(self.root)
columns = ("序号", "歌手", "歌名", "专辑", "时长")
self.tree = ttk.Treeview(frame3, columns=columns, show="headings", height=22)
for col in columns:
self.tree.heading(col, text=col)
self.tree.column(col, width=100 if col=="序号" else 180, anchor="center")
self.tree.pack(fill=tk.BOTH, expand=True)
frame3.pack(padx=10, pady=5, fill=tk.BOTH, expand=True)
# 下载按钮
frame4 = tk.Frame(self.root)
tk.Button(frame4, text="下载选中", command=self.download_selected, bg="#67c23a", fg="white").pack(side=tk.LEFT, padx=10)
tk.Button(frame4, text="批量下载本页", command=self.batch_download, bg="#e6a23c", fg="white").pack(side=tk.LEFT, padx=10)
frame4.pack(pady=10)
# ================== 线程安全的UI更新 ==================
def safe_tree_insert(self, values):
self.root.after(0, lambda: self.tree.insert("", "end", values=values))
def safe_show_message(self, title, msg, kind="info"):
def show():
if kind == "info":
messagebox.showinfo(title, msg)
elif kind == "warning":
messagebox.showwarning(title, msg)
elif kind == "error":
messagebox.showerror(title, msg)
self.root.after(0, show)
def safe_tree_clear(self):
self.root.after(0, lambda: [self.tree.delete(i) for i in self.tree.get_children()])
# ================== 搜索入口 ==================
def start_search_thread(self):
kw = self.keyword.get().strip()
if not kw:
self.safe_show_message("提示", "请输入关键词", "warning")
return
Thread(target=self.do_search, daemon=True).start()
def do_search(self):
self.safe_tree_clear()
self.song_list.clear()
kw = self.keyword.get().strip()
p = self.platform.get()
try:
if p == 1:
self.search_kuwo(kw)
elif p == 2:
self.search_netease(kw)
elif p == 3:
self.search_qq(kw)
elif p == 4:
self.search_kugou(kw)
if not self.song_list:
self.safe_show_message("提示", "没有搜索到相关歌曲", "info")
except Exception as e:
self.safe_show_message("错误", f"搜索失败:{str(e)}", "error")
# ================== 1. 酷我 (Kuwo) ==================
def search_kuwo(self, kw):
session = requests.Session()
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36 Edg/147.0.0.0",
"Referer": "https://,
"Origin": "https://
}
token = "Fp4lRrKJ2LmN3pQw" # 通用token
headers["csrf"] = token
headers["Cookie"] = f"kw_token={token}"
url = "https://www.
params = {"key": kw, "pn": 1, "rn": 30, "httpsStatus": 1}
try:
res = session.get(url, params=params, headers=headers, timeout=10).json()
songs = res.get("data", {}).get("list", [])
for i, s in enumerate(songs):
self.safe_tree_insert((i+1, s.get("artist", ""), s.get("name", ""), s.get("album", ""), ""))
self.song_list.append({
"type": "kuwo", "id": s.get("rid"),
"name": s.get("name", ""), "artist": s.get("artist", "")
})
except Exception as e:
print(f"酷我搜索异常: {e}")
# ================== 2. 网易云 (Netease) ==================
def search_netease(self, kw):
url = "https://music.
params = {"s": kw, "type": 1, "limit": 30, "offset": 0}
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36 Edg/147.0.0.0",
"Referer": "https://music.,
"Cookie": "_iuqxldmzr_=32; _ntes_nnid=70b45e8ac49f0009882cdc85f2dbc4a1,1776222084257; _ntes_nuid=70b45e8ac49f0009882cdc85f2dbc4a1; NMTID=00Oq60uB016Bdgsw0JGhqLiIEZDEagAAAGdjxYQvA; WEVNSM=1.0.0; WNMCID=jkpvho.1776222091815.01.0; ntes_utid=tid._.sEHsk6TKPMVAU0UFUAaS54XvRmlObXMB._.0; sDeviceId=YD-a%2FIEZhpUUzpFBlQFUFOHo9TvEj1faHIF; WM_TID=73WR6veDAypEQFBAERbX4pDvFm1bkJFi; __snaker__id=Q0syMXr8fQlCJmia; gdxidpyhxdE=X4l%2FPWSKUBViiQaKvLdfDwuVItrhSa5Mr%2BWsjUppMNDZRPzZvN7wGpRD8so%2F8WC%5CX8576KOG%2BIQIoce%2BqqDnC3iGKN3IX17ciEvm1OhCfIgMpAKGji%5CYtDNxomRU3p0cEpffIshIxrEyv065jHL9qKtEtC6m0y%2F1DJisB1g%2Bz4X36isg%3A1776223006427; MUSIC_U=0033F87DE3FDDA24D91DAC42B7B27E55EF416FF3721663443320A6BF4FB7F33EC9A1F38694657B7E554E15B8C4F7BB4F4D59F99FA6DB648FE660818A4319BE882C073821ADF6920C5B9F8A3820831347509E4AFD836E1C5C12EC36F937835675087E8D8435F13C2B3EAD72357A7CC0F8220B3F12B4DA2FB26F7A7564A5CB7207F9BAE7DFB199D2A01E46DDA394B76F2CD3F07B334FCCB5577BAAE5698590DD4AB1BE8DE64A6419E0B679472F0E7C38F375EEE5ECE4D5A0DA10BCF2663B22A659BEAFBAD5698D97E61CC40B9F833578E7508651429758914CAB816D3EB3EF71216D901060035B620E06001CE45F984F0FC07EE51D0320ED0B5F4943830F10CBEE642D4FA44052FC0C6BB518FF11E2DE94676BA5B4231CB607DE516D5919E108AA535021026256776D228DC91DCAFF661EC4F955E1EF3ED7097D94380C2C57873A4497435D72F4298B4A14B239CE954CAA23CFDFB06673B95D442C38408761726150611A415525CAE3D046113408BD867F06697BAC3F45CD9A2661ED4316713D12E8401CFB368E5E523218F71CC22FDF656BB6B0F6A11CABEA7B970792A5B705CC99; __csrf=3e372ad5cd968d847a1382ba525f9312; ntes_kaola_ad=1; JSESSIONID-WYYY=SKU%5CZuQfUYFsSVNWPFxPBZtSv8IOKFJVreOqyD3xYlIPfBjJueRvk%2B7aOgeMIwD2u%5C6qdoSfTff06tEQ1HHQSE%2BXEWq2JR%5CIRge9jVBlX9l033%5CpNeKvb%2FB9FK5ecSJQk6PxkBbTbAEyYPBU%5CsmSdf94VsJ%2B%2Fpgj7kPKfx8Hf57ACwcy%3A1776409492544; Hm_lvt_1483fb4774c02a30ffa6f0e2945e9b70=1776222091,1776407693; HMACCOUNT=6C051FAE07DABF5F; WM_NI=xQnk8BmW2YdIuvW%2FZ0e4FgLn0p9Zu218rglr6BkIJzDCGt0J%2FScCbzg8fjNxTRkhaLITaQJt9%2BiUb%2Bd6sdwraYLAVXwy1zeaS2rjbmpdcAXSOpIWGKLIx%2FHuVVcrDiiaeng%3D; WM_NIKE=9ca17ae2e6ffcda170e2e6ee8de461b2ebaab5d94bf1bc8aa6d55b878f9b83c234fbbb87b4cb74b0b7b693d32af0fea7c3b92abc8fa28cbb4aaf89febae743b7aa99d0d93ca7ec8a88cf66f4b0f7bbf83af3e8be88ef7af69ea5aac972889d8995b342ed8dada4e172a5e8aa8bcd74988ea589e4488b8cafb2c980fbf5a7b6e121a8eabd85f67aa38784a2d554b5a7ff9be86b91eebe8be167b4bcada2db598db087b8c6419097b8d0cb7f968aad93c763abbfab8fe637e2a3; Hm_lpvt_1483fb4774c02a30ffa6f0e2945e9b70=1776407735"
}
try:
res = requests.post(url, data=params, headers=headers, timeout=10).json()
songs = res.get("result", {}).get("songs", [])
for i, s in enumerate(songs):
name = s.get("name", "")
artist = s.get("artists", [{}])[0].get("name", "")
album = s.get("album", {}).get("name", "")
self.safe_tree_insert((i+1, artist, name, album, ""))
self.song_list.append({
"type": "netease", "id": s.get("id"),
"name": name, "artist": artist
})
except Exception as e:
print(f"网易云搜索异常: {e}")
# ================== 3. QQ音乐 ==================
def search_qq(self, kw):
url = "https://c.y.
params = {"w": kw, "format": "json", "p": 1, "n": 30}
headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36 Edg/147.0.0.0",
"Referer": "https://cn.,
'Cookie':'pgv_pvid=8035143406; qq_domain_video_guid_verify=64d4a72c5e0a5937; _qimei_uuid42=1a3070a0e2a100d79d01f20a86a53bbbca72b9fdd2; _qimei_fingerprint=5de5aa2c0706f4723398e79048ea8c6e; _qimei_h38=19c54d0b9d01f20a86a53bbb0200000241a307; _qimei_i_3=5cef24d6c45c56dcc197f738598570b4f1efacf9130857d7e5897c582ec07138676a62943c89e2b885ac; _qimei_q32=4ab2cb2cba9268490efda42af3843cc9; _qimei_q36=4b595b61524338d549255b9030001a61a210; _qimei_i_2=58c95f86c75f59de9391ff35598070e3ffbaa1f51b590687e686285b2693206d306536c43a88e2bbaaa8; _qimei_i_1=71c955839c0955df9597ac390f8472b6f5eef5f9140a0681e6dd7a582493206c6163359139d8e1dcd182c3c2; fqm_pvqid=1fc55efa-e3cd-495a-a05f-10c10631ef98; ts_refer=cn. ts_uid=9448146472; RK=0aqfi3zl8a; ptcz=b1b31d1eeb3bfd3ef9502526daaf5a5571feb62b7855a3f8a67a6c99700f0c3c; wxopenid=; music_ignore_pskey=202306271436Hn@vBj; euin=ow4ANeoPNeoF7c**; tmeLoginType=2; psrf_qqrefresh_token=2C93D909767D56175EDFE5AD52466C5C; psrf_access_token_expiresAt=1781585793; psrf_qqopenid=F8D1359FD5B671B64324318A6C77631D; uin=2528348386; qqmusic_key=Q_H_L_63k3NY-RST7MAbgkb4Vaw2dHO10kR7moJvwbQdU4gA742wwBCQ-0aQ_AF0gyGOU4uzU9nQXRcz0GnzNvi7LGSyYNWFe0d1g; wxunionid=; psrf_qqunionid=C8BD2B972C4C1D555A6F41A401DB163A; wxrefresh_token=; qm_keyst=Q_H_L_63k3NY-RST7MAbgkb4Vaw2dHO10kR7moJvwbQdU4gA742wwBCQ-0aQ_AF0gyGOU4uzU9nQXRcz0GnzNvi7LGSyYNWFe0d1g; psrf_musickey_createtime=1776401793; psrf_qqaccess_token=5E6D10A4E32D288C68262ECFB9045BE2; fqm_sessionid=ce4dd248-d4b3-4ae9-a254-f9f258b8e40c; pgv_info=ssid=s3944066700; ts_last=y.'
}
try:
res = requests.get(url, params=params, headers=headers, timeout=10).json()
songs = res.get("data", {}).get("song", {}).get("list", [])
for i, s in enumerate(songs):
name = s.get("songname", "")
artist = s.get("singer", [{}])[0].get("name", "")
album = s.get("albumname", "")
self.safe_tree_insert((i+1, artist, name, album, ""))
self.song_list.append({
"type": "qq", "id": s.get("songmid", ""),
"name": name, "artist": artist
})
except Exception as e:
print(f"QQ音乐搜索异常: {e}")
# ================== 4. 酷狗 (Kugou) ==================
def search_kugou(self, kw):
clienttime = int(round(time.time() * 1000))
# 生成设备标识
mid = hashlib.md5(f"kugou_{clienttime}_{random.random()}".encode()).hexdigest()
# 构建签名
sign_str = f"keyword={kw}&page=1&pagesize=30&clienttime={clienttime}&mid={mid}"
signature = hashlib.md5(sign_str.encode()).hexdigest()
url = "https://complexsearch.
params = {
"keyword": kw, "page": 1, "pagesize": 30,
"srcappid": "2919", "clientver": "1000",
"clienttime": clienttime, "mid": mid, "uuid": mid,
"signature": signature
}
headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36 Edg/147.0.0.0", "Referer": "https://www.}
try:
res = requests.get(url, params=params, headers=headers, timeout=10)
# 处理JSONP
text = res.text
if text.startswith("callback123("):
text = text[11:-2]
data = json.loads(text)
songs = data.get("data", {}).get("lists", [])
for i, s in enumerate(songs):
name = s.get("SongName", "")
artist = s.get("SingerName", "")
album = s.get("AlbumName", "")
self.safe_tree_insert((i+1, artist, name, album, ""))
self.song_list.append({
"type": "kugou", "hash": s.get("FileHash", ""),
"name": name, "artist": artist
})
except Exception as e:
print(f"酷狗搜索异常: {e}")
# ================== 获取播放链接 ==================
def get_song_url(self, song):
q = self.quality.get()
try:
# ---------- 酷我 ----------
if song["type"] == "kuwo":
session = requests.Session()
headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36 Edg/147.0.0.0", "Referer": "https://}
# 使用通用token
token = "Fp4lRrKJ2LmN3pQw"
headers["csrf"] = token
headers["Cookie"] = f"kw_token={token}"
url = f"https://antiserver.{song['id']}&format=mp3"
resp = session.get(url, headers=headers, allow_redirects=False, timeout=10)
if resp.status_code == 302:
return resp.headers.get("Location")
return None
# ---------- 网易云 ----------
elif song["type"] == "netease":
official = f"https://music.{song['id']}.mp3"
try:
head = requests.head(official, allow_redirects=True, timeout=8)
if head.status_code == 200:
return official
except:
pass
# 备用:使用第三方API(如果还能用的话)
try:
api = "https://api.
params = {"mid": song["id"], "type": "url"}
resp = requests.get(api, params=params, timeout=10)
data = resp.json()
if data.get("code") == 1 and data.get("data", {}).get("url"):
return data["data"]["url"]
except:
pass
return None
# ---------- QQ音乐 ----------
elif song["type"] == "qq":
if not song["id"]:
return None
# 先获取歌曲详情,提取 media_mid
detail_url = "https://c.y.
params = {"songmid": song["id"], "format": "json", "platform": "yqq"}
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36 Edg/147.0.0.0", "Referer": "https://y.}
detail_res = requests.get(detail_url, params=params, headers=headers, timeout=10)
detail_data = detail_res.json()
song_data = detail_data.get("data", {}).get("list", [])
if song_data:
media_mid = song_data[0].get("mid", "")
if media_mid:
# 128k使用M500,320k使用M800,flac使用F000
prefix = {"128k": "M500", "320k": "M800", "flac": "F000"}.get(q, "M500")
return f"https://dl.stream.qqmusic.{prefix}{media_mid}.mp3?vkey=0&guid=0&fromtag=66"
# 备用:使用第三方API
try:
api = "https://api.
params = {"mid": song["id"], "quality": q.replace("k", "")}
resp = requests.get(api, params=params, timeout=10)
data = resp.json()
if data.get("code") == 1 and data.get("data", {}).get("url"):
return data["data"]["url"]
except:
pass
return None
# ---------- 酷狗 ----------
elif song["type"] == "kugou":
if not song["hash"]:
return None
url = "https://www. + song["hash"]
headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36 Edg/147.0.0.0","Referer": "https://www.}
resp = requests.get(url, headers=headers, timeout=10)
data = resp.json()
if data.get("status") == 1:
play_url = data.get("data", {}).get("play_url")
if play_url:
return play_url
return None
except Exception as e:
print(f"获取播放地址异常 [{song['name']}]: {e}")
return None
# ================== 下载逻辑 ==================
def download_selected(self):
sel = self.tree.selection()
if not sel:
self.safe_show_message("提示", "请先选择歌曲", "warning")
return
idx = self.tree.index(sel[0])
if idx < len(self.song_list):
song = self.song_list[idx]
Thread(target=self.do_download, args=(song,), daemon=True).start()
else:
self.safe_show_message("错误", "未找到对应的歌曲信息", "error")
def batch_download(self):
if not self.song_list:
self.safe_show_message("提示", "暂无歌曲可下载", "info")
return
num = simpledialog.askinteger("批量下载", f"共{len(self.song_list)}首,下载前几首?",
minvalue=1, maxvalue=len(self.song_list))
if not num:
return
def batch_worker():
for song in self.song_list[:num]:
self.do_download(song)
Thread(target=batch_worker, daemon=True).start()
def do_download(self, song):
url = self.get_song_url(song)
if not url:
self.safe_show_message("失败", f"{song['name']} 无法获取播放地址", "error")
return
save_dir = "./MusicDownload"
os.makedirs(save_dir, exist_ok=True)
name = re.sub(r'[\\/*?:"<>|]', "", song["name"])
artist = re.sub(r'[\\/*?:"<>|]', "", song["artist"])
ext = "flac" if self.quality.get() == "flac" else "mp3"
path = os.path.join(save_dir, f"{name} - {artist}.{ext}")
if os.path.exists(path):
self.safe_show_message("提示", f"{name} 已存在,跳过下载", "info")
return
try:
headers = {"User-Agent": "Mozilla/5.0"}
r = requests.get(url, headers=headers, stream=True, timeout=30)
r.raise_for_status()
with open(path, "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
self.safe_show_message("完成", f"下载成功:\n{name}", "info")
except Exception as e:
self.safe_show_message("错误", f"{name} 下载失败:{str(e)}", "error")
def run(self):
self.root.mainloop()
if __name__ == "__main__":
app = MusicDownloader()
app.run()






