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

如何通过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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 00:42:26