You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为基于customtkinter的Python GUI应用实现GitHub自动更新功能

Python GUI应用基于GitHub Releases的自动更新方案(适配CustomTkinter)

针对托管在GitHub Releases的CustomTkinter GUI应用,这里提供一套简洁的Python风格自动更新实现,支持点击按钮触发检测、下载和安装流程,分源码分发版和打包分发版两种场景。

核心步骤概览

  1. 调用GitHub API检测最新版本
  2. 对比本地版本与最新版本
  3. 下载更新资源
  4. 替换应用文件(或通过辅助脚本完成打包版更新)

1. 版本检测实现

使用GitHub REST API获取最新Release的版本号,结合packaging库做版本号对比:

import requests
import packaging.version
import sys

# 配置项,根据你的仓库修改
CURRENT_VERSION = "1.0.0"
REPO_OWNER = "your-github-username"
REPO_NAME = "your-repo-name"

def check_for_update():
    """检查是否有可用更新,返回最新Release数据和版本号"""
    try:
        # 调用GitHub最新Release API
        resp = requests.get(f"https://api.github.com/repos/{REPO_OWNER}/{REPO_NAME}/releases/latest")
        resp.raise_for_status()
        release_data = resp.json()
        
        # 处理版本号(假设tag为vX.X.X格式,去掉前缀v)
        latest_version = release_data["tag_name"].lstrip("v")
        
        # 版本号比较
        if packaging.version.parse(latest_version) > packaging.version.parse(CURRENT_VERSION):
            return release_data, latest_version
        return None, None
    except Exception as e:
        print(f"更新检测失败: {str(e)}")
        return None, None

2. 下载更新资源

根据分发类型下载对应的更新包:

源码版(直接下载源码Zip)

import zipfile
import os
from pathlib import Path

def download_source_update(release_data, temp_dir="./update_temp"):
    """下载并解压源码更新包,返回解压后的源码目录"""
    os.makedirs(temp_dir, exist_ok=True)
    zip_path = Path(temp_dir) / "update.zip"
    
    # 下载源码Zip包
    with requests.get(release_data["zipball_url"], stream=True) as r:
        r.raise_for_status()
        with open(zip_path, "wb") as f:
            for chunk in r.iter_content(chunk_size=8192):
                f.write(chunk)
    
    # 解压Zip
    with zipfile.ZipFile(zip_path, "r") as zip_ref:
        zip_ref.extractall(temp_dir)
    
    # 返回解压后的根目录(GitHub Zip包会带仓库名+commit前缀)
    return Path(temp_dir) / next(os.scandir(temp_dir)).name

打包版(下载对应平台的可执行文件)

def download_packaged_update(release_data, target_platform="windows", temp_dir="./update_temp"):
    """下载打包后的可执行文件(如exe/dmg),返回文件路径"""
    os.makedirs(temp_dir, exist_ok=True)
    # 匹配对应平台的Asset(需要你在Release中按规范命名,比如app-win.exe)
    asset_url = None
    asset_name = None
    for asset in release_data["assets"]:
        if target_platform in asset["name"].lower():
            asset_url = asset["browser_download_url"]
            asset_name = asset["name"]
            break
    
    if not asset_url:
        raise ValueError(f"未找到{target_platform}平台的更新包")
    
    asset_path = Path(temp_dir) / asset_name
    with requests.get(asset_url, stream=True) as r:
        r.raise_for_status()
        with open(asset_path, "wb") as f:
            for chunk in r.iter_content(chunk_size=8192):
                f.write(chunk)
    return asset_path

3. 应用更新逻辑

源码版:直接替换文件

import shutil

def apply_source_update(update_dir, app_root="./"):
    """将更新源码复制到应用目录"""
    app_root = Path(app_root)
    # 递归复制所有文件
    for src_path in update_dir.rglob("*"):
        rel_path = src_path.relative_to(update_dir)
        dest_path = app_root / rel_path
        if src_path.is_dir():
            dest_path.mkdir(exist_ok=True)
        else:
            dest_path.write_bytes(src_path.read_bytes())
    
    # 清理临时文件
    shutil.rmtree(update_dir.parent)
    print("更新完成,请重启应用")

打包版:通过辅助脚本完成更新

由于运行中的可执行文件无法自我替换,需要编写一个独立的更新脚本:

辅助更新脚本(update_script.py)

import shutil
import os
import sys
import time

def update_app(new_exe_path, target_exe_path):
    # 等待主应用完全退出
    time.sleep(2)
    # 替换目标可执行文件
    shutil.copy(new_exe_path, target_exe_path)
    # 启动更新后的应用
    os.startfile(target_exe_path)
    # 清理临时文件和自身
    shutil.rmtree(Path(new_exe_path).parent)
    os.remove(sys.argv[0])

if __name__ == "__main__":
    if len(sys.argv) != 3:
        sys.exit(1)
    update_app(sys.argv[1], sys.argv[2])

主应用中触发打包版更新

def apply_packaged_update(release_data):
    """触发打包版应用更新"""
    try:
        # 下载更新包
        updated_exe = download_packaged_update(release_data)
        # 获取当前应用的可执行文件路径
        current_exe = Path(sys.executable).resolve() if getattr(sys, 'frozen', False) else Path(__file__).resolve()
        # 启动更新脚本并退出主应用
        import subprocess
        subprocess.Popen([sys.executable, "./update_script.py", str(updated_exe), str(current_exe)])
        sys.exit()
    except Exception as e:
        print(f"更新失败: {str(e)}")

4. 集成到CustomTkinter GUI

将更新逻辑绑定到按钮,添加状态提示:

import customtkinter as ctk

class UpdateEnabledApp(ctk.CTk):
    def __init__(self):
        super().__init__()
        self.title("带自动更新的GUI应用")
        self.geometry("400x200")
        
        # 状态标签
        self.status_label = ctk.CTkLabel(self, text="")
        self.status_label.pack(pady=20)
        
        # 更新按钮
        self.update_btn = ctk.CTkButton(
            self, 
            text="检查并更新", 
            command=self.trigger_update_flow
        )
        self.update_btn.pack(pady=10)
    
    def trigger_update_flow(self):
        self.update_btn.configure(state="disabled")
        self.status_label.configure(text="正在检查更新...")
        self.update()  # 强制刷新GUI
        
        # 检测更新
        release_data, latest_version = check_for_update()
        if not release_data:
            self.status_label.configure(text="当前已是最新版本")
            self.update_btn.configure(state="normal")
            return
        
        self.status_label.configure(text=f"发现新版本 {latest_version},正在下载...")
        self.update()
        
        # 根据你的分发类型选择对应逻辑
        # 源码版:
        update_dir = download_source_update(release_data)
        apply_source_update(update_dir)
        self.status_label.configure(text="更新完成,请重启应用")
        
        # 打包版:
        # apply_packaged_update(release_data)
        # self.status_label.configure(text="正在更新,请稍候...")
        
        self.update_btn.configure(state="normal")

if __name__ == "__main__":
    app = UpdateEnabledApp()
    app.mainloop()

关键注意事项

  • 依赖安装:需要提前安装requests和packaging:pip install requests packaging
  • 权限问题:更新时需要应用目录的写入权限,Windows打包版可能需要管理员权限
  • 版本规范:GitHub Release的Tag需遵循语义化版本(如v1.0.0),确保版本对比准确
  • 备份机制:建议在更新前备份当前应用文件,避免更新失败导致应用损坏
  • 错误处理:实际部署时需补充更多异常捕获,给用户清晰的错误提示

内容的提问来源于stack exchange,提问作者Ilya Chernyakov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 23:10:02