如何通过Python调用GitHub API实现Node项目自动更新?
解决GitHub API调用与自动更新的完整方案
我明白你卡在GitHub API获取最新版本和文件下载这一步了,直接给你一套能跑通的Python脚本方案,把整个更新流程串起来:
首先,咱们得先理清楚几个核心步骤的实现逻辑:
1. 调用GitHub API获取最新Release的下载链接
GitHub的官方API提供了获取最新Release的端点,你只需要替换自己的用户名和仓库名就行。这里要注意必须加User-Agent请求头,不然GitHub会返回403拒绝访问(API限流要求)。用urllib的话,咱们这么写:
2. 用urlopen下载文件(替代wget)
直接用urlopen配合shutil.copyfileobj来分块下载,既符合你的要求,又能处理大文件不会占满内存。
3. 整合PM2操作、解压、清理的完整脚本
下面是完整的脚本代码,你只需要替换开头的几个配置项:
import urllib.request import json import subprocess import zipfile import os import shutil # -------------------------- 配置项 -------------------------- GITHUB_OWNER = "你的GitHub用户名" # 比如:octocat GITHUB_REPO = "你的仓库名" # 比如:hello-world PM2_APP_NAME = "你的PM2应用名称" # 比如:my-node-app RELEASE_ZIP_NAME = "release.zip" # 你发布的压缩包文件名 # ----------------------------------------------------------- def main(): try: # 1. 停止PM2中的Node服务 print("正在停止Node服务...") subprocess.run(["pm2", "stop", PM2_APP_NAME], check=True, capture_output=True, text=True) print("服务已停止") # 2. 调用GitHub API获取最新Release信息 print("正在获取最新版本信息...") api_url = f"https://api.github.com/repos/{GITHUB_OWNER}/{GITHUB_REPO}/releases/latest" # 必须设置User-Agent,否则GitHub API会拒绝请求 headers = {"User-Agent": "Node-App-Auto-Update-Script"} req = urllib.request.Request(api_url, headers=headers) with urllib.request.urlopen(req) as response: if response.getcode() != 200: raise Exception(f"API请求失败,状态码:{response.getcode()}") release_data = json.load(response) # 找到对应压缩包的下载链接 download_url = None for asset in release_data.get("assets", []): if asset.get("name") == RELEASE_ZIP_NAME: download_url = asset.get("browser_download_url") break if not download_url: raise Exception(f"在最新Release中未找到名为{RELEASE_ZIP_NAME}的文件") print(f"获取到最新版本下载链接:{download_url}") # 3. 下载压缩包到当前目录 print("正在下载最新版本...") zip_file_path = os.path.join(os.getcwd(), RELEASE_ZIP_NAME) with urllib.request.urlopen(download_url) as response, open(zip_file_path, "wb") as out_file: shutil.copyfileobj(response, out_file) print("下载完成") # 4. 解压压缩包到当前目录(覆盖现有文件) print("正在解压文件...") with zipfile.ZipFile(zip_file_path, "r") as zip_ref: # 遍历所有文件,强制覆盖 for member in zip_ref.infolist(): target_path = os.path.join(os.getcwd(), member.filename) # 如果目标文件已存在,先删除 if os.path.exists(target_path): if os.path.isdir(target_path): shutil.rmtree(target_path) else: os.remove(target_path) zip_ref.extract(member, os.getcwd()) print("解压完成") # 5. 删除下载的压缩包 print("正在清理临时文件...") os.remove(zip_file_path) print("清理完成") # 6. 重启PM2服务 print("正在重启Node服务...") subprocess.run(["pm2", "start", PM2_APP_NAME], check=True, capture_output=True, text=True) print("服务已重启,更新完成!") except subprocess.CalledProcessError as e: print(f"PM2命令执行失败:{e.stderr}") # 可以在这里加回滚逻辑,比如重启原来的服务 subprocess.run(["pm2", "start", PM2_APP_NAME], check=False) except Exception as e: print(f"更新失败:{str(e)}") # 异常时尝试重启服务 subprocess.run(["pm2", "start", PM2_APP_NAME], check=False) if __name__ == "__main__": main()
关键细节说明
- GitHub API请求头:一定要加
User-Agent,不然会触发API限流,返回403错误。 - 文件覆盖逻辑:解压时先删除已存在的文件/目录,确保新文件完全覆盖旧内容。
- 异常处理:脚本里加了异常捕获,即使更新失败也会尝试重启服务,避免服务挂掉。
- PM2命令:用
subprocess.run的check=True来确保命令执行成功,如果失败会抛出异常。
你只需要把开头的配置项换成自己的信息,然后在Node里用child_process.exec或者spawn调用这个脚本就行,比如:
const { exec } = require('child_process'); exec('python3 update_script.py', (error, stdout, stderr) => { if (error) { console.error(`更新脚本执行失败: ${error.message}`); return; } if (stderr) { console.error(`脚本错误输出: ${stderr}`); return; } console.log(`更新脚本输出: ${stdout}`); });
内容的提问来源于stack exchange,提问作者K20GH




