前言:本记录为笔者自学过程中的简单笔记,说明了从安装库到最终打包成 .exe 的过程,分为三个部分,供初学者参考交流,存在表述有误的部分还请读者们多多指教,下方为第二部分目录。
文章目录
一、Qt控件功能的实现:从界面获取参数
1、按钮pushButton和文本框lineEdit
重于到重头戏了(发出了萌新的叫声),那么接下来就基于上半部分的控件讲一讲如何从界面获取用户输入的各类参数;
从上左至下右,第一个是更新 Cookie 按钮,旁边是用于接收参数的 lineEdit 控件,可写入单行文本(TextEdit为多行),在 Qt Designer 界面左边找到拖出来,放在 Widegt 上;
Cookie 是笔者所用爬虫所需参数之一,每天都需要更新(未做模拟登录,直接在浏览器里F12然后找到复制过来,爬虫相关知识在此不赘述),但一般同一天内无需多次更新。如此,需要实现储存当日Cookie 的功能;
让我们在 getXlsxForm 类(详见上一篇第三节部分:爬取基本页功能类)中添加成员方法reCookie()
,首先用text()
方法接收输入的文本,因 Cookie 为必要参数所以需验证是否输入(未限定输入有效性),若输入了则把Cookie存入到本地.txt文件,若未输入则从该文件中载入历史记录,注意:路径 .\\GUI_Call
表示当前 .py 文件根目录下的 GUI_Call 文件夹;
def reCookie(self): # 更新Cookie---------------------------------【1】
inputCookie = self.lineEdit_RenewCookie.text()
if inputCookie == '':
QMessageBox.about(self,"提示:请确认是否更新!","未输入Cookie,已载入cookie.txt中的历史记录")
f = open(".\\GUI_Call\\cookie.txt", "r")
inputCookie = f.readlines()[0]
self.lineEdit_RenewCookie.setText(inputCookie)
f.close() # 注意:文件打开后要关闭,避免占用内存资源
else:
with open(".\\GUI_Call\\cookie.txt", "w", encoding='utf-8') as f:
f.write(inputCookie)
# 使用with可省去close()
QMessageBox.about(self,"提示","已更新Cookie至cookie.txt")
笔者将其他参数的接收统一写入运行爬虫按钮,添加runCrawler()
,同样用text()
方法接收用户输入,在爬取之前获取的数据有些要经过处理,于是另外还写了个setParameter()
,其处理内容在此不赘述。再次验证Cookie是否存在,验证后传参调用爬虫,代码如下:
def runCrawler(self): # 点击运行爬虫按钮,爬取基本页信息-----------------【2】
Cookie = self.lineEdit_RenewCookie.text()
sheetname = self.lineEdit__SheetName.text() # 输出的表名
typeIndex = self.comboBox_infortype.currentIndex() # 类型选择下拉框
maxpage = self.lineEdit_maxpage.text() # 最大页码
paraList = self.setParameter(typeIndex,maxpage) # 处理类型、历史类型、最大页码
begintime = self.lineEdit_begtime.text() # 开始日期
endtime = self.lineEdit_endtime.text() # 结束日期
brist = str(self.lineEdit_brist.text()) # 内容搜索
accregpername = self.lineEdit_register.text() # 作者
if Cookie!= '':
# print("此处为传参调用爬虫程序中的方法MyCrawler.get_xlsx()")
else:
QMessageBox.about(self,"提示","请注意:未更新Cookie")
2、下拉框comboBox和单选按钮RadioButton
说明一下 comboBox 控件,即下拉框。在 Qt Designer 中拖至界面后双击,增加需要的下拉选项即可,通过currentIndex()
方法获取其当前选项序号(从0开始);
以及它旁边的 RadioButton 控件,即单选按钮。拖出来两个,笔者对1个月内选项的按钮命名为radioButton_0,通过isChecked()
方法确认其是否被点击;
if self.radioButton_0.isChecked() == True: # 点击1个月内,datatype设为0
datatype = '0'
else:
datatype = '1' # 否则datatype设为1,即点击历史
小结:至此,基本的界面构建和参数传递方式就说明完毕,后续再做进一步的优化。本部分的关键点为:1、lineEdit.text(),2、comboBox. currentIndex(),3、RadioButton. isChecked()。
二、界面优化:日历控件及按钮图标、内容提示等
1、日历CalendarWidget
(再看一眼界面,不然忘了有啥玩意儿额)前文提到更新 Cookie 按钮的作用是便于暂存和载入 Cookie ,那么日期是否也可以更便利地获取呢?
因为笔者所用爬虫所需的日期参数格式为”yyyy-mm-dd“,可以导入time模块使用time.strftime("%Y-%m-%d")
来按格式获取当天日期,在getXlsxForm类的__init__
方法中添加代码即可,通过setText()
方法来设置日期文本框的默认值;
class getXlsxForm(QWidget,getXlsxWidget.Ui_Form):
def __init__(self):
super(getXlsxForm, self).__init__()
self.setupUi(self)
# 设置默认日期为当日
self.lineEdit_begtime.setText(time.strftime("%Y-%m-%d"))
self.lineEdit_endtime.setText(time.strftime("%Y-%m-%d"))
但通常在使用爬虫执行查询时,日期段并非是当日,还需手动修改日期,为了避免繁琐地更改输入造成的麻烦,Qt 提供的 CalendarWidget 控件可以满足傻瓜式点击选择的需求。注意:下面这个 dateEdit 控件的图标看起来也像日历那么回事儿,可以自己试试是啥子哎;
我们要用的是 CalendarWidget ,将其放置到合适位置,比如在日期文本框旁边的按钮下方,便于呈现点击按钮后弹出日历的效果,如下图:
同样在getXlsxForm类的__init__
方法中添加代码,将日历面板先设为隐藏,并且将日期文本框设为只读,无需手动输入更改。之后依照常规流程“某按钮.被点击.连接执行(某方法)”绑定按钮被点击时执行的方法,显示出隐藏的日历面板,与日历被点击选择后设置日期,日历面板被点击返回 QDate;
self.date_start.setHidden(True) # 隐藏日期面板
self.date_end.setHidden(True)
self.lineEdit_begtime.setReadOnly(True) # 日期框只读
self.lineEdit_endtime.setReadOnly(True)
self.pB_date_start.clicked.connect(self.showDate1) #显示日历面板
self.pB_date_end.clicked.connect(self.showDate2)
self.date_start.clicked[QDate].connect(self.setDate1) # 设置所选日期
self.date_end.clicked[QDate].connect(self.setDate2)
添加自定义的showDate()
方法,先使用isHidden()
方法判断是否为隐藏,当前为隐藏,则点击按钮后用setVisible()
方法显示,当前为显示,则点击按钮后隐藏;
def showDate1(self): # 显示日历面板--------------------------------【5】
if self.date_start.isHidden():
self.date_start.setVisible(True)
else:
self.date_start.setHidden(True)
def showDate2(self):
if self.date_end.isHidden():
self.date_end.setVisible(True)
else:
self.date_end.setHidden(True)
添加setDate()
方法,通过selectedDate()
方法得到日历面板被选择的日期,并用toString()
方法按格式返回字符串,用setText()
方法设置日期文本框的显示值,选择完成后隐藏日历面板。同时需进行验证,开始日期不可晚于结束日期,日期字符串的格式为“yyyy-MM-dd“,故用replace('-','')
去掉横杠来得到纯数字,再转为int类型以比较大小,如 20200101<20201231;
(这样转换比较好像麻烦了点哈哈,还请大佬指点一下更优解ლ(°◕‵ƹ′◕ლ))
def setDate1(self): # 设置所选日期----------------------------------【6】
date = self.date_start.selectedDate().toString("yyyy-MM-dd")
if int(date.replace('-','')) > int(self.lineEdit_endtime.text().replace('-','')):
QMessageBox.warning(self,"警告","开始日期不可晚于结束日期!")
else:
self.lineEdit_begtime.setText(date)
self.date_start.setHidden(True)
def setDate2(self): #
date = self.date_end.selectedDate().toString("yyyy-MM-dd")
if int(date.replace('-','')) < int(self.lineEdit_begtime.text().replace('-','')):
QMessageBox.warning(self,"警告","结束日期不可早于开始日期!")
else:
self.lineEdit_endtime.setText(date)
self.date_end.setHidden(True)
如此一来,日期的获取操作看起来就更高端啦,那么接下来咱再瞅瞅还能再捣鼓点啥。
比如设置一下按钮的图标,不要只是干巴巴的文字,转为光标悬停时出现文字提示,那么就拿一直都空着的下半部分来开刀吧!(这句有点凉凉的…)
2、按钮图标和内容提示
通过setStyleSheet()
方法来设置按钮样式,详细可搜索 Qt 样式表相关内容,接触过前端的同学处理起来应该很快,和 css 差不多。通过setToolTip()
方法来实现文本提示。另外,除了可在样式表中设置按钮背景图片,也可以通过setIcon()
方法设置图标;
#设置按钮样式和提示
self.pB_runCrawler.setStyleSheet("QPushButton{border-image:url(./QtFile/images/run.png)}"
"QPushButton:hover {border:2px solid black}"
"QPushButton {border:none}")
self.pB_runCrawler.setToolTip("爬取基本页")
self.pB_stop.setStyleSheet("QPushButton{border-image:url(./QtFile/images/stop.png)}"
"QPushButton:hover {border:2px solid black}"
"QPushButton {border:none}")
# self.pB_stop.setIcon(QIcon(".\\QtFile\\images\\stop.png"))
self.pB_stop.setToolTip("停止程序")
效果大概是下图这样,挺丑;
还可以用setClearButtonEnabled()
方法来为文本框添加清空功能,如下图,点击 X 即可;
代码:
# lineEdit内容删除提示
self.lineEdit_RenewCookie.setClearButtonEnabled(True)
self.lineEdit__SheetName.setClearButtonEnabled(True)
self.lineEdit_brist.setClearButtonEnabled(True)
self.lineEdit_register.setClearButtonEnabled(True)
还记得runCrawler()
方法中的这个部分吗?下一篇对将爬虫反馈信息逐条更新至多行文本控件 TextEdit 进行说明。
if Cookie!= '':
# print("此处为传参调用爬虫程序中的方法MyCrawler.get_xlsx()")
else:
QMessageBox.about(self,"提示","请注意:未更新Cookie")
小结:CalendarWidget.selectedDate().toString(“yyyy-MM-dd”)
(突然惜字如金啊…喂!)
三、界面刷新:加载爬虫反馈信息
本节(可以不看,实为笔者牢骚)说明如何实现在界面中加载爬虫的反馈,效果就如同常见的软件安装时的详细说明一样,在一个多行文本框里逐条加载进度信息;
嗯;
嗯…emmmmmm;
咳咳,真的不是恶意凑篇幅,怎么办才好呢,老实说这一部分也费了老大功夫(大佬一眨眼,学渣苦半晌),一开始还想着要动用 Qt 真正的杀手锏:自定义信号和槽,并且结合多线程。但是为了快速实现需求,能不花时间学新东西就不学(等等等等等!误人子弟的话慎言啊!)
开始简单地方法:先用clear()
方法在每次运行爬虫前清空上一次的记录,在for循环中执行爬虫,MyCrawler.getBaseData()为笔者所用的爬虫程序,return一个被爬取服务端的响应状态码,爬虫相关知识不展开说明。通过append()
方法将相应的信息添加到多行文本框中,即上方的灰色区域;
if Cookie!= '':
self.textEdit_CheckInfo.clear()
for i in range(1, paraList[2] + 1):
i = str(i)
status_code = MyCrawler.getBaseData(爬虫参数省略……)
info = " >>> 已爬取第%s页" % i + "(状态码:%s)" %status_code +"\n"
self.textEdit_CheckInfo.append(info)
QApplication.processEvents()
time.sleep(0.1)
self.textEdit_CheckInfo.append("爬取完成!")
else:
QMessageBox.about(self,"提示","请注意:未更新Cookie")
那么问题来了,由于爬虫运行时界面阻塞,即在爬虫全流程结束前界面都是卡死的,无法逐条更新信息,会导致在爬虫结束后一次性打印全部信息。而笔者懒得学多线程不对不对、故而笔者另辟蹊径,发现了QApplication.processEvents()
这个方法,作用是刷新界面信息,最终实现了目标效果;
不过仍然存在问题,虽然看上去是一行一行的打印出来,但是在这个过程中界面本身依然是不可点击操作的,应使用多线程来解决。
四、后记
虽说总标题为“PyQt5初学试验记录”,但笔者在此之前已接触过 Qt,Qt 是基于 C++ 实现的用于编写 GUI 程序的库,功能强大,比其他实现相似功能的库更易上手。当初是在做毕业设计的时候学习了 Qt,同样是写了一个简单的界面,通过阅读霍亚飞老师写的《Qt Creator快速入门》来进行,其中对Qt特性的讲述十分详尽,在此向有兴趣的同学推荐。另外,关于 C++ 的学习,有需要的同学在B站搜索“清华大学C++”即可。
在学习 Python 中的图形界面时,Qt 又帮了大忙,而这次学习 PyQt5 的资料大部分源于 CSDN,因此笔者便想以博文的方式记录一番。有机会的话笔者会再回顾整理一下 C++ 中的 Qt,以便和我一样的初学者能比较快地了解和使用它。另外,本次制作的界面工具还有待改善,比如多线程方面,有新发现时笔者会继续补充。
感谢各位的阅读,谢谢!
下一部分将继续说明如何用 pyinstaller 打包成 .exe 文件。