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

跨平台实现脚本退出后维持Selenium驱动后台运行并可管控的方案

方案选择:用fork()实现后台守护进程,放弃守护线程

核心原因:线程无法脱离主进程存活

守护线程(Thread(daemon=True))本质是依附于主进程的执行单元,主脚本一旦退出,整个进程被销毁,所有线程都会被强制终止,完全满足不了「主脚本退出后后台进程继续运行」的需求。而fork()创建的是独立的子进程,主进程退出后,子进程会被系统的init(或systemd)进程接管,能持续运行,且拥有独立的PID,正好符合你用PID管控的需求。

Unix系(Mac/Linux)下的标准守护进程实现步骤

要让fork出的进程真正成为后台守护进程,需要遵循「双fork+会话隔离」的标准流程,避免进程挂在终端下,同时防止僵尸进程:

  1. 第一次fork:主进程创建子进程后立即退出,子进程成为「孤儿进程」,被系统init进程接管,脱离原终端的控制。
  2. 创建新会话:子进程调用os.setsid(),成为新会话的首进程,彻底脱离原终端的关联。
  3. 第二次fork:子进程再创建一个孙子进程后退出,孙子进程不会成为进程组组长,避免意外打开终端。
  4. 重定向标准IO:把孙子进程的标准输入、输出、错误都重定向到/dev/null,避免占用终端资源。
  5. 写入PID到注册表文件:将孙子进程的PID写入你指定的注册表文件,后续通过这个PID即可终止进程。

Python代码示例

import os
import sys
import signal
import time
from selenium import webdriver

driver = None

def handle_exit(signum, frame):
    # 收到终止信号时,清理Selenium驱动
    global driver
    if driver:
        driver.quit()
    sys.exit(0)

def daemonize(pid_file):
    # 第一次fork
    try:
        pid = os.fork()
        if pid > 0:
            # 主进程退出
            sys.exit(0)
    except OSError as e:
        sys.stderr.write(f"第一次fork失败: {e}\n")
        sys.exit(1)

    # 脱离原会话和终端
    os.chdir("/")
    os.setsid()
    os.umask(0)

    # 第二次fork
    try:
        pid = os.fork()
        if pid > 0:
            sys.exit(0)
    except OSError as e:
        sys.stderr.write(f"第二次fork失败: {e}\n")
        sys.exit(1)

    # 重定向标准IO
    sys.stdout.flush()
    sys.stderr.flush()
    si = open(os.devnull, 'r')
    so = open(os.devnull, 'a+')
    se = open(os.devnull, 'a+')
    os.dup2(si.fileno(), sys.stdin.fileno())
    os.dup2(so.fileno(), sys.stdout.fileno())
    os.dup2(se.fileno(), sys.stderr.fileno())

    # 写入PID到文件
    with open(pid_file, 'w') as f:
        f.write(str(os.getpid()))

    # 注册信号处理函数,处理终止信号
    signal.signal(signal.SIGTERM, handle_exit)
    signal.signal(signal.SIGINT, handle_exit)

def run_selenium_background():
    global driver
    # 启动Selenium驱动(这里以Chrome为例)
    options = webdriver.ChromeOptions()
    options.add_argument('--headless=new')  # 无头模式,适合后台运行
    driver = webdriver.Chrome(options=options)

    # 可以把session ID和remote URL也写入注册表文件,方便后续连接
    session_info = {
        "pid": os.getpid(),
        "session_id": driver.session_id,
        "remote_url": driver.command_executor._url
    }
    # 示例:写入JSON格式的注册表文件
    # import json
    # with open("/tmp/selenium_registry.json", 'w') as f:
    #     json.dump(session_info, f)

    # 模拟定期检查或维持会话的逻辑
    while True:
        # 定期访问空白页保持驱动活跃
        try:
            driver.get("about:blank")
        except Exception as e:
            handle_exit(None, None)
        # 每5分钟检查一次
        time.sleep(300)

if __name__ == "__main__":
    PID_FILE = "/tmp/selenium_daemon.pid"
    daemonize(PID_FILE)
    run_selenium_background()

终止后台进程的方法

读取注册表文件中的PID,直接发送终止信号即可:

import os
import signal

PID_FILE = "/tmp/selenium_daemon.pid"
with open(PID_FILE, 'r') as f:
    pid = int(f.read().strip())

# 发送SIGTERM信号,触发进程的清理逻辑
os.kill(pid, signal.SIGTERM)
# 删除PID文件
os.unlink(PID_FILE)

注意事项

  • 僵尸进程处理:双fork的设计让最终的守护进程父进程是init,init会自动回收僵尸进程,无需额外处理。
  • Selenium资源清理:一定要在信号处理函数中调用driver.quit(),避免浏览器进程残留。
  • 权限问题:确保PID文件和注册表文件的读写权限,避免无权限写入或读取。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 14:00:26