python项目实战---使用图形化界面下载音乐
音乐下载
设计思路:
- 设计界面
- 编写爬虫代码
- 绑定爬虫
- 打包exe文件
这个是最终的设计成果,所有的下载歌曲都在“下载mp3”文件夹里面
完整代码
- 逻辑代码
import os.path
import re
import requests
from PyQt5.QtWidgets import QApplication,QWidget,QMessageBox
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from get_music import get_url
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(800, 500)
self.verticalLayout = QtWidgets.QVBoxLayout(Form)
self.verticalLayout.setObjectName("verticalLayout")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.label = QtWidgets.QLabel(Form)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.label.setFont(font)
self.label.setObjectName("label")
self.horizontalLayout.addWidget(self.label)
self.lineEdit = QtWidgets.QLineEdit(Form)
self.lineEdit.setObjectName("lineEdit")
self.horizontalLayout.addWidget(self.lineEdit)
self.verticalLayout.addLayout(self.horizontalLayout)
self.listWidget = QtWidgets.QListWidget(Form)
self.listWidget.setObjectName("listWidget")
self.verticalLayout.addWidget(self.listWidget)
self.listWidget.itemDoubleClicked.connect(Form.downloads_music)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem)
self.pushButton = QtWidgets.QPushButton(Form)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.pushButton.setFont(font)
self.pushButton.setObjectName("pushButton")
self.horizontalLayout_2.addWidget(self.pushButton)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem1)
self.pushButton_2 = QtWidgets.QPushButton(Form)
self.pushButton_2.setObjectName("pushButton_2")
self.horizontalLayout_2.addWidget(self.pushButton_2)
spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem2)
self.pushButton_3 = QtWidgets.QPushButton(Form)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.pushButton_3.setFont(font)
self.pushButton_3.setObjectName("pushButton_3")
self.horizontalLayout_2.addWidget(self.pushButton_3)
spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem3)
self.verticalLayout.addLayout(self.horizontalLayout_2)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "音乐下载器"))
self.label.setText(_translate("Form", "音乐名称:"))
self.pushButton.setText(_translate("Form", "搜索"))
self.pushButton.clicked.connect(Form.btn_search)
self.pushButton_2.setText(_translate("Form", "更多"))
self.pushButton_2.clicked.connect(Form.btn_more)
self.pushButton_3.setText(_translate("Form", "清空"))
self.pushButton_3.clicked.connect(Form.btn_clear)
class MyWindow(QWidget):
def __init__(self):
super().__init__()
self.ui = Ui_Form()
self.ui.setupUi(self)
self.page1=1
def btn_search(self):
# print('点击搜索')
self.page1 = 1
pr_input = self.ui.lineEdit.text()
linlks = get_url(pr_input,1)
for linlk in linlks:
self.ui.listWidget.addItem(f'歌名:{linlk[0]},歌手:{linlk[1]},id={linlk[2]},下载链接:{linlk[3]}')
# print(f'歌名:{linlk[0]},歌手:{linlk[1]}id={linlk[2]}')
def btn_more(self):
pr_input = self.ui.lineEdit.text()
self.page1 += 1
linlks = get_url(pr_input, self.page1)
for linlk in linlks:
self.ui.listWidget.addItem(f'歌名:{linlk[0]},歌手:{linlk[1]},id={linlk[2]},下载链接:{linlk[3]}')
def btn_clear(self):
self.ui.lineEdit.clear()
self.ui.listWidget.clear()
self.page1 = 1
def downloads_music(self,data): # 这里第二个参数就把双击对象的文本内容传过来了
# print(data.text())
data = data.text()
author = re.findall(fr'歌名:(.*?),',data)[0]
song = re.findall(fr'歌手:(.*?),',data)[0]
id = re.findall(fr'id=(.*?),',data)[0]
download_urls = re.findall('下载链接:(.*)',data)[0]
# print(author)
# print(song)
# print(id)
# print(download_urls)
# QMessageBox.information(self, '下载提示!', f'是否下载{song}-{author}?', QMessageBox.Yes | QMessageBox.No,QMessageBox.Yes) # 这里是下载前的提示,我觉得没用,就隐藏了
code = requests.get(download_urls)
url_text = code.text
if 'ID3'in url_text: # 因为mp3文件的开头都是ID3
music = requests.get(download_urls).content
if not os.path.exists('下载mp3'):
os.mkdir('下载mp3')
# print(f'下载mp3/{song}-{author}-{id}.mp3')
with open(fr'下载mp3/{song}-{author}-{id}.mp3', 'wb') as f:
f.write(music)
QMessageBox.warning(self, '下载提示!', '下载成功')
else:
QMessageBox.warning(self,'下载提示!',f'下载失败!下载地址是:{download_urls}')
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
- 爬虫代码
import pprint
import re
import requests
def get_url(input,pages=1):
url = 'https://music.txqq.pro/'
data = {
'input': input,
'filter': 'name',
'type': 'netease',
'page': pages
}
header = {'x-requested-with':'XMLHttpRequest'}
rt = requests.post(url=url,headers=header,data=data)
rt.encoding = rt.apparent_encoding
links = rt.json()
# print(links)
re_code = links['code']
print(re_code)
mp3_links = links['data']
track_links = []
for link0 in mp3_links:
downloed_url = link0['url']
title = link0['title']
author = link0['author']
id = re.findall('id=(.*?).mp3',downloed_url)[0]
# print(title,author,id,downloed_url)
track_links.append([title,author,id,downloed_url])
return track_links
- 界面py
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '音乐下载器.ui'
#
# Created by: PyQt5 UI code generator 5.15.11
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(400, 300)
self.verticalLayout = QtWidgets.QVBoxLayout(Form)
self.verticalLayout.setObjectName("verticalLayout")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.label = QtWidgets.QLabel(Form)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.label.setFont(font)
self.label.setObjectName("label")
self.horizontalLayout.addWidget(self.label)
self.lineEdit = QtWidgets.QLineEdit(Form)
self.lineEdit.setObjectName("lineEdit")
self.horizontalLayout.addWidget(self.lineEdit)
self.verticalLayout.addLayout(self.horizontalLayout)
self.listView = QtWidgets.QListView(Form)
self.listView.setObjectName("listView")
self.verticalLayout.addWidget(self.listView)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem)
self.pushButton = QtWidgets.QPushButton(Form)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.pushButton.setFont(font)
self.pushButton.setObjectName("pushButton")
self.horizontalLayout_2.addWidget(self.pushButton)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem1)
self.pushButton_2 = QtWidgets.QPushButton(Form)
self.pushButton_2.setObjectName("pushButton_2")
self.horizontalLayout_2.addWidget(self.pushButton_2)
spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem2)
self.pushButton_3 = QtWidgets.QPushButton(Form)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.pushButton_3.setFont(font)
self.pushButton_3.setObjectName("pushButton_3")
self.horizontalLayout_2.addWidget(self.pushButton_3)
spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem3)
self.verticalLayout.addLayout(self.horizontalLayout_2)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "音乐下载器"))
self.label.setText(_translate("Form", "音乐名称:"))
self.pushButton.setText(_translate("Form", "搜索"))
self.pushButton_2.setText(_translate("Form", "更多"))
self.pushButton_3.setText(_translate("Form", "清空"))
设计音乐下载器界面
首先要整一个标签,提示用户要输入的信息
然后再整一个输入框,让用户输入信息,我们获取用户输入的信息
然后就是一个list view这个文本框显示我们根据用户输入的信息得到的内容
接着就是三个按钮:搜索,下一页,清空
这是比较基本的需求,具体的细节可以自己脑补
爬虫代码
设计思路:
- 设计函数:给一个歌名,返回一个含歌曲详细信息的列表
- 找网站
首先是找个音乐网站,这个网站不要太牛逼,不然光反爬就够你喝一壶了
最好是找个小网站,也不要太卡,功能不用太复杂,支持搜索音乐,下载音乐就行
像图片这种格局的网站就好了
这里使用F12查看咱们下载的音乐在哪里,一般都是在数据包里面,也就是异步获取,需要使用post请求
这里的url和data不要弄错,最重要的是请求头headers
因为服务器会从请求头来判断给你返回什么内容,如果没有请求头,就返回源代码,而音乐的下载链接等详细信息都在数据包里面,我们就得不到想要的结果
所以,先去原网站,打开开发者工具栏,找下载链接,先找源代码(一般源代码都没有)
然后刷新网页,查看数据包,你会找到一个只有data的数据包,里面就是一系列的音乐下载链接
可能会遇到的问题
- 你一点击F12网页自动关闭
- 解决方法:换一个网站
- 网站上能找到数据包,但是pycharm中无论如何都得不到数据包,只有源代码
- 解决方法:把网站的数据包的所有请求头全部复制,制成字典,得到数据包之后,再一个个删除键值对
- 数据包里面不是mp3结尾的下载链接
- 解决方法:换网站,这个可能是api,反正我不会
- 没有找到数据包
- 解决方法:换网站,可能是反爬,问题比较复杂
具体问题具体分析,有其他问题评论区见
处理数据包
也就是拿到网站返回的数据包后,对数据包进行拆解
这个数据包一般是json格式,所以用json进行解析,不要用text了
import pprint
import re
import requests
def get_url(input,pages=1):
url = 'https://music.txqq.pro/'
data = {
'input': input,
'filter': 'name',
'type': 'netease',
'page': pages
}
header = {'x-requested-with':'XMLHttpRequest'}
rt = requests.post(url=url,headers=header,data=data)
rt.encoding = rt.apparent_encoding
links = rt.json()
# print(links)
re_code = links['code']
print(re_code)
mp3_links = links['data']
track_links = []
for link0 in mp3_links:
downloed_url = link0['url']
title = link0['title']
author = link0['author']
id = re.findall('id=(.*?).mp3',downloed_url)[0]
# print(title,author,id,downloed_url)
track_links.append([title,author,id,downloed_url])
return track_links
这个数据包一般返回一个字典,第一个数据是code也就是状态码,咱们一般用不到,当然为了使代码更加健壮,可以获取一下这个状态码
第二个数据就是各种歌曲的信息了,包括歌曲的下载链接,歌名,歌手,作词,作曲等等,很明显也是一个列表,所以我使用了for循环,依次拿数据来解析
列表里面的每一项都是字典,所以我用取值的方式得到了对应的信息,也就是23~25行
因为数据包里面有一套歌曲信息,一个个返回比较麻烦,就以列表的形式进行返回了
绑定爬虫
在上一步,我们拿到了歌曲数据包,也就是返回值的列表
函数的使命完成了
现在就是设计槽函数和按钮连接了
第一个槽函数–btn_search
def btn_search(self):
# print('点击搜索')
self.page1 = 1
pr_input = self.ui.lineEdit.text()
linlks = get_url(pr_input,1)
for linlk in linlks:
self.ui.listWidget.addItem(f'歌名:{linlk[0]},歌手:{linlk[1]},id={linlk[2]},下载链接:{linlk[3]}')
# print(f'歌名:{linlk[0]},歌手:{linlk[1]}id={linlk[2]}')
这个槽函数对应的就是搜索按钮,我们想要搜索,就要知道用户想要搜索什么,所以使用lineEdit.text()得到输入框的内容
然后就是把得到的歌名给爬虫函数
然后把得到的信息放在listWidget里面,使用addItem一行行的把内容填充进去
这里一般为了整洁会把下载链接隐藏,那样就会导致一个问题,下面你想要下载的时候,还要去找这个链接,老师的方法是把所有的链接和其他信息储存起来,放在一个全局列表里面,在以后想要下载的时候再去找
我感觉很麻烦,确实是干净了,但是代码的复杂程度就上去了,还浪费了一定的内存空间,去列表找链接也浪费时间
所以我把链接也一同放在listWidget里面了
第二个槽函数–btn_more
def btn_more(self):
pr_input = self.ui.lineEdit.text()
self.page1 += 1
linlks = get_url(pr_input, self.page1)
for linlk in linlks:
self.ui.listWidget.addItem(f'歌名:{linlk[0]},歌手:{linlk[1]},id={linlk[2]},下载链接:{linlk[3]}')
这个槽函数对应的是“更多”按钮,也就是我们想要在同一歌名下得到更多的信息
因为数据包也有次序,在网站上直接搜索歌曲得到的是页数为1的数据包,再点击下一页又会刷新新的数据包,得到页数为2的数据包等等
所以涉及一个页数的信息,在init初始化里面定义一个变量page1,这个变量是专门来定位数据包的
第三个槽函数–btn_clear
def btn_clear(self):
self.ui.lineEdit.clear()
self.ui.listWidget.clear()
self.page1 = 1
这个槽函数是最简单的,负责清理面板,也就是点击‘’清理‘’的时候把lineEdit输入框和listWidget展示框清空,有自带的clear函数
第四个槽函数–downloads_music
def downloads_music(self,data): # 这里第二个参数就把双击对象的文本内容传过来了
# print(data.text())
data = data.text()
author = re.findall(fr'歌名:(.*?),',data)[0]
song = re.findall(fr'歌手:(.*?),',data)[0]
id = re.findall(fr'id=(.*?),',data)[0]
download_urls = re.findall('下载链接:(.*)',data)[0]
# print(author)
# print(song)
# print(id)
# print(download_urls)
# QMessageBox.information(self, '下载提示!', f'是否下载{song}-{author}?', QMessageBox.Yes | QMessageBox.No,QMessageBox.Yes) # 这里是下载前的提示,我觉得没用,就隐藏了
code = requests.get(download_urls)
url_text = code.text
if 'ID3'in url_text: # 因为mp3文件的开头都是ID3
music = requests.get(download_urls).content
if not os.path.exists('下载mp3'):
os.mkdir('下载mp3')
# print(f'下载mp3/{song}-{author}-{id}.mp3')
with open(fr'下载mp3/{song}-{author}-{id}.mp3', 'wb') as f:
f.write(music)
QMessageBox.warning(self, '下载提示!', '下载成功')
else:
QMessageBox.warning(self,'下载提示!',f'下载失败!下载地址是:{download_urls}')
这个槽函数负责处理咱们想要下载的歌曲,也就是在展示框里面看到想要下载的歌曲,双击一下,直接下载
这里需要一个参数,也就是想要下载的歌曲信息,也就是列表的那一行信息,我们只需要在参数里面加个data就可以得到双击的那一行信息
因为前面我把下载的链接写在了信息里面,所以可以直接下载链接,这里我拿了一下歌曲的名字和歌手来创建mp3文件,因为涉及一个重名的问题,我发现重名歌曲的id不同,所以我还取了一下id来作为文件名,经过这个组合就避免的重名的问题
遇到的问题
歌曲的下载链接无效,因为涉及版权等等问题,很多下载链接都是不能使用的,但是这些链接都能正常访问
也就是说,失效的地址和正常的地址都能正常访问
失效的地址打开是一个网站,正常的地址打开就是mp3文件
这里还要再次判断一下链接是否失效,发现mp3文件的开头是’’ID3‘‘,而失效的地址一般没有
所以使用if判断‘’ID3“是否在网页源代码里面
为了让下载的歌曲比较集中,我把所有的歌曲放在了文件夹里面,名称就是下载mp3
打包exe
最后就是把写好的py程序打包,毕竟费那么大精力,不就是让不会python的人直接使用吗
要是仅仅下载歌曲,不搞界面程序,一个爬虫代码就结束了
这里使用pycharm里面的工具,鼠标右键“打开于(open in)”----找到‘’终端‘’
使用pyinstaller
输入指令-F -i 图标.ico 主函数main.py --noconsole(意思是不要终端那个黑窗口)
这里就是之前的文章写的打包命令,在爬虫基础1里面的末尾
-F 生成exe文件
-i 设置图标