继续写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制作音乐下载工具的资料请关注自学编程网其它相关文章!