掌握 PyQt5:从零开始的桌面应用开发
PyQT5——图形化界面
文章目录
- PyQT5——图形化界面
- 集成化图形界面工具
- 为什么使用 \$ProjectFileDir$?
- 示例场景
- 其他 Varaiables
- pyuic参数解释
- 整体含义
- 示例
- 使用PyQt5和pyuic
- 创建pyqt5的程序
- 创建一个窗口
- app.exec\_()和sys.exit(app.exec_())的区别
- 1. `app.exec_()`
- 2. `sys.exit(app.exec_())`
- 总结
- 添加控件——按钮,对话框等等
- 信号与槽
- 鼠标事件和键盘事件
- 关于鼠标事件的第二个函数的参数
- 参数名称的影响
- 1. **参数的实际用途**:
- 2. **代码可读性**:
- 3. **调用上下文**:
- 小结
- 布局管理
- 盒子布局
- 弹簧--addStretch()
- 网格布局 -- QGridLayout
- 表单布局 -- QFormLayout
- 布局嵌套
- 常用控件
- 文本输入框
- 单选按钮--QRadioButton
- 把信息写在窗体里面
- 复选框--QCheckBox
- 状态转变--stateChanged
- 列表--QListWidget
- 下拉列表--QComboBox
- 下拉列表的桥--currentIndexChanged
- 表格--QTableWidget
- 表格字体设置
- 列表转表格--QTableWidgetItem
- 表格的桥--itemSelectionChanged
- QT图形设计工具--那个外部软件designer
- 业务逻辑
- 案例--音乐下载器
- 完整代码
- 设计音乐下载器界面
- 爬虫代码
- 可能会遇到的问题
- 处理数据包
- 绑定爬虫
- 第一个槽函数--btn_search
- 第二个槽函数--btn_more
- 第三个槽函数--btn_clear
- 第四个槽函数--downloads_music
- 打包exe
需要的库:pyqt5,pyqt5-tools
集成化图形界面工具
安装完上面两个库之后,按着以下顺序找图形化界面工具的程序
在项目里面打开Lib–>site-packages -> qt5-applications -> Qt -> bin -> designer.exe
找对之后,直接双击打开就是图形界面工具
找到这个程序之后就是引入到pycharm里面的外部工具了,在设置里面,找到外部工具那一栏,点击加号,名称自拟,在程序那一栏找刚刚找到的图形界面工具,实参不用管,工作目录写$ProjectFileDir$
这是图形界面工具,我们使用这个工具之后产生的是ui后缀的文件,不能直接被pycharm使用,还需要转化为py格式,所以引入另一个外部工具————pyuic,可以使用pycharm搜索安装或者pip安装,同理先找程序位置,在程序那一栏选择该地址,在实参里面写-m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py
,工作目录写$FileDir$
这里的参数不用管为什么,只需要配置一次的事,不必理会
如果有好奇的,也没事,我问过gpt了
为什么使用 $ProjectFileDir$?
在 PyCharm 中,配置外部工具时,使用$ProjectFileDir$
作为工作目录(working directory)是因为它会自动解析为当前项目的根目录。这是一种方便且灵活的方式,以确保无论你的项目结构如何,脚本或工具都能在正确的目录下运行。
-
项目根目录路径:
$ProjectFileDir$
用于引用PyCharm项目的根目录。这个变量会在运行外部工具时被自动替换为项目文件夹的绝对路径。这意味着不管你在哪里运行该工具,它总是能在项目的根目录下找到你的资源、脚本和输出文件。 -
跨平台兼容性: 使用这种变量避免了手动输入特定路径,特别是在多人协作或跨平台开发时(如在 Windows 和 Unix 系统间切换)。这样,可以避免路径分隔符(如
\
和/
)造成的问题。 -
重用配置: 当你在不同的机器或克隆项目到不同目录时,不需要修改工具配置中的路径,这使得项目配置更加方便和易于管理。
示例场景
假设你有一个名为 test.py
的 Python 脚本,存放在项目的根目录下。如果在 PyCharm 中配置外部工具执行这个脚本:
- 外部工具的程序:
python
- 参数:
test.py
- 工作目录:
$ProjectFileDir$
在运行时,PyCharm 会将 $ProjectFileDir$
替换为实际项目的路径,比如 C:\Users\username\Projects\MyProject
,然后实际的命令将变为:
C:\Python39\python.exe C:\Users\username\Projects\MyProject\test.py
其他 Varaiables
PyCharm 还提供了许多其他预定义变量,可以在配置外部工具时使用,例如:
$FilePath$
: 当前打开文件的路径。$FileDir$
: 当前打开文件的目录。$ProjectName$
: 当前项目的名称。
使用这些变量可以提高开发效率和减少配置错误,使得项目的可移植性和适应性更强。
pyuic参数解释
在 PyCharm 中配置外部工具时,实参(arguments)中的 -m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py
用于运行 PyQt5 的图形用户界面(GUI)文件转换工具 pyuic
,具体来说,解释如下:
-
-m PyQt5.uic.pyuic
:- 这个部分的意思是告诉 Python 运行一个模块,
-m
选项用于执行库中的模块。 - 在这里,
PyQt5.uic.pyuic
是 PyQt5 库中的一个模块,负责将.ui
文件(由 Qt Designer 生成的 GUI 设计文件)转换为 Python 代码。
- 这个部分的意思是告诉 Python 运行一个模块,
-
$FileName$
:- 这是一个 PyCharm 的预定义变量,它会自动被替换为当前打开的文件的完整路径和文件名。
- 例如,如果你打开的文件是
C:\Users\username\Projects\MyProject\test.ui
,那么$FileName$
就会被替换成这个完整路径。
-
-o $FileNameWithoutExtension$.py
:-o
是一个参数,表示输出文件(output file)。$FileNameWithoutExtension$
是另一个预定义变量,它会被替换为当前文件的名称(不带文件扩展名)。- 如果当前打开的文件是
test.ui
,则$FileNameWithoutExtension$
将被替换为test
,所以-o $FileNameWithoutExtension$.py
就会变成-o test.py
。 - 这意味着输出的 Python 文件将被命名为
test.py
(与 UI 文件同名,但扩展名为.py
)。
整体含义
综上所述,整个命令的目的是将当前打开的 .ui
文件转换为 .py
文件,命名方式为与原 .ui
文件相同的名字,但扩展名为 .py
。这在使用 Qt Designer 设计用户界面并希望将其集成到 Python 程序中时非常有用。
示例
假设你在 PyCharm 中打开一个名为 mainwindow.ui
的文件,运行该外部工具后,命令将被解释为:
python -m PyQt5.uic.pyuic C:\Users\username\Projects\MyProject\mainwindow.ui -o C:\Users\username\Projects\MyProject\mainwindow.py
这条命令会将 mainwindow.ui
文件转换为 mainwindow.py
文件,生成的 Python 文件中包含了构建该用户界面的代码,从而可以在 Python 程序中使用它。
使用PyQt5和pyuic
首先是点击工具,在下拉式弹窗里面选择外部工具,选择PyQt5,这时候运行的页面就是自制的窗口了,经过一系列设计,最后点击保存,一般是保存在项目里面,会产生一个ui文件
对着这个ui文件鼠标右键,选择外部工具pyuic,就会自动生成一个同名的py文件,这个py文件就是我们刚刚设计的图形界面
现在的pycharm已经解决了pyqt5只能使用低版本的问题,所以,直接使用你现在使用的python版本就可以了
这些外部工具的安装路径最好不能有中文,可能报错
创建pyqt5的程序
两个基本要素:应用,窗口
-
创建应用
使用类QApplication
-
创建窗口
-
创建控件
控件就是各种对话框,按钮,输入框等等
我们要使用的各种类,都在PyQt5下面的QtWidgets类里面
还要导入应用程序QApplication,
以及三种常用的基本窗口QWidget(小窗口),QMainWindow(大窗口),QDialog
- QWidget 简单窗口,输入框,标签
- QMainWindow 复杂窗口,也就是整个应用界面
- QDialog 对话框
最后使用sys库,自带的,接收底层参数
创建一个窗口
因为这个程序比较复杂,使用类的方式创建,也就是继承类
from PyQt5.QtWidgets import QApplication,QWidget,QMainWindow,QDialog
import sys# 继承类创建程序
class MyWindow(QWidget):def __init__(self):super().__init__()# 设置窗口大小self.resize(1000,900) # 长,宽# 设置窗口位置self.move(600,300) # 长,宽# 设置窗口的标题self.setWindowTitle('这是一个pyqt窗口') if __name__ == '__main__':app = QApplication(sys.argv) # 实例化应用类,使用sys.argv,可以接收参数到命令行window = MyWindow() # 窗口默认隐藏window.show() # 显示窗口# app.exec_() # 运行程序sys.exit(app.exec_())
首先是继承小窗口
使用初始化函数,设置窗口的大小,位置,标题等
- self.resize(长,宽)
- self.move(长,宽)
- self.setWindowTitle(窗口名称)
后面的参数就是以像素为单位,在屏幕上创建
然后就是主函数启动程序,使用QApplication实例化,使用sys.argv接收参数,再实例化设计的窗口,
对于程序退出时,有两种写法
app.exec_()和sys.exit(app.exec_())的区别
在创建 PyQt5 程序时,app.exec_()
和 sys.exit(app.exec_())
都是用来启动应用程序的事件循环,但它们在使用时会有些不同。下面详细解释这两者的区别:
1. app.exec_()
-
功能:
app.exec_()
是启动 PyQt5 应用程序事件循环的简单调用。它开始处理事件,如用户交互、窗口更新等。 -
返回值: 此方法返回一个整数值,表示应用程序的退出状态。这个值通常用于在程序结束时进行一些状态检查,但如果只是单独调用
app.exec_()
,这个返回值并没有被使用。 -
使用方式: 你可以直接调用它来启动应用程序:
app = QApplication(sys.argv) # 其他初始化代码... app.exec_() # 启动事件循环
2. sys.exit(app.exec_())
-
功能:
sys.exit()
是一个用于结束程序的函数,它接受一个整数参数,通常是应用程序的退出状态码。 -
作用: 当你把
app.exec_()
包裹在sys.exit()
中时,你确保应用程序在结束时返回其退出状态码到操作系统。这是一个良好的编程习惯,特别是在需要与其他系统工具、脚本或服务集成时,它们通常根据退出状态来进行错误检查或异常处理。 -
返回值:
app.exec_()
的返回值被传递给sys.exit()
,这样可以使操作系统知道应用是正常退出(返回0)还是异常退出(返回其他非零值)。 -
使用方式: 通常,更推荐的写法是:
app = QApplication(sys.argv) # 其他初始化代码... sys.exit(app.exec_()) # 启动事件循环并确保正确退出状态
总结
- 就功能而言,
app.exec_()
启动事件循环,而sys.exit(app.exec_())
启动事件循环并确保在程序结束时返回正确的退出代码。 - 最佳实践:推荐使用
sys.exit(app.exec_())
,以确保在应用程序结束时,正确传递退出状态给操作系统。这有助于在需要时进行错误处理或调试。
所以,虽然两者在一定情况下可以互换使用,但出于健壮性和可维护性的考虑,最好使用 sys.exit(app.exec_())
。
添加控件——按钮,对话框等等
- 标签 QLabel
- 按钮 QPushButton
- 输入框 QLineEdit
class MyWindow(QWidget):def __init__(self):super().__init__()# 设置窗口大小self.resize(700,600)# 设置窗口位置self.move(600,300)# 设置窗口的标题self.setWindowTitle('这是一个pyqt窗口')# 创建标签self.label = QLabel(self)# 实例化标签,把当前窗口传进去self.label.setText('这是一个标签') # 设置标签内容self.label.move(100,200) # 设置标签位置# 创建按钮self.button = QPushButton(self)self.button.setText('这是按钮')self.button.move(250,200)
信号与槽
每个按钮和标签都是独立的,互不关联,现在我们希望按下按钮,标签发生改变等等一系列操作
可以通过槽函数,把指令传输给其他的地方
class MyWindow(QWidget):def __init__(self):super().__init__()# 设置窗口大小self.resize(700,600)# 设置窗口位置self.move(600,300)# 设置窗口的标题self.setWindowTitle('这是一个pyqt窗口')# 创建标签self.label = QLabel(self)# 实例化标签,把当前窗口传进去self.label.setText('这是一个标签') # 设置标签内容self.label.move(100,200) # 设置标签位置# 创建按钮self.button = QPushButton(self)self.button.setText('这是按钮')self.button.move(250,200)self.button.clicked.connect(self.btn_click)def btn_click(self):self.label.setText('点击')
这里我们在类里面创建了一个槽函数,这个函数就是点击按钮的时候,需要发生的事件,这里就是把标签的内容改成“点击”
然后就是把按钮和槽函数绑定
首先是按钮,发生点击事件,也就是按钮被点击了clicked,然后使用connect链接槽函数
鼠标事件和键盘事件
就是点击鼠标左右键,还有按下键盘这些动作
class MyWindow(QWidget):def __init__(self):super().__init__()# 设置窗口大小self.resize(700,600)# 设置窗口位置self.move(600,300)# 设置窗口的标题self.setWindowTitle('这是一个pyqt窗口')# 创建标签self.label = QLabel(self)# 实例化标签,把当前窗口传进去self.label.setText('这是一个标签') # 设置标签内容self.label.move(100,200) # 设置标签位置# 创建按钮self.button = QPushButton(self)self.button.setText('这是按钮')self.button.move(250,200)self.button.clicked.connect(self.btn_click)# 改写鼠标事件def mousePressEvent(self, event):if event.button() == Qt.LeftButton:self.label.setText('鼠标左键')elif event.button() == Qt.RightButton:self.label.setText('鼠标右键')else:self.label.setText('鼠标未按下')def mouseReleaseEvent(self, a123):self.label.setText('鼠标释放')# 键盘事件def keyPressEvent(self, event):self.label.setText('键盘按下')def keyReleaseEvent(self, a0):self.label.setText('键盘释放')# 槽函数def btn_click(self):self.label.setText('点击')
这里有两个鼠标事件,第一个是鼠标按下mousePressEvent,这里需要使用Qt库来判断鼠标的动作
初始化函数加一:from PyQt5.QtCore import Qt
鼠标事件的第一个参数是这个程序本身,第二个参数则是事件的别名,这里的event.button() 就是鼠标的按键,如果是鼠标左键,或者右键等等就执行对应的函数
- mousePressEvent 鼠标按下
- mouseReleaseEvent 鼠标释放
- keyPressEvent 键盘按下
- keyReleaseEvent 键盘释放
关于鼠标事件的第二个函数的参数
在 PyQt5 中,鼠标事件(以及其他事件)的方法是由 Qt 框架在内部调用的。这些事件方法通常名称是固定的,例如 mouseReleaseEvent(self, event)
,但是你可以自定义它们的参数名称。event
是一个常用的命名习惯,但它并不是必须的。
def mouseReleaseEvent(self, a123): # 这实际上是可行的# 处理鼠标释放事件pass
参数名称的影响
1. 参数的实际用途:
- 方法的第二个参数(如
event
)是一个事件对象,封装了关于事件的所有信息(如鼠标位置、按钮状态等)。 - 你可以用任何有效的标识符来替代
event
,但在使用这个参数时,必须知道它代表的是哪个事件类型。
2. 代码可读性:
- 使用
event
作为参数名可以提高代码的可读性。当其他开发者(或你自己)看到这个方法时,自然会理解这是一个事件对象。 - 如果使用不太明确的名称(如
a123
),尽管代码仍然可以正常工作,但其他人可能会难以理解其含义。
3. 调用上下文:
- Qt 框架根据事件类型和相关信息自动调用这些方法。在调用时,Qt 会将实际的事件对象传递给你自定义的参数。因此,无论你命名参数为多少,实际上传递的都是同一个事件对象。
小结
- 在技术上,参数的名称是可随意选择的,不会影响功能。例如,
def mouseReleaseEvent(self, custom_name):
和def mouseReleaseEvent(self, event):
的效果是相同的。 - 然而,为了提高代码的清晰度和可维护性,建议遵循社区的命名规范,使用
event
或更具描述性的名称。这样能够提高代码的可读性,帮助他人理解你的代码。
布局管理
之前学的那种move,resize等等都是固定好位置的布局,不能随着窗口大小的改变而改变,就是不太美观,可能会出问题,所以引申出布局管理来
- 水平布局 QHBoxLayout
- 垂直布局 QVBoxLayout
- 网格布局
- 表单布局
盒子布局
水平布局和垂直布局需要使用盒子管理器,就是那些控件需要在盒子里面摆放
class MyWindow(QWidget):def __init__(self):super().__init__()# 设置窗口大小self.resize(700,600)# 设置窗口位置self.move(600,300)# 设置窗口的标题self.setWindowTitle('这是一个pyqt窗口')# 创建按钮btn1 = QPushButton('按钮1',self)btn2 = QPushButton('按钮2',self)btn3 = QPushButton('按钮3',self)# 放置按钮到盒子v_layout = QVBoxLayout() # 实例化垂直布局盒子v_layout.addWidget(btn1)v_layout.addWidget(btn2)v_layout.addWidget(btn3)# 把盒子放在窗体里面self.setLayout(v_layout)
这些控件在初始化的时候就确定好了,不用再写函数了
这里我们创建了三个按钮btn123,还有一个垂直布局的盒子,把按钮放在盒子里面,最后把盒子整个放在窗体里面就完了
弹簧–addStretch()
在垂直布局或者水平布局的时候,不希望按钮或者标签之间离的太近,或者离得太远,就需要弹簧来控制,
比如在按钮1和2之间加一个弹簧
v_layout.addWidget(btn1)
v_layout.addStretch()
v_layout.addWidget(btn2)
v_layout.addWidget(btn3)
很明显,无论如何拖动窗口一直保持这个布局
这里我们可以添加很多个弹簧
v_layout.addWidget(btn1)
v_layout.addStretch(1)
v_layout.addWidget(btn2)
v_layout.addStretch(4)
v_layout.addWidget(btn3)
这个弹簧里面的参数,就是弹簧之间的比例,不写参数默认是1,也就是弹簧所占的空间一样大
像这里,我写了1和4,创建页面的时候就是一比四的空隙
水平布局和垂直布局类似
只需要改调用的盒子类型,就可以从垂直布局改成水平布局了
# 创建按钮
btn1 = QPushButton('按钮1',self)
btn2 = QPushButton('按钮2',self)
btn3 = QPushButton('按钮3',self)
# 放置按钮到盒子
v_layout = QHBoxLayout() # 实例化水平布局盒子 # 这里从QVBoxLayout发生改变v_layout.addWidget(btn1)
v_layout.addStretch(1)
v_layout.addWidget(btn2)
v_layout.addStretch(4)
v_layout.addWidget(btn3)
网格布局 – QGridLayout
# 创建按钮
btn1 = QPushButton('按钮1',self)
btn2 = QPushButton('按钮2',self)
btn3 = QPushButton('按钮3',self)
btn4 = QPushButton('按钮4',self)
btn5 = QPushButton('按钮5',self)
# 放置按钮到盒子
g_layout = QGridLayout()
g_layout.addWidget(btn1,0,0)
g_layout.addWidget(btn2,1,0)
g_layout.addWidget(btn3,0,1)
g_layout.addWidget(btn4,2,2)
g_layout.addWidget(btn5,2,4)self.setLayout(g_layout)
这里就是设置了5个按钮,下面就是放置的位置
使用网格布局的时候,这个网格的大小,由填写的xy坐标最大值决定,比如在上面的5个按钮中,最大的x是2,最大的y是4,由于第3列是空行,所以,这里的按钮5自动填充在(2,3)了
表单布局 – QFormLayout
这个布局在使用的时候,是按行添加内容的,一般处理输入用户名密码之类的
f_layout = QFormLayout()
f_layout.addRow(QLabel('用户名:'),QLineEdit())
f_layout.addRow(QLabel('密码:'),QLineEdit())
self.setLayout(f_layout)
在参数里面直接写标签和输入框库就可以了
布局可以互相嵌套
因为按钮一般存放在布局里面,而布局在类里面,所以第二个参数self可以不写
布局嵌套
# 表单布局
f_layout = QFormLayout()
f_layout.addRow(QLabel('用户名:'),QLineEdit())
f_layout.addRow(QLabel('密码:'),QLineEdit())# 水平布局盒子
h_layout = QHBoxLayout()
btn_ok = QPushButton('确认')
btn_cancel = QPushButton('取消')
h_layout.addWidget(btn_ok)
h_layout.addWidget(btn_cancel)
f_layout.addRow(h_layout)self.setLayout(f_layout)
首先是实例化一个表单布局
使用addRow添加控件,这里使用了QLabel添加标签,使用QLineEdit添加输入框
同样在添加控件,然后就是实例化一个水平布局的盒子,使用QPushButton创建俩按钮,再使用addWidget添加控件,最后把这个水平布局盒子按行添加到表单布局里面
常用控件
- 文本输入控件
- 单行文本输入 – QLineEdit
- 多行文本输入 – QTextEdit
- 单选控件
- 复选控件(多选)
- 列表控件
- 下拉式列表控件
- 表格控件
文本输入框
在输入文本的时候可以是单行输入也可以是多行输入
在输入的时候,还可以设置显示模式,比如输入密码的时候,不显示输入的信息,只有那几个黑点,用来保护密码
f_layout = QFormLayout()
user_name = QLineEdit('Aa123') # 参数啥也不写就是空
user_password = QLineEdit()
user_password.setEchoMode(QLineEdit.Password) # 把密码的输入框设置为密码模式,就看不到输入的内容了text_Edit = QTextEdit()
f_layout.addRow(QLabel('用户名:'),user_name)
f_layout.addRow(QLabel('密码:'),user_password)
f_layout.addRow(text_Edit)self.setLayout(f_layout)
我们在实例化输入框的时候可以在参数里面写一些信息,这里的Aa123就会显示在输入框里面
单选按钮–QRadioButton
h_layout = QHBoxLayout()
btn1 = QRadioButton('A')
btn2 = QRadioButton('B')
btn3 = QRadioButton('C')
h_layout.addWidget(QLabel("请选择:"))
h_layout.addWidget(btn1)
h_layout.addWidget(btn2)
h_layout.addWidget(btn3)
self.setLayout(h_layout)
现在是创建了几个按钮,想要把这些按钮绑定事件需要使用槽函数
h_layout = QHBoxLayout()btn1 = QRadioButton('A')btn2 = QRadioButton('B')btn3 = QRadioButton('C')h_layout.addWidget(QLabel("请选择:"))btn1.toggled.connect(self.btn_click) btn2.clicked.connect(self.btn_click)btn3.toggled.connect(self.btn_click)h_layout.addWidget(btn1)h_layout.addWidget(btn2)h_layout.addWidget(btn3)self.setLayout(h_layout)def btn_click(self):btn = self.sender()if btn.isChecked(): # 同一布局下面是互斥的msg = f'你选择了:{btn.text()}'print(msg)
增加了按钮和槽函数的链接
由两种方式可以连接按钮和槽函数:clicked(点击),toddled(选中)
槽函数:这里使用了self.sender() sender可以获取发送信息的对象
btn.isChecked()是为了再次确认按钮被选择
{btn.text()}是显示按钮的文本信息
把信息写在窗体里面
按照我的想法测试了一下,竟然实现了
这里我是想实现把你的操作输出在窗体里面,也就是你选择了ABC等等,之前是打印在了pycharm里面
完整代码:
class MyWindow(QWidget):def __init__(self):super().__init__()self.resize(700,600)self.move(600,300)self.setWindowTitle('pyqt窗口')self.choose_btn = QLabel()h_layout = QHBoxLayout()btn1 = QRadioButton('A')btn2 = QRadioButton('B')btn3 = QRadioButton('C')h_layout.addWidget(QLabel("请选择:"))btn1.toggled.connect(self.btn_click)btn2.clicked.connect(self.btn_click)btn3.toggled.connect(self.btn_click)h_layout.addWidget(btn1)h_layout.addWidget(btn2)h_layout.addWidget(btn3)h_layout.addWidget(self.choose_btn)self.setLayout(h_layout)def btn_click(self):btn = self.sender()self.choose_btn.setText(f'你选择了{btn.text()}')
这里我增加了一个标签choose_btn,我希望这个标签显示的是我的选择,所以在点击按钮时,触发槽函数,进而输出信息到窗体
复选框–QCheckBox
v_layout = QVBoxLayout()v_layout.addWidget(QLabel('你的爱好是:'))
s1 = QCheckBox('编程')
s2 = QCheckBox('打游戏')
s3 = QCheckBox('追剧')
s4 = QCheckBox('听歌')v_layout.addWidget(s1)
v_layout.addWidget(s2)
v_layout.addWidget(s3)
v_layout.addWidget(s4)
v_layout.addStretch(1)
self.setLayout(v_layout)
使用复选框QCheckBox库,按顺序创建按钮并添加到复选框里面
状态转变–stateChanged
这次把按钮和槽函数连接的时候需要使用的是状态改变函数
v_layout = QVBoxLayout()v_layout.addWidget(QLabel('你的爱好是:'))s1 = QCheckBox('编程')s2 = QCheckBox('打游戏')s3 = QCheckBox('追剧')s4 = QCheckBox('听歌')s1.stateChanged.connect(self.btn_chicked)s2.stateChanged.connect(self.btn_chicked)s3.stateChanged.connect(self.btn_chicked)s4.stateChanged.connect(self.btn_chicked)v_layout.addWidget(s1)v_layout.addWidget(s2)v_layout.addWidget(s3)v_layout.addWidget(s4)v_layout.addStretch(1)self.setLayout(v_layout)def btn_chicked(self):btn = self.sender()print(f'你选择了{btn.text()},当前状态是{btn.isChecked()}')# btn.isChecked()是判断是否被勾选,如果被勾选为真否则为假
因为这个选项点击一次是True,再次点击同一个选项就是False,都是点击,但是选项的状态不同,使用isChecked可以看到选项的状态
列表–QListWidget
这个函数需要使用addItem添加内容,之前是addWidget
f_layout = QFormLayout()list1 = QListWidget()list1.addItem('A')
list1.addItem('B')list1.addItems(['c','d','e'])
f_layout.addRow(QLabel('你的选择是:'),list1)
self.setLayout(f_layout)
首先是实例化了一个表单布局的盒子
然后是实例化一个列表
接着往列表里面添加内容,有两种方式:一次添加一项内容addItem()
一次添加多个内容:addItems([‘’,‘’,‘’]) 这里的参数就是一个列表
列表选项绑定槽函数
f_layout = QFormLayout()list1 = QListWidget()list1.addItem('A')list1.addItem('B')list1.addItems(['c','d','e'])f_layout.addRow(QLabel('你的选择:'),list1)list1.currentItemChanged.connect(self.list_selected)self.setLayout(f_layout)def list_selected(self,item):print(f'{item.text()}')
这里使用了currentItemchanged作为选项被选中的信号
下拉列表–QComboBox
f_layout = QFormLayout()
list1 = QComboBox()
list1.addItem('A')
list1.addItem('B')
list1.addItems(['c','d','e'])
f_layout.addRow(QLabel('你的选择:'),list1)
self.setLayout(f_layout)
和列表差不多,这个有索引值,也就是说每个选项按顺序排好有自己的序号
下拉列表的桥–currentIndexChanged
class MyWindow(QWidget):def __init__(self):super().__init__()self.resize(700,600)self.move(600,300)self.setWindowTitle('pyqt窗口')f_layout = QFormLayout()list1 = QComboBox()list1.addItem('A')list1.addItem('B')list1.addItems(['c','d','e'])list1.currentIndexChanged.connect(self.list_selected)f_layout.addRow(QLabel('你的选择:'),list1)self.setLayout(f_layout)def list_selected(self,idx):item = self.sender()print(f'你选择了:{item.currentText()},索引值是:{idx}')
这里是self.sender是发出信号的来源,给了item,对item再使用currentText(),就可以获取按钮的文本信息了,这里idx是按钮的索引值
索引值是从0开始的
表格–QTableWidget
类似excel里面的表格
- 设置表格行数–setRowCount
- 设置表格列数–setColumnCount
- 设置表头–setHorizontalHeaderLabels
- 设置表格数据–setItem
表格字体设置
from PyQt5.QtGui import QFont
table.setFont(QFont('宋体',12))
列表转表格–QTableWidgetItem
list_tatle = ['代号','类别','姓名'] # 表头
list1 = [[1122,'aabb','小王'],[2233,'bbcc','小张'],[3344,'adad','小李'],[5566,'cece','小红']] # 表格数据v_layout = QVBoxLayout() # 盒子
table = QTableWidget() # 表格
table.setRowCount(len(list1)) # 设置行数
table.setColumnCount(len(list_tatle)) # 设置列数
table.setFont(QFont('宋体',12)) # 设置字体
table.setHorizontalHeaderLabels(list_tatle) # 设置标头
# 填充表格数据 # 使用两个for循环依次填入表格
for row in range(len(list1)):for col in range(len(list_tatle)):data = QTableWidgetItem(list1[row][col])table.setItem(row,col,data)v_layout.addWidget(table)
self.setLayout(v_layout)
我们需要使用QTableWidgetItem从列表获取数据,再使用setItem把二维列表的数据传给表格
表格的桥–itemSelectionChanged
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QDialog, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, \QGridLayout, QFormLayout, QLineEdit, QTextEdit,QRadioButton,QCheckBox,QListWidget,QComboBox,QTableWidget,QTableWidgetItem
import sys
from PyQt5.QtCore import Qt # 对鼠标按键的识别
from PyQt5.QtGui import QFontlist_tatle = ['代号','类别','姓名']
list1 = [['1122','aabb','小王'],['2233','bbcc','小张'],['3344','adad','小李'],['5566','cece','小红']]# 继承类创建程序
class MyWindow(QWidget):def __init__(self):super().__init__()self.resize(700,600)self.move(600,300)self.setWindowTitle('pyqt窗口')v_layout = QVBoxLayout()table = QTableWidget()table.setRowCount(len(list1)) # 设置行数table.setColumnCount(len(list_tatle)) # 设置列数table.setFont(QFont('宋体',12)) # 设置字体table.setHorizontalHeaderLabels(list_tatle) # 设置标头# 填充表格数据 # 使用两个for循环依次填入表格for row in range(len(list1)):for col in range(len(list_tatle)):data = QTableWidgetItem(list1[row][col])table.setItem(row,col,data)table.itemSelectionChanged.connect(self.item_select)v_layout.addWidget(table)self.setLayout(v_layout)def item_select(self):data = self.sender()row = data.currentRow()print(list1[row])if __name__ == '__main__':app = QApplication(sys.argv)window = MyWindow()window.show()sys.exit(app.exec_())
这里需要把我们自己创建的二维列表放在全局变量当中,不然槽函数不能访问到这个列表
我们使用itemSelectionChanged连接表格和槽函数
槽函数使用self.sender找到信息来源,在使用currentRow获取当前选择的是表格的哪一行
QT图形设计工具–那个外部软件designer
首先一上来就是选择窗口,默认是有5个选择:
从上到下的顺序:按钮在底部,按钮在右侧,没有按钮,大窗口,小窗口(widget)
然后在软件的左侧就是各种控件
在构造界面的时候只需要从左侧把控件移动到窗口中
想要让两个控件对齐,只需要选中他们,鼠标右键选择布局,就可以对齐了
把配套的案件对齐之后,对着界面为空的地方再次单击鼠标右键就可以实现对整体的布局了
对整体进行布局之后,就可以实现对窗体的任意拖动了,窗体内的控件也可以等比例变化了
在对整体完成布局之后,还可以对控件进行微调,比如点击用户名这个标签,可以在属性里面选择font,进而选择粗体,就可以改变字体的属性了
这是实现了对窗口的重命名,首先在对象查看器里面选择窗口,然后在属性里面找到windowTitle,在这一行后面填写你想要的名字
设计完界面之后进行保存,会在pycharm的项目文件夹出现这个ui文件,在使用pyuic这个工具进行格式转换
然后就是调用这个设计的界面了
模板:
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QDialog, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, \QGridLayout, QFormLayout, QLineEdit, QTextEdit,QRadioButton,QCheckBox,QListWidget,QComboBox,QTableWidget,QTableWidgetItem
import sysclass MyWindow(QWidget):def __init__(self):super().__init__()self.ui = Ui_Form() # 实例化界面类self.ui.setupUi(self) # 实例化界面函数,真正的启动 # 这里的setupUi需要一个对象也就是咱自己创建的类selfif __name__ == '__main__':app = QApplication(sys.argv)window = MyWindow()window.show()sys.exit(app.exec_())
这个主函数是启动应用的开关,class类是咱自己设计的逻辑函数,里面负责调用界面脚本等等
前两行就是需要的库了,当然是用到什么库写什么库,我这里是把全部的库都写上了,有点臃肿
然后就是ui转py文件了,这里是把py文件整体复制下来,填充到4、5行的位置
最后自己构建一下槽函数,把按钮绑定一下,就完了
业务逻辑
业务逻辑就是验证用户名和密码,这个函数写在自己的类里面,界面py尽量不动
def retranslateUi(self, Form):_translate = QtCore.QCoreApplication.translateForm.setWindowTitle(_translate("Form", "登录框"))self.label.setText(_translate("Form", "用户名"))self.label_2.setText(_translate("Form", "密码 "))self.pushButton.setText(_translate("Form", "确认"))self.pushButton.clicked.connect(Form.get_login) # 绑定槽函数,注意参数是Formself.pushButton_2.setText(_translate("Form", "取消"))class MyWindow(QWidget):def __init__(self):super().__init__()self.ui = Ui_Form() # 实例化界面类self.ui.setupUi(self) # 实例化界面函数,真正的启动def get_login(self):print('点击确认')user_name = self.ui.lineEdit.text()password = self.ui.lineEdit_2.text()print(f'用户{user_name},密码{password}')
在逻辑函数中,可以访问界面函数的变量,比如这里就获取了输入框的信息
案例–音乐下载器
设计思路:
- 设计界面
- 编写爬虫代码
- 绑定爬虫
- 打包exe文件
这个是最终的设计成果,所有的下载歌曲都在“下载mp3”文件夹里面
完整代码
- 逻辑代码
import os.path
import reimport requests
from PyQt5.QtWidgets import QApplication,QWidget,QMessageBox
import sysfrom PyQt5 import QtCore, QtGui, QtWidgets
from get_music import get_urlclass 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.translateForm.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=1def btn_search(self):# print('点击搜索')self.page1 = 1pr_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 += 1linlks = 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 = 1def 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.textif 'ID3'in url_text: # 因为mp3文件的开头都是ID3music = requests.get(download_urls).contentif 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 requestsdef 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_encodinglinks = 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, QtWidgetsclass 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.translateForm.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 requestsdef 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_encodinglinks = 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 = 1pr_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 += 1linlks = 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.textif 'ID3'in url_text: # 因为mp3文件的开头都是ID3music = requests.get(download_urls).contentif 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 设置图标