Pyside2封装数据采集

Pyside2封装数据采集

Python图形界面开发

程序的用户交互界面我们称之为UI

目前python语言GUI有以下选择

  • Tkinter:官方标准库,稳定,程序小,控件较少
  • wxPython:基于wxWidgets的python库,控件丰富,文档少,用户少
  • Pyside2、PyQt5:基于Qt的python库,控件丰富,跨平台体验好,文档完善用户多,但程序较大。(是真的555
# 豆瓣源
pip install pyside2 -i https://pypi.douban.com/simple/

利用QtDesigner进行布局规python划

pip安装好pyside2包后到package文件夹下有Designer.exe,打开进行布局,如下是我的布局。这里要注意,一定要把所有控件布局在一起,这样控件才会随着软件的缩放而整体缩放。

具体使用方法这里参考官方文档:QApplication Class | Qt Widgets 5.15.5

中文版简化教程:Python Qt 简介 | 白月黑羽 (byhy.net)

对源程序进行重构

源程序目标网址类型较少,在这里更换为Wallhaven。

pyside2自定义类例程

from PySide2.QtWidgets import QApplication, QMessageBox
from PySide2.QtUiTools import QUiLoader

class Wallpaper:
    def __init__(self):
        self.wp.button
        self.wp.textEdit
        # 注意,刚刚设计的.ui保存至程序当前目录下
        self.wp = QUiLoader().load('main.ui')
        # xxxx是.ui中的button参数名称
        self.wp.xxxx.clicked.connect(self.handleCalc)

    def handleCalc(self):
		# button点击后运行函数
		
		
app = QApplication([])
stats = Stats()
stats.wp.show()
app.exec_()

重构爬虫函数进行类封装

class Wallpaper:

    def __init__(self):
    	# sort即是选择类型
    	self.sort = 0
    	self.signal_1 = 2
    	self.signal_2 = 0
    	self.sorts_name = ['动漫', '女生动漫', '明日方舟', '城市', '简约'
    , '科幻', '繁星', '英雄联盟', '太空', '航海王', '天空', '风景']
        self.ui = QUiLoader().load('wallpaper.ui')
        self.ui.button.clicked.connect(self.handleCalc)
        # 添加类型
        self.ui.cb.addItems(self.sorts_name)
		# 将类型选择框与信号槽连接     self.ui.cb.currentIndexChanged.connect(self.selectionchange)
		# 打印抓取进度
        self.ui.ms.text_print.connect(self.gui)

    def selectionchange(self, i):
    	# 获取当前类型选中序号i,0为起始  
        self.sort = i

    def handleCalc(self):
    	# 对每个网址的最大页数分别赋值
        page = [2763, 2678, 58, 315, 539, 418, 208, 151, 336, 38, 764, 1311]
		# 进行勾选框判断
        if self.ui.check.isChecked():
        # 停止或开始信号标记
            self.signal_1 += 1
            # 满足开始信号
            if self.signal_1 % 2 == 1:
            # 更改button名称
                self.ui.button.setText('停止')
                # 列表元素均为网址,在此插入,略
                sorts = [anime, anime_girl, ark_nights, cityscape, minimalism, science, stars, legends, space, one_peace, sky, landscape]
                base_url = sorts[self.sort]
                # 对文件夹名称、路径分类化
                fold_name = self.sorts_name[self.sort]
                fold_path = self.sorts_name[self.sort] + '\\'

				# <insert>
				
            if self.signal_1 % 2 == 0:
                self.ui.button.setText('开始')
        else:
        	# 勾选框未勾选提示
            msgBox1 = QMessageBox()
            msgBox1.setWindowTitle("( ゜-゜)つロ")
            msgBox1.setText('记得勾选"我已阅读《使用手册》"哦~')
            msgBox1.setStandardButtons(QMessageBox.Ok)
            msgBox1.setDefaultButton(QMessageBox.Ok)
            ret = msgBox1.exec_()

    def gui(self, text):
    	# 打印抓取进度
        self.ui.infoBox.append(str(text))
        # 让infoBox刷新至最新一条
        self.ui.infoBox.ensureCursorVisible()
        
app = QApplication(sys.argv)
# 可以更换自己的logo,保存在当前目录下
app.setWindowIcon(QIcon('logo.png'))
wallpaper_catch = Wallpaper()
wallpaper_catch.wp.show()
# 当最后一个线程结束时,程序关闭
sys.exit(app.exec_())

requests向服务器请求需要时间,爬虫打印进程又直接对主程序进行渲染,其中最末尾的app.exec_()就是让主线程,不断循环处理用户操作的事件。要解决这个问题,就要让爬虫在子线程里运行,注意不能直接对主窗口进行渲染操作

python既支持多进程,也支持多线程。运行在操作系统中的每一个进程都可以拥有多个线程,每个进程具有自己的内存。即便如此,网页抓取不能仅仅依靠提高进程的数量,而应该从代码优化的角度考虑问题。

from threading import Thread # 引入threading模块

同时,我们要让子线程在打印时发出信号给主线程(推荐所有图片渲染的操作都在主线程中进行,避免出现程序无响应的问题)

因此我们创建Catch_Signal类,继承自QObject类:

class Catch_Signals(QObject):
    procedure = Signal(str)

在Wallpaper类中定义实例属性:

self.wp.ms = Catch_Signals()
self.wp.ms.procedure.connect(self.gui)

再在Wallpaper类中定义GUI绘制函数:

def gui(self, text):
    self.wp.infoBox.append(str(text))
    self.wp.infoBox.ensureCursorVisible()

将爬虫程序放入子线程:

# 插入<insert>处
            thread = Thread(target=self.threadSend,
                 args=(base_url, fold_name, fold_path))
            thread.start()
# 定义子线程函数            
def threadSend(self, base_url, fold_name, fold_path):
    try:
        # 爬虫函数略
    except requests.URLRequired as e:
        # 略

补充import:

from PySide2.QtWidgets import QApplication, QMessageBox
from PySide2.QtUiTools import QUiLoader
from PySide2.QtGui import QIcon
from PySide2.QtCore import Signal, QObject

对源程序继续完善

Sleep延迟

我们在for循环里加入sleep函数

引入库:

import time
from random import randint

for循环下载img最后一行加入:

time.sleep(sleep_time / 100)

当然,子进程try:第一行加入:

sleep_time = randint(1, 201)

保证在每次下载间隔1~2s

sys.exit(app.exec_())

sys.exit(app.exec_())这行代码的意思是,当最后一个线程结束时,退出命令行(即总程序)。但是一旦子线程开始便无法通过这行语句停下来,尝试用signal进行判断限制,无果;尝试直接将其放入子线程,无果;尝试寻找退出主程序弹窗函数,无果…

注意!这里是个坑,当ui界面是动态加载时,重写closeEvent函数是无效的!

也就是说,当需要重写closeEvent函数时,必须要静态加载ui文件,在同目录cmd命令行下运行以下命令

pyside2-uic xxxxx.ui -> xxxxx.py

然后在主函数中调用该py文件即相当于调用ui文件

from PySide2.QtGui import QIcon
# 根目录下保存名为'logo.png'的logo
app.setWindowIcon(QIcon('logo.png'))

当然这时Qt窗口的logo,APP的logo会在打包时添加

附属文件插入

打包完成后别着急运行,好些个附属文件还没有转移,例如logo.png文件是没有被打包进去的,因此需要我们手动粘贴到相应目录下。

这时运行可执行文件即可看到我们的程序。

成果图

程序框架

快期末了,没错,这就是我的期末作业。

因为考试原因,计划用一周的时间来写,写到哪里算哪里嘛 😝 一边上课、一边复习、一边利用课余时间开发,这也就是我一周的成果吧。

本来打算做一个爬虫工具集合软件,做到一半又想利用采集到的数据进行可视化展示或者数据分析,还想自动生成页面部署到服务器上…没错,就是这个原因,导致重构了好几次代码 😢 所以,设计框架之前一定要想好需求!一口气吃不成一个胖子,慢慢积累吧~

主要步骤实现

设计UI(考虑主要功能以及交互方式)

因为以前没有构建大型程序框架的经历,这个程序也算是我的第一个“大型程序”啦 😆 记得听老师说过一句话:“从上而下构建,从下而上实现。”这句话一直指引着我去优化封装、去重构代码。我也确实体会到了一个好的程序框架的重要性。

前台界面怎能不炫酷?!

没错,我找到了Qt Design Studio - UI Design Tool for Applications,但是…

太劝退了吧这…

再想一想,python的话,应该对GUI或者前台没有太大的需求,投入过多精力似乎没有太大必要,毕竟就看小程序来讲,Qt的designer已经足够了。于是,我又开始用designer设计界面,然后保存ui文件调用,等等!就是这里,一定要强调下pyside2使用ui文件的两种方法:

1.利用pyside2-uic工具把ui文件转化为python类

命令行输入以下代码

pyside2-uic mainwindow.ui > ui_mainwindow.py

创建类时调用以下代码即可

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

2.动态加载

# 导入QtUiTools模块
from PySide2.QtUiTools import QUiLoader

QUiLoader可以动态加载ui文件并立即使用
ui_file = QFile("mainwindow.ui")
ui_file.open(QFile.ReadOnly)

loader = QUiLoader()
window = loader.load(ui_file)
window.show()

3.补充:pyqt5中使用方法

# 使用pyuic5转换到py文件
# 使用pyrcc5转化资源文件
# 动态加载
from PyQt5.uic import loadUi
...
loadUi("widget.ui",self)
...

第一次我使用了“动态加载”,这里会有一些bug。例如我们有设置这个程序的主题title,但这里的主题名称却是“Mfgtool”(默认的主题名)。还比如不会触发QMainWindow窗口的事件等等。当然要能接收窗口事件,需要重写对应的事件接口,而且这样重写出来的效果也不是最佳,所以这里不作详细说明了。具体例子:python - PySide2 QMainWindow loaded from ui file not triggering window events - Stack Overflow

所以,采用静态方式加载,这样事件也可以覆盖,非常类似于直接使用Qt IDE开发工具一样方便。

整理子爬虫程序(找到相同点和不同点)

保证子程序可以正常运行的情况下,整合到主程序里。

可以找到不同程序的共同点,例如requests请求,可以封装为get请求,保存数据也可以封装到save函数里。针对不同网址放入不同的类型里面即可,既利于对程序的修改,有利于整理思路脉络,还能减少bug发生的概率。

对不同窗口进行整合
from PySide2.QtWidgets import QApplication, QWidget
#引入主窗口
from mainUI import Ui_mainUI
#引入子窗口
from childUI import Ui_childUI
 
#创建主窗口类
class MainWindow(QWidget):
    def __init__(self):
        super(MainWindow, self).__init__()
        //引入主窗口类
        self.ui = Ui_mainUI()
        self.ui.setupUi(self)
 
        #创建按钮点击信号连接 其中button是主窗口中的按钮,在主窗口类文件中添加,这里不详细说明
        self.ui.button.clicked.connect(self.childShowFun)
    #创建子窗口的方法,即槽函数
    def childShowFun(self):
        #注意,这里的childwindow不能定义成临时变量,必须定义成主窗口类MainWindow的成员变量,'
        '如果是临时变量,即前面没有self,那么子窗口只会闪一下,就会消失
        self.childwindow = ChildWindow()
        self.childwindow .show()
 
 
#创建子窗口类
class ChildWindow(QWidget):
    def __init__(self):
        super(ChildWindow, self).__init__()
        #引入子窗口类
        self.ui = Ui_childUI()
        self.ui.setupUi(self)
 
 
if __name__ == "__main__":
 
    app = QApplication([])
    mainwindow = MainWindow()
    mainwindow.show()
    app.exec_()

这是一个子窗口模板,这里也要用静态加载。

⚠注意:如果有多个子窗口,要注意不同模块的函数名是否相同,不然会出现不同槽函数指向同一窗口的现象。我试图通过改文件名,改属性,最后发现进入ui生成的静态py文件更改函数名称是最佳解决方式。

填充小功能

这一阶段主要是查看官方文档,找到相关的函数,之后链接自定义一些相关的槽函数即可。

一些问题与改进

logo的icon文件以及png文件无法打包好还需qrc资源嵌入,也就是涉及到图片的储存与调用,后期修bug和添加小功能时,也许会增加进去吧。

相关资源

程序源码请前往Alexation/Spider_Collection: Pyside2封装爬虫 (github.com)

打包程序请前往Alexation.zip - 蓝奏云 (lanzoui.com)