信息发布→ 登录 注册 退出

python基于tkinter制作无损音乐下载工具(附源码)

发布时间:2026-01-11

点击量:
目录
  • 一.准备工作
  • 二.预览
    • 1.搜索
    • 2.下载
    • 3.结果
  • 三.详细设计
    • 四.源代码
      • 4.1 Music_Search-v1.0.py
      • 4.2 Music_Search_Engine.py
    • 五.总结

      继续写GUI,本次依然使用Tkinter设计一款图形界面,使用Tkinter做一款音乐下载软件,听起来听平常的,但是我这款软件能够下载 无损音乐下载软件,听起来不错吧,Let`s go!

      一.准备工作

      python Tkinter

      二.预览

      1.搜索

      2.下载

      3.结果

      无损音乐就这样下载完了。

      三.详细设计

      这里仅展示我设计的整体思路。

      四.源代码

      4.1 Music_Search-v1.0.py

      from tkinter import *
      from tkinter import ttk
      from tkinter import messagebox
      from Music_Search_Engine import Spider
      import threading
      from tkinter.filedialog import askdirectory
      import os
      
      '''
      1.加入e1绑定事件,b1='disable'
      2. 03.15-使用self.flag判断当前下载任务是否完成
      3.实现UI和爬虫分离,返回实时进度
      '''
      class App:
       def __init__(self):
       self.w=Tk()
       self.w.title('Music_Search-v1.0')
       self.w.resizable(0,0)
       self.flag=True
       width=400
       height=560
       left=(self.w.winfo_screenwidth()-width)/2
       top=(self.w.winfo_screenheight()-height)/2
       self.w.geometry('%dx%d+%d+%d'%(width,height,left,top))
       self.create_widget()
       self.set_widget()
       self.place_widget()
       self.w.mainloop()
      
       def create_widget(self):
       self.e2_var=StringVar()
       self.r_choice=IntVar()
       self.l3_var=StringVar()
       self.l1=ttk.Label(self.w,text='关键字:')
       self.e1=ttk.Entry(self.w)
       self.b1=ttk.Button(self.w,text='搜索')
       self.l4 = ttk.Label(self.w, text='存储路径:')
       self.e2 = ttk.Entry(self.w,textvariable=self.e2_var)
       self.b2 = ttk.Button(self.w, text='选择')
       self.l2=ttk.Label(self.w,text='下载品质:')
       self.r1=Radiobutton(self.w,text='标准',value=1)
       self.r2=Radiobutton(self.w,text='高品',value=2)
       self.r3=Radiobutton(self.w,text='无损',value=3)
       self.b3=ttk.Button(self.w,text='下载')
       self.listbox=Listbox(self.w)
       self.canvas = Canvas(self.w, bg="white")
       self.l3=ttk.Label(self.w)
       self.m=Menu(self.w)
       self.w['menu']=self.m
       self.s1=Menu(self.m,tearoff=False)
       self.s2=Menu(self.m,tearoff=False)
       self.s3=Menu(self.m,tearoff=False)
      
       def set_widget(self):
       self.b1.config(command=lambda:self.thread_it(self.search_music))
       self.e1.config(justify='center')
       self.b2.config(command=self.open_file_savepath)
       self.r1.config(variable=self.r_choice,command=self.show_size,state='disable')
       self.r2.config(variable=self.r_choice,command=self.show_size,state='disable')
       self.r3.config(variable=self.r_choice,command=self.show_size,state='disable')
       self.b3.config(command=lambda:self.thread_it(self.pre_download))
       self.canvas.config(width=380, height=20)
       self.w.bind('<<ListboxSelect>>',self.show_info)
       self.e1.bind('<Return>',self.do_search)
       self.w.protocol('WM_DELETE_WINDOW',self.quit_window)
       self.w.bind('<Escape>',self.do_escape)
       self.l3.config(textvariable=self.l3_var,background='lightblue',justify='center')
       self.l3_var.set('请先搜索')
       self.listbox.config(state='disable')
       self.abs_path = os.path.abspath('./')
       self.e2_var.set(self.abs_path)
       self.e2.config(state='readonly')
       self.b3.config(state='disable')
       self.m.add_cascade(label='文件',menu=self.s1)
       self.s1.add_command(label='打开文件夹',command=self.open_dir)
       self.s1.add_separator()
       self.s1.add_command(label='退出',command=self.quit_window)
       self.m.add_cascade(label='操作',menu=self.s2)
       self.s2.add_command(label='搜索',command=lambda:self.thread_it(self.search_music))
       self.s2.add_command(label='下载',command=lambda:self.thread_it(self.pre_download))
       self.s2.entryconfig("下载",state=DISABLED)
       self.m.add_cascade(label='关于',menu=self.s3)
       self.s3.add_command(label='说明',command=self.show_explian)
      
       def place_widget(self):
       self.l1.place(x=10,y=10)
       self.e1.place(x=80,y=10,width=200)
       self.b1.place(x=310,y=10,height=25,width=80)
       self.l2.place(x=10,y=80)
       self.r1.place(x=80,y=80)
       self.r2.place(x=160,y=80)
       self.r3.place(x=240,y=80)
       self.l4.place(x=10,y=50)
       self.e2.place(x=80,y=50,width=200)
       self.b2.place(x=310,y=45,height=25,width=80)
       self.b3.place(x=310,y=80,height=25,width=80)
       self.listbox.place(x=10,y=110,width=380,height=380)
       self.l3.place(x=0,y=520,width=400,height=35)
       self.canvas.place(x=10,y=492)
      
       def thread_it(self,func,*args):
       t=threading.Thread(target=func,args=args)
       t.setDaemon(True)
       t.start()
      
       def do_search(self,event):
       self.thread_it(self.search_music)
      
       def search_music(self):
       self.l3_var.set('')
       self.listbox.delete(0,END)
       spider=Spider()
       if self.e1.get():
       self.music_list=spider.Get_Music_List(self.e1.get())
       if self.music_list:
       self.listbox.config(state='normal')
       counter=1
       for data in self.music_list:
        song_name = data.get('song_name')
        self.listbox.insert(END,str(counter)+'、'+song_name)
        self.listbox.update()
        counter+=1
       self.l3_var.set(f'共检索到了{len(self.music_list)}首歌曲')
       self.s2.entryconfig("下载", state=NORMAL)
       self.b3.config(state='normal')
       else:
       messagebox.showinfo('提示','没有找到相关歌曲,请更换关键字!')
       self.l3_var.set('没有找到相关歌曲,请更换关键字!')
       self.l3.config(background='lightblue')
       else:
       messagebox.showerror('错误','请输入关键字!')
       self.l3_var.set('请输入关键字!')
       self.l3.config(background='red')
      
       def show_info(self, event):
       self.r1.config(state='normal')
       self.r2.config(state='normal')
       self.r3.config(state='normal')
       self.r_choice.set(0)
       try:
       listbox_index = self.listbox.curselection()[0]#获取选中歌曲索引
       data=self.music_list[listbox_index]
       if data['FileHash']==''and data['FileSize']==0:
       self.r1.config(state='disable')
       self.r1.config(state='disable')
       self.file_size=data['FileSize']
       if data['HQFileHash'] == ''and data['HQFileSize']==0:
       self.r2.config(state='disable')
       self.hq_size=data['HQFileSize']
       if data['SQFileHash'] == ''and data['SQFileSize']==0:
       self.r3.config(state='disable')
       self.sq_size=data['SQFileSize']
       self.l3_var.set('歌曲名称:'+data['song_name'])
       except (IndexError,TclError):
       pass
      
       def show_size(self):
       try:
       if self.r_choice.get() == 1:
       self.l3_var.set('标准格式文件大小:' + self.process_size(self.file_size))
       elif self.r_choice.get() == 2:
       self.l3_var.set('高品质格式文件大小:' + self.process_size(self.hq_size))
       elif self.r_choice.get() == 3:
       self.l3_var.set('无损格式文件大小:' + self.process_size(self.sq_size))
       except AttributeError:
       messagebox.showwarning('警告','请先选择歌曲')
       self.r_choice.set(0)
      
       def process_size(self,bytes):
       try:
       bytes=float(bytes)
       kb=bytes/1024
       except:
       return 'error'
       if kb>1024:
       mb=kb/1024
       if mb>1024:
       gb=mb/1024
       return '%.2fGB'%gb
       else:
       return '%.2fMB'%mb
       else:
       return '%.2fKB'%kb
      
       def open_file_savepath(self):
       self.file = askdirectory()
       if self.file:
       self.e2_var.set(self.file)
      
       def pre_download(self):
       listbox_index = self.listbox.curselection()[0] # 获取选中歌曲索引
       data = self.music_list[listbox_index]
       music_name=data['song_name']
       if self.r_choice.get()==1:
       FileHash=data['FileHash']
       real_link=Spider().get_music_link(FileHash)
       type='mp3'
       if real_link:
       self.download_music(real_link,music_name,type)
       else:
       messagebox.showwarning('警告','没有此音乐版权,正在争取!')
       elif self.r_choice.get()==2:
       HQFileHash=data['HQFileHash']
       type='mp3'
       real_link=Spider().get_music_link(HQFileHash)
       if real_link:
       self.download_music(real_link,music_name,type)
       else:
       messagebox.showwarning('警告','没有此音乐版权,正在争取!')
       elif self.r_choice.get()==3:
       SQFileHash=data['SQFileHash']
       type='flac'
       real_link=Spider().get_music_link(SQFileHash)
       if real_link:
       self.download_music(real_link,music_name,type)
       else:
       messagebox.showwarning('警告','没有此音乐版权,正在争取!')
      
       def download_music(self,music_link,music_name,music_type):
       if self.flag:
       self.flag=False
       file_path=self.e2_var.get()
       # 先清空进度条,再下载
       self.clean_progressbar()
      
       try:
       os.mkdir(file_path+'/My_Music/')
       except:
       pass
       file = file_path+f'/My_Music/{music_name}.{music_type}'
       fill_line = self.canvas.create_rectangle(1.5, 1.5, 0, 23, width=0, fill="green")
       self.l3_var.set(f'正在下载{music_name}......')
       for process in Spider().download_music(music_link,file_path=file):
       self.canvas.coords(fill_line, (0, 0, process, 60))
       self.w.update()
       self.l3_var.set(f'{music_name}.{music_type}下载完成!')
       messagebox.showinfo('提示',f'{music_name}.{music_type}下载完成!')
       self.flag=True
       else:
       messagebox.showwarning('警告','请等待当前任务完成!')
      
       def clean_progressbar(self):
       # 清空进度条
       fill_line = self.canvas.create_rectangle(1.5, 1.5, 0, 23, width=0, fill="white")
       x = 500 # 未知变量,可更改
       n = 380 / x # 465是矩形填充满的次数
       for t in range(x):
       n = n + 380 / x
       # 以矩形的长度作为变量值更新
       self.canvas.coords(fill_line, (0, 0, n, 60))
       self.w.update()
      
       def open_dir(self):
       file_path=self.e2_var.get()
       try:
       os.mkdir(file_path + '/My_Music/')
       except:
       pass
       os.startfile(file_path + '/My_Music/')
      
       def show_explian(self):
       messagebox.showwarning('敬告','本软件仅供学习交流!')
      
       def do_escape(self,event):
       self.quit_window()
      
       def quit_window(self):
       ret=messagebox.askyesno('退出','是否要退出?')
       if ret:
       self.w.destroy()
      
      if __name__ == '__main__':
       a=App()
      

      4.2 Music_Search_Engine.py

      import requests
      import re
      import json
      from urllib import parse
      import hashlib
      from requests.adapters import HTTPAdapter
      
      class Spider(object):
      
       def clean_txt(self, title): # 清洗标题中不能用于命名文件的字符
       rstr = r"[\/\\\:\*\?\"\<\>\|]" # '/ \ : * ? " < > |'
       title = re.sub(rstr, "_", title) # 替换为下划线
       return title
      
       def get_one_page(self, url):
       headers = {
       'referer': 'https://www.kugou.com/song/',
       'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'
       }
       try:
       s = requests.Session() # 保持会话
       s.mount('http://', HTTPAdapter(max_retries=3)) # 最大重试
       s.mount('https://', HTTPAdapter(max_retries=3))
       r = s.get(url, headers=headers, timeout=15) # 超时设置
       r.raise_for_status() # 状态码 如果不是200则报错
       r.encoding = 'utf-8' # r.apparent_encoding#字符类型
       return r.text # 返回页面
       except:
       pass
      
       def Get_Music_List(self, key_word):
       result_list=[]
       search_url = 'http://songsearch.kugou.com/song_search_v2?keyword={}&page=1'.format(key_word)
       total = json.loads(self.get_one_page(search_url))['data']['total']
       #total值为0就是没有搜索到相关歌曲
       if total != 0:
       search_total_url = search_url + '&pagesize=%d' % total
       music_list = json.loads(self.get_one_page(search_total_url))['data']['lists'] # 歌曲列表
       for music in music_list:
       item = {}#防止字典值覆盖
       item['song_name']=self.clean_txt(music['FileName'].replace('<em>', '').replace('</em>', '')) # 歌手—歌曲
       item['FileHash']=music['FileHash']
       item['HQFileHash']=music['HQFileHash']
       item['SQFileHash']=music['SQFileHash']
       item['FileSize']=music['FileSize']
       item['HQFileSize']=music['HQFileSize']
       item['SQFileSize']=music['SQFileSize']
       result_list.append(item)
       return result_list
       else:
       return None
      
       def v2_md5(self, Hash): # 用于生成key,
       return hashlib.md5((Hash + 'kgcloudv2').encode('utf-8')).hexdigest()
      
       def get_music_link(self, hash):
       Hash = str.lower(hash) # 小写哈希值
       key_new = self.v2_md5(Hash) # 生成v2系统key
       Music_api_1 = 'http://trackercdnbj.kugou.com/i/v2/'
       params = {
       'cmd': 23,
       'pid': 1,
       'behavior': 'download',
       'hash': Hash,
       'key': key_new
       }
       try:
       real_music_link=json.loads(self.get_one_page(Music_api_1+'?'+parse.urlencode(params)))['url']
       return real_music_link
       except KeyError:
       return None
      
       #实时返回当前下载进度
       def download_music(self,music_link,file_path):
       headers = {
       'sec-fetch-dest': 'document',
       'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Mobile Safari/537.36'
       }
       r = requests.get(music_link, headers=headers, stream=True)
       chunk_size = 1024 # 每一块的大小,每次下载块的大小
       file_size = int(r.headers['Content-Length']) # 提取出来的文件大小为string格式,使用int()强制转化
       raise_data = 380 / (file_size / chunk_size) # 增量大小,380为进度条的长度
       _size = 0 # 已经下载文件的大小
       with open(file_path, "wb") as f:
       n = 0
       for data in r.iter_content(chunk_size): # inter_content:用于边下载边存硬盘,每次下载chunk_size大小的块
        f.write(data)
        n += raise_data
        yield n
      

      五.总结

      本次使用TKinter制作一款无损音乐下载软件,工具打包好放在了蓝奏云,请自取。思路、代码方面有什么不足欢迎各位大佬指正、批评!如果觉得软件还可以,点个赞吧。

      以上就是python基于tkinter制作无损音乐下载工具(附源码)的详细内容,更多关于python制作音乐下载工具的资料请关注其它相关文章!

      在线客服
      服务热线

      服务热线

      4008888355

      微信咨询
      二维码
      返回顶部
      ×二维码

      截屏,微信识别二维码

      打开微信

      微信号已复制,请打开微信添加咨询详情!