Python+PyQt5開發(fā)一個Windows EXE程序在線更新工具
一、前言
在使用 PyQt5 + PyInstaller 開發(fā) Windows 桌面工具時,一個非?,F(xiàn)實的問題是:
程序如何自動檢測新版本,并完成在線升級?
本文基于一個真實可用、已落地的更新方案,實現(xiàn)了:
- 遠程版本號檢測
- 語義化版本對比
- 更新提示彈窗
- 子線程下載更新包
- 實時下載進度條
- ZIP 解壓
- EXE 自動替換
- 舊版本自動備份
- 更新完成后安全退出
非常適合 工具類 / 內(nèi)部系統(tǒng) / 單機 EXE 程序。
二、運行效果說明
整體更新流程如下:
- 點擊「獲取最新版本」
- 請求遠程
version.txt - 發(fā)現(xiàn)新版本 → 彈窗提示
- 用戶確認 → 下載
update.zip - 顯示下載進度條
- 解壓更新文件
- 備份舊 EXE
- 替換新 EXE
- 提示升級完成并退出程序
三、環(huán)境與依賴說明
Python 版本
Python 3.8+
pip 依賴安裝
pip install PyQt5 pip install requests pip install packaging
(如需打包 EXE)
pip install pyinstaller
四、服務(wù)器端準備
版本號文件(version.txt)
1.2.0
更新包(update.zip)
update.zip
├── update.exe
├── logo.png(可選)
五、完整代碼
SoftVesion.py
def current_version():
"""
Returns the version of the package
"""
return "1.1.0"
def latest_version():
"""
Returns the latest version of the package
"""
return "1.1.0"
def upgrade_date():
"""
Returns the time of the upgrade
"""
return "2024年1月19日"
def current_title():
"""
Returns the title of the package
"""
return "Excel常用小工具"
def remote_version_url():
"""
Returns the url of the latest version
"""
return "http://82.157.62.197:97/version.txt"
def update_url():
"""
Returns the url of the version info
"""
return "http://192.168.31.219:8080/update.zip"
UpdateApp.py
import os
import zipfile
import requests
from packaging import version
from PyQt5.QtCore import QThread, pyqtSignal, Qt
from PyQt5.QtWidgets import QApplication,QDialog, QVBoxLayout,QMainWindow, QPushButton, QMessageBox, QProgressDialog,QLabel,QAction,QDesktopWidget
import shutil
from .SoftVesion import current_version,latest_version,remote_version_url,update_url,current_title
from PyQt5.QtGui import QStandardItemModel, QStandardItem,QIcon
import sys
import time
import subprocess
class DownloadThread(QThread):
progressChanged = pyqtSignal(int)
downloadFinished = pyqtSignal()
unableToFetchUpdateLink = pyqtSignal()
def __init__(self, url, save_path):
super().__init__()
self.url = url
self.save_path = save_path
def run(self):
try:
response = requests.get(self.url, stream=True)
if response.status_code == 200:
total_size = int(response.headers.get('content-length', 0))
downloaded_size = 0
with open(self.save_path, 'wb') as file:
for data in response.iter_content(chunk_size=8192):
file.write(data)
downloaded_size += len(data)
progress = int((downloaded_size / total_size) * 100)
self.progressChanged.emit(progress)
self.downloadFinished.emit()
else:
print("Error fetching update link:")
self.unableToFetchUpdateLink.emit()
except Exception as e:
print("Error fetching update link:", e)
self.unableToFetchUpdateLink.emit()
class UpdateApp(QDialog):
def __init__(self,parent=None):
super().__init__(parent)
self.current_version = current_version() # 當(dāng)前應(yīng)用程序版本號
self.latest_version = latest_version()
self.progress_dialog = None
self.download_thread = None
self.remote_version_url= remote_version_url()
self.update_url = update_url()
self.init_ui()
#self.show_update_notification()
def move_to_center(self, widget):
# 將 widget 移動到屏幕中央
desktop_center = QDesktopWidget().availableGeometry().center()
widget_frame = widget.frameGeometry()
widget_frame.moveCenter(desktop_center)
widget.move(widget_frame.topLeft())
def init_ui(self):
self.setFixedSize(300, 200)
self.setWindowTitle("檢查更新")
icon = QIcon("icon.png")
self.setWindowIcon(icon)
self.version_label = QLabel("當(dāng)前版本號: " + self.current_version, self)
self.version_label.setAlignment(Qt.AlignCenter)
self.version_label.setGeometry(100, 10, 110, 20)
self.update_button = QPushButton("獲取最新版本", self)
self.update_button.setGeometry(100, 60, 110, 40)
self.update_button.clicked.connect(self.show_update_notification)
if self.download_thread:
self.download_thread.unableToFetchUpdateLink.connect(self.show_unable_to_fetch_update_link)
def show_unable_to_fetch_update_link(self):
QMessageBox.critical(self, "無法獲取更新鏈接", "無法獲取更新鏈接,請檢查網(wǎng)絡(luò)連接。")
def update_app(self):
try:
remote_version = self.get_remote_version()
if version.parse(remote_version) > version.parse(self.current_version):
if self.has_network_connection():
self.perform_upgrade()
else:
QMessageBox.information(self, "無法獲取版本號", "無法獲取遠程版本號,請檢查網(wǎng)絡(luò)連接。")
else:
QMessageBox.information(self, "無需升級", "當(dāng)前應(yīng)用程序已經(jīng)是最新版本。")
except:
QMessageBox.information(self, "無法獲取版本號", "無法獲取遠程版本號,請檢查網(wǎng)絡(luò)連接。")
def get_remote_version(self):
try:
response = requests.get(self.remote_version_url)
if response.status_code == 200:
return response.text.strip()
return self.latest_version
except:
return self.latest_version
def perform_upgrade(self):
update_url = self.update_url
temp_zip_path = "temp_update.zip"
self.download_thread = DownloadThread(update_url, temp_zip_path)
self.download_thread.progressChanged.connect(self.update_progress_bar)
self.download_thread.downloadFinished.connect(self.handle_download_finished)
self.progress_dialog = QProgressDialog("正在下載更新...", None, 0, 100, self)
self.progress_dialog.setWindowModality(Qt.WindowModal)
self.progress_dialog.setAutoClose(False)
self.progress_dialog.setAutoReset(False)
self.progress_dialog.setWindowTitle("下載進度")
self.move_to_center(self.progress_dialog)
self.progress_dialog.canceled.connect(self.download_thread.quit)
self.download_thread.start()
def update_progress_bar(self, progress):
self.progress_dialog.setValue(progress)
def handle_download_finished(self):
self.progress_dialog.hide()
if os.path.exists("temp_update.zip"):
self.process_download("temp_update.zip")
os.remove("temp_update.zip")
sys.exit()
def has_network_connection(self):
try:
requests.get(remote_version_url(), timeout=5)
return True
except:
return False
def show_update_notification(self):
try:
remote_version = self.get_remote_version()
if version.parse(remote_version) > version.parse(self.current_version):
reply = QMessageBox.question(
self, "版本更新提示",
f"有新版本可用:{remote_version}\n您當(dāng)前的版本:{self.current_version}\n是否要更新?",
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
self.update_app()
else:
QMessageBox.information(self, "版本更新提示", "您當(dāng)前的版本是最新版本。")
except:
pass
if __name__ == "__main__":
app = QApplication([])
window = UpdateApp()
window.show()
app.exec_()
六、關(guān)鍵設(shè)計說明
使用 QThread 防止界面卡死
使用 packaging.version 進行版本比較
ZIP 更新包支持擴展文件
EXE 覆蓋前自動備份
更新完成后安全退出
七、適用場景
PyQt5 工具軟件
內(nèi)部辦公系統(tǒng)
單機 EXE 程序
不依賴第三方更新框架
八、總結(jié)
本文提供的是一個 可直接落地、真實可用 的 PyQt5 自動更新方案,無需復(fù)雜服務(wù)器邏輯,維護成本極低。
到此這篇關(guān)于Python+PyQt5開發(fā)一個Windows EXE程序在線更新工具的文章就介紹到這了,更多相關(guān)Python EXE程序更新內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python OpenCV 彩色與灰度圖像的轉(zhuǎn)換實現(xiàn)
為了加快處理速度在圖像處理算法中,往往需要把彩色圖像轉(zhuǎn)換為灰度圖像,本文主要介紹了Python OpenCV 彩色與灰度圖像的轉(zhuǎn)換實現(xiàn),感興趣的可以了解一下2021-06-06
使用TensorFlow創(chuàng)建生成式對抗網(wǎng)絡(luò)GAN案例
這篇文章主要為大家介紹了使用TensorFlow創(chuàng)建生成式對抗網(wǎng)絡(luò)GAN案例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03
解決Pycharm雙擊圖標啟動不了的問題(JetBrains全家桶通用)
這篇文章主要介紹了Pycharm雙擊圖標啟動不了(JetBrains全家桶通用),本文給大家分享問題及解決方法,需要的朋友可以參考下2020-08-08

