跨平台实现脚本退出后维持Selenium驱动后台运行并可管控的方案
方案选择:用
fork()实现后台守护进程,放弃守护线程 核心原因:线程无法脱离主进程存活
守护线程(Thread(daemon=True))本质是依附于主进程的执行单元,主脚本一旦退出,整个进程被销毁,所有线程都会被强制终止,完全满足不了「主脚本退出后后台进程继续运行」的需求。而fork()创建的是独立的子进程,主进程退出后,子进程会被系统的init(或systemd)进程接管,能持续运行,且拥有独立的PID,正好符合你用PID管控的需求。
Unix系(Mac/Linux)下的标准守护进程实现步骤
要让fork出的进程真正成为后台守护进程,需要遵循「双fork+会话隔离」的标准流程,避免进程挂在终端下,同时防止僵尸进程:
- 第一次fork:主进程创建子进程后立即退出,子进程成为「孤儿进程」,被系统init进程接管,脱离原终端的控制。
- 创建新会话:子进程调用
os.setsid(),成为新会话的首进程,彻底脱离原终端的关联。 - 第二次fork:子进程再创建一个孙子进程后退出,孙子进程不会成为进程组组长,避免意外打开终端。
- 重定向标准IO:把孙子进程的标准输入、输出、错误都重定向到
/dev/null,避免占用终端资源。 - 写入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
相关产品推荐
相关产品推荐

