• 【实操日记】使用 PyQt5 设计下载远程服务器日志文件程序


    最近通过 PyQt5 设计了一个下载服务器指定日期日志文件的程序,里面有些有意思的技术点,现在做一些分享。

    PyQt5 是一套 Python 绑定 Digia Qt5 应用的框架,是最强大的 GUI 库之一,使用 PyQt5 我们能够很容易的开发桌面应用,接下来我们将用它来开发一个下载服务器日志文件的小程序。

    前期准备

    软件

    • QT5
      Python 模块
    • PyQt5==5.15.7
    • paramiko==2.9.2
      PyCharm 添加扩展工具 PyUIC
      PyUIC 扩展用于将使用 Qt Designer 生成的 ui 文件转成 py 文件,可以在 PyCharm 中通过 Preferences-Tools-External Tools 进行配置,截图如下:

    Program:/Users/macbookpro/workspace/projects/DownloadServerLog/venv/bin/python3.9
    Arguments:-m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py
    Working directory:/Users/macbookpro/workspace/projects/DownloadServerLog/ui
    
    • 1
    • 2
    • 3

    实操步骤

    1. 创建项目

    创建 DownloadServerLog 项目,设计程序结构如下:

    DownloadServerLog
    ├── app
    │   ├── downloadlog.py
    │   └── downloadlog_qtui.py
    ├── main.py
    └── ui
    │   └── downloadlog_qtui.ui
    ├── .env
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    main.py 作为程序入口文件,.env 存放环境变量,ui 存放使用 Qt Designer 设计界面导出的源码文件,app 存放下载程序文件。

    2.使用 QtDesigner 设计界面

    Qt Designer 使用起来非常简单,可以通过“拖拉拽”的形式生成 UI 界面(文档:https://doc.qt.io/qtcreator/creator-using-qt-designer.html),设计界面如下:

    这个程序功能一目了然,左侧几个输入框用于输入必要的信息,右侧一个展示框用于展示程序实时日志。界面设计好后可以将其保存至项目 DownloadServerLog 下的 ui 目录下 downloadlog_qtui.ui,供后续使用。

    3. 使用 ui 生成对应的 py 文件

    使用 PyCharm 打开项目,在 downloadlog_qtui.ui 文件上右键,选择 External Tools 使用 PyUIC 根据 ui 文件生成对应的 py 文件 downloadlog_qtui.py,将文件存放至 app 目录。

    4. 新建 main.py 作为程序入口

    在项目根目录下创建 main.py 文件:

    import sys
    
    from PyQt5 import QtCore
    from PyQt5.QtCore import QObject, pyqtSignal
    from PyQt5.QtWidgets import QApplication, QMainWindow
    from threading import Thread
    
    from app.downloadlog_qtui import Ui_Dialog
    from app.downloadlog import DownloadLog
    
    
    class CommunicateSignal(QObject):
        text_print = pyqtSignal(str)
    
    
    # MyWindow 是主窗口程序,继承自 PyQt5.QtWidgets.QMainWindow
    # 和通过 ui 文件生成的 downloadlog_qtui.py 中的 Ui_Dialog 类
    class MyWindow(QMainWindow, Ui_Dialog):
        def __init__(self, parent=None):
            super().__init__(parent)
            self.setupUi(self)
            self.btn_download.clicked.connect(self.click_download)
    
            # 自定义信号处理函数
            self.comm_signal = CommunicateSignal()
            self.comm_signal.text_print.connect(self.show_text)
    
            self.set_window_init_data()
    
        def set_window_init_data(self):
            """设置程序窗体初始值"""
            # 从 .env 读取环境变量
            result_dict = dict()
            with open('.env', 'r', encoding='utf-8') as f:
                for line in f.readlines():
                    key = line.split('=')[0].strip()
                    value = line.split('=')[-1].strip()
                    result_dict[key] = value
    
            # 设置输入框值
            _translate = QtCore.QCoreApplication.translate
            self.host.setText(_translate("Dialog", result_dict.get("HOST", '')))
            self.port.setText(_translate("Dialog", result_dict.get("PORT", '22')))
            self.username.setText(_translate("Dialog", result_dict.get("USERNAME", 'root')))
            self.password.setText(_translate("Dialog", result_dict.get("PASSWORD", '')))
            self.directory.setPlainText(_translate("Dialog", result_dict.get("DIRECTORY", '')))
            self.startTime.setDate(QtCore.QDate.currentDate())
            self.endTime.setDate(QtCore.QDate.currentDate())
    
    
        def get_window_input_value(self):
            """获取程序各「输入框」组件值"""
            return {
                "host": self.host.text(),
                "port": self.port.text(),
                "username": self.username.text(),
                "password": self.password.text(),
                "directory": self.directory.toPlainText(),
                "start_time": self.startTime.date().toString("yyyy-MM-dd"),
                "end_time": self.endTime.date().toString("yyyy-MM-dd"),
                "suffix": ".log",
            }
    
        def show_text(self, text):
            """将文本内容追加到程序「展示框」"""
            self.textBrowser.append(text)
    
        def click_download(self):
            """处理点击「下载」按钮事件"""
            params = self.get_window_input_value()
    
            def run():
                res = DownloadLog(conn_type='ssh', comm_signal=self.comm_signal, **params)
                res.main()
    
            t = Thread(target=run)
            t.start()
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        myWin = MyWindow()
        myWin.show()
        sys.exit(app.exec_())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84

    MyWindow 作为主窗口程序,程序初始化时会将 self.click_download 方法注册到 下载 按钮的点击事件,并自动调用 self.set_window_init_data 方法来设置输入框初始值。

    5. 下载

    下载日志程序 DownloadLog 定义在 app/downloadlog.py 中,远程下载文件主要步骤有两步:

    • 通过 SSH 登录远程服务器

    • 通过 FTP 进行文件下载

    这里采用 paramiko 来实现远程下载功能,paramiko 是一个纯 Python 库,它实现了 SSHv2 协议,提供了 SSH 和 FTP 的能力。

    核心代码如下,读者可以根据自己的需求实现 DownloadLog:

    class DownloadLog(object):
        def __init__(self, **kwargs):
            """初始化一些参数"""
            ...
    
        def main(self):
            # 获取 Transport 实例
            tran = paramiko.Transport((self.host, int(self.port)))
            # 连接 SSH 服务端
            tran.connect(username=self.username, password=self.password)
            # 创建 SFTP 实例
            self.sftp = paramiko.SFTPClient.from_transport(tran)
            # 下载文件
            # :param str remotepath: the remote file to copy
            # :param str localpath: the destination path on the local host
            self.sftp.get(remotepath=self.remote_path, localpath=self.local_path)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    6. 展示下载过程

    为了将下载程序执行步骤实时展示到输出框,这里需要引入 PyQt5 的信号处理机制。

    由于 PyQt 建议只在主线程中操作界面,可以发现我们在 main.py 中调用 DownloadLog.main 方法时创建了一个新的线程。

    所有的 GUI 程序都是事件驱动的,事件可能由用户触发,比如点击 下载 按钮事件,也可能由程序触发,比如我们现在要实现的展示下载过程的功能,就需要使用程序主动触发事件。

    在 PyQt5 中通过 Signal 信号来处理事件,其基本使用步骤如下:

    自定义一个 CommunicateSignal 类,继承自 PyQt5 的 QObject 类,里面封装自定义的 Signal 信号(Signal 实例对象的初始化参数指定的类型,就是发出信号对象时,传递的参数数据类型。因为 PyQt5 底层是 C++ 开发的,必须指定类型)。

    class CommunicateSignal(QObject):
        text_print = pyqtSignal(str)
    
    • 1
    • 2

    定义主线程执行的函数处理 Signal 信号(通过 connect 方法绑定)。

    # 自定义信号处理函数
    self.comm_signal = CommunicateSignal()
    self.comm_signal.text_print.connect(self.show_text)
    
    • 1
    • 2
    • 3

    在 DownloadLog 线程需要操作界面的时候,就通过自定义对象(CommunicateSignal)发出信号(使用 emit 方法发出信号),所以在实例化 DownloadLog 时会将 comm_signal 传递进去。

    # 通过该信号对象的 emit 方法发出信号,emit 方法的参数传递必要的数据。
    # 参数类型遵循定义 Signal 时指定的类型。
    self.comm_signal.text_print.emit(text)
    
    • 1
    • 2
    • 3

    主线程信号处理函数,被触发执行,获取 Signal 里面的参数,执行必要的更新界面操作,这里将每次通过事件传过来的文本内容展示到输出框内。

    def show_text(self, text):
        """将文本内容追加到程序「展示框」"""
        self.textBrowser.append(text)
    
    • 1
    • 2
    • 3

    7. 效果展示

    通过以上步骤我们完成的程序设计,现在可以验证下这个下载日志文件的小程序了:

    查看下载结果:

    总结

    我们通过 PyQt5 实现了一个下载远程服务器日志文件的小程序,其实它不止可以用来下载日志,同样可以用来下载其他文件。
    借助 PyQt5 强大的能力,我们可以通过“拖拉拽”的形式很容易地实现桌面端程序,只需要将原来的 Python 脚本绑定到 UI 程序的事件中,就实现了命令行程序到桌面程序的演进。
    接下来你可以根据自己的需求来定制自己的桌面小程序啦~

    资料参考:

    https://download.qt.io/archive/qt/5.14/5.14.2/

    https://doc.qt.io/qtcreator/creator-using-qt-designer.html

    https://docs.paramiko.org/en/stable/

  • 相关阅读:
    pytorch TORCH.NN 到底是什么?
    北邮21硕后端开发笔记
    Vue2源码学习笔记 - 11.响应式原理—Observer 类详解
    APP使用相机CameraX
    【嵌入式Linux应用开发】设计温湿度采集MCU子系统
    Redis各数据类型特定的命令和用法 1.0版本
    Unity3D学习笔记12——渲染纹理
    java毕业生设计非处方药物的查询与推荐系统计算机源码+系统+mysql+调试部署+lw
    Python爬虫:让“蜘蛛”帮我们工作
    【23真题】难!985难度前五名!
  • 原文地址:https://blog.csdn.net/github_36774378/article/details/127806232