如何通过Flask Web界面启停独立运行的Python脚本
Flask控制独立常驻Python脚本启停实现方案
核心逻辑是 用操作系统子进程托管带while True的业务脚本,不要在Flask的请求线程里直接跑循环,否则会阻塞Web服务;通过记录子进程PID实现状态判断和停止操作,全程用Python标准库就能实现,不需要额外依赖。
基础实现代码
1. 约定配置
提前明确两个固定路径:
- 待控制的常驻业务脚本命名为
worker.py - 用本地文件
worker.pid存储运行中脚本的进程ID,用于状态校验和停止操作
2. Flask服务端代码(app.py)
from flask import Flask, render_template_string, jsonify import subprocess import os import signal app = Flask(__name__) PID_FILE = "worker.pid" WORKER_PATH = "worker.py" # 替换成你的脚本绝对路径更稳妥 def check_worker_status(): """校验脚本运行状态,返回(是否运行, 进程ID)""" if not os.path.exists(PID_FILE): return False, None # 读PID,无效文件直接清理 try: with open(PID_FILE, "r") as f: pid = int(f.read().strip()) except (ValueError, IOError): if os.path.exists(PID_FILE): os.remove(PID_FILE) return False, None # 发空信号检测进程是否存活,不实际终止进程 try: os.kill(pid, 0) return True, pid except OSError: os.remove(PID_FILE) return False, None @app.route("/") def index(): is_running, _ = check_worker_status() # 单页模板,仅保留状态显示和切换按钮 page = """ <!DOCTYPE html> <html> <head><title>脚本控制面板</title></head> <body style="padding: 2rem;"> <h4>常驻脚本控制</h4> <p>当前状态:<b id="status">{{ "运行中" if is_running else "已停止" }}</b></p> <button onclick="toggle()" style="padding: 0.5rem 1rem;"> {{ "停止脚本" if is_running else "启动脚本" }} </button> <script> async function toggle() { const res = await fetch("/toggle", {method: "POST"}); const data = await res.json(); document.getElementById("status").innerText = data.is_running ? "运行中" : "已停止"; document.querySelector("button").innerText = data.is_running ? "停止脚本" : "启动脚本"; } </script> </body> </html> """ return render_template_string(page, is_running=is_running) @app.route("/toggle", methods=["POST"]) def toggle_worker(): is_running, pid = check_worker_status() if is_running: # 停止逻辑:发终止信号,等待进程退出后清理PID文件 os.kill(pid, signal.SIGTERM) try: os.waitpid(pid, 0) except ChildProcessError: pass if os.path.exists(PID_FILE): os.remove(PID_FILE) return jsonify({"is_running": False, "msg": "已停止"}) else: # 启动逻辑:创建独立会话的子进程,和Flask进程解耦 proc = subprocess.Popen( ["python", WORKER_PATH], stdout=open("worker.log", "a"), # 业务日志输出到worker.log stderr=subprocess.STDOUT, start_new_session=True # *关键配置:子进程脱离Flask进程组,不会随Flask退出被连带杀掉* ) # 写入PID到文件 with open(PID_FILE, "w") as f: f.write(str(proc.pid)) return jsonify({"is_running": True, "msg": "已启动"}) if __name__ == "__main__": # 注意:不要开debug模式,否则会启动双进程导致PID记录混乱 app.run(host="0.0.0.0", port=5000, debug=False)
3. 业务脚本适配(worker.py)
建议在你的while True脚本里加信号捕获逻辑,收到停止指令时可以做资源清理,避免强制退出导致数据异常:
import time import signal running = True def exit_handler(signum, frame): global running running = False # 注册SIGTERM信号处理 signal.signal(signal.SIGTERM, exit_handler) if __name__ == "__main__": while running: # 替换成你的实际业务逻辑 print(f"业务执行中,时间:{time.time()}") time.sleep(2) # 收尾逻辑:关闭连接、刷入缓存等 print("脚本正常退出,资源清理完成")
避坑说明
- 启动子进程必须加
start_new_session=True,否则Flask重启/退出时,子进程会被系统连带终止,无法独立运行 - 生产环境部署Flask时不要开多worker模式(比如gunicorn配置
-w 4),多进程同时读写PID文件会导致状态判断错误,单worker足够支撑这种简单控制面板;如果必须开多worker,把PID存储换成Redis这类独立中间件即可 - 如果用虚拟环境运行,
subprocess.Popen里的python要替换成虚拟环境下python解释器的绝对路径,避免找不到依赖 - 如果出现状态异常(比如脚本实际挂了但页面显示运行中),手动删除目录下的
worker.pid文件即可重置状态
内容的提问来源于stack exchange,提问作者zrvcat
相关产品推荐
相关产品推荐

