运行程序时自动打开两个Selenium实例的问题排查
问题根因
双浏览器实例是Flask debug模式的默认自动重载机制导致的:
- 配置
debug=True时,Werkzeug会启动两个独立进程:一个是监控项目文件变更的重载父进程,一个是实际承载Web服务的工作子进程 - 你的Selenium driver初始化逻辑写在
if __name__ == '__main__'作用域下,两个进程启动时都会执行这段初始化代码,因此会弹出两个Chrome窗口 - 只有工作子进程会实际接收并处理API请求,父进程启动的浏览器实例没有请求路由,就会出现一个窗口正常响应操作、另一个窗口完全闲置的现象。
排查步骤
按顺序验证可快速定位:
- 临时将
app.run()的debug=True改为debug=False后重启服务,如果仅弹出1个浏览器窗口,即可确认是重载器双进程导致的问题 - 可在driver初始化代码前添加
print(f"当前进程ID: {os.getpid()}"),运行后控制台会输出两个不同的进程ID,对应两个独立启动的进程 - 若关闭debug后仍存在双窗口,再排查代码中是否存在导入模块时隐式触发driver初始化的逻辑(比如全局变量赋值、装饰器执行时触发的初始化)。
解决方案
根据使用场景选择对应方案即可:
方案1:关闭自动重载(本地快速调试适用)
修改app.run配置,在保留debug模式其他特性的前提下关闭重载器,避免双进程启动:
app.run(host='127.0.0.1', port='5000', debug=True, use_reloader=False)
修改后仅会启动单个服务进程,仅初始化一个Chrome实例。
方案2:请求生命周期内初始化driver(生产/长期运行推荐)
不要在服务启动入口处初始化全局driver,改为在接口请求触发时初始化实例,请求结束后主动释放资源:
- 借助Flask的
g对象存储单请求维度的driver实例 - 搭配
teardown_request钩子在请求结束后自动退出driver,避免内存和进程泄漏
参考代码:
from flask import g @app.before_request def init_selenium_driver(): if not hasattr(g, 'driver'): # 这里的options沿用你之前的配置即可 g.driver = webdriver.Chrome(os.getcwd() + "\\chromedriver.exe", options=options) @app.teardown_request def release_selenium_driver(exc): if hasattr(g, 'driver'): g.driver.quit() # 后续接口逻辑中直接通过g.driver调用Selenium操作方法即可
方案3:匹配子进程标识初始化(需保留自动重载功能适用)
如果需要保留debug模式的文件变更自动重载能力,可以增加环境变量判断,仅让实际承载服务的子进程执行driver初始化:
if __name__ == '__main__': # 你的所有options配置逻辑保持不变 # 仅重载器启动的工作子进程满足该环境变量条件,会执行driver初始化 if os.environ.get('WERKZEUG_RUN_MAIN') == 'true': driver = webdriver.Chrome(os.getcwd() + "\\chromedriver.exe", options=options) app.run(host='127.0.0.1', port='5000', debug=True)
WERKZEUG_RUN_MAIN是Werkzeug重载器给工作子进程注入的专属标记,父进程启动时该变量不存在,不会执行driver初始化,既保留自动重载能力,也不会弹出多余的闲置浏览器窗口。
内容的提问来源于stack exchange,提问作者Rahil Kadakia
相关产品推荐
相关产品推荐

