news 2026/10/6 2:46:37

在使用 Selenium 进行网页自动化、数据采集、自动化测试或浏览器交互任务时

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
在使用 Selenium 进行网页自动化、数据采集、自动化测试或浏览器交互任务时

在使用 Selenium 进行网页自动化、数据采集、自动化测试或浏览器交互任务时,开发者常常会把注意力放在定位元素、点击按钮、填写表单、等待页面加载等操作上面。然而,一个同样重要却容易被忽视的问题是:脚本结束时必须正确关闭浏览器并释放驱动进程。如果只关闭浏览器窗口,却没有调用driver.quit(),操作系统中可能仍然残留浏览器进程、驱动进程,甚至临时用户数据目录。长时间运行后,这些残留会占用 CPU、内存、端口和文件句柄,严重时会导致后续脚本无法启动新的浏览器实例。

因此,driver.quit()并不是一个简单的“收尾动作”,而是 Selenium 自动化脚本生命周期管理中的关键环节。它负责向浏览器驱动发送退出指令,关闭所有浏览器窗口,并终止驱动进程。与之相比,driver.close()只关闭当前窗口,不会自动结束驱动服务;而直接结束 Python 进程,也不一定能可靠地清理浏览器和驱动资源。可靠的资源释放策略,应当把driver.quit()放入异常处理或上下文管理机制中,确保无论脚本成功执行、发生异常,还是被提前中断,浏览器和驱动都能被及时释放。

一、为什么必须调用 driver.quit()

Selenium 的工作方式并不是让 Python 直接控制浏览器,而是通过 WebDriver 协议与浏览器驱动通信。以 Chrome 为例,Python 脚本会启动chromedriver,再由chromedriver启动 Chrome 浏览器。脚本中的每一个操作,例如打开网页、查找元素、输入文本、点击按钮,都会转化为 WebDriver 协议请求发送给驱动进程。

如果脚本结束时没有调用driver.quit(),可能出现以下几种情况:

  1. 浏览器窗口关闭,但驱动进程仍在运行
    有些情况下,用户手动关闭浏览器窗口,或者脚本只调用了driver.close(),浏览器窗口确实会消失,但驱动进程可能仍然存在于系统中。

  2. 浏览器进程残留
    即使浏览器窗口已经不可见,浏览器子进程、渲染进程或后台进程仍可能继续占用内存。

  3. 临时用户数据目录未被清理
    Selenium 启动浏览器时,有时会创建临时用户数据目录。如果未正常退出,这些目录可能不会被删除,长期积累会占用磁盘空间。

  4. 端口或资源被占用
    驱动进程通常会监听本地端口。如果进程未释放,后续脚本再次启动浏览器时,可能出现端口冲突、启动失败或资源竞争。

  5. 自动化任务稳定性下降
    在定时任务、批量采集、CI/CD 测试或服务器环境中,残留进程会不断累积,最终导致脚本运行变慢、浏览器无法启动,甚至服务异常。

因此,driver.quit()的价值在于它提供了一个相对完整的退出流程:关闭浏览器窗口、结束会话、释放驱动进程,并尽可能清理相关资源。

二、driver.quit() 与 driver.close() 的区别

很多初学者容易混淆driver.close()和driver.quit()。二者虽然都与“关闭”有关,但作用范围完全不同。

方法作用是否释放驱动进程
driver.close()关闭当前浏览器窗口否
driver.quit()关闭所有窗口并结束 WebDriver 会话是

如果浏览器只打开了一个窗口,调用driver.close()后,窗口会被关闭,但驱动进程不一定结束。此时如果继续执行driver.get()或其他操作,通常会因为会话已失效而报错。

如果浏览器打开了多个窗口,driver.close()只会关闭当前焦点窗口,其他窗口仍然存在。只有调用driver.quit(),才会关闭所有窗口并结束整个浏览器会话。

在脚本结束时,通常应该使用driver.quit(),而不是driver.close()。driver.close()更适合在需要关闭某个窗口、但仍保留浏览器会话的场景中使用。

三、基础写法:在 finally 中确保释放

最基础、也最常见的写法是使用try...finally结构。无论脚本是否发生异常,finally块中的代码都会执行,因此可以把driver.quit()放在其中。

fromseleniumimportwebdriverfromselenium.webdriver.chrome.serviceimportServicefromselenium.webdriver.chrome.optionsimportOptionsfromselenium.webdriver.common.byimportByfromselenium.webdriver.support.uiimportWebDriverWaitfromselenium.webdriver.supportimportexpected_conditionsasECdefrun_basic_task():"""基础示例:使用 try/finally 确保浏览器驱动被释放"""options=Options()# 生产环境可考虑无头模式# options.add_argument("--headless")options.add_argument("--disable-gpu")options.add_argument("--no-sandbox")driver=Nonetry:driver=webdriver.Chrome(options=options)driver.get("https://www.example.com")# 等待页面标题包含示例关键字wait=WebDriverWait(driver,10)wait.until(EC.title_contains("Example"))title=driver.titleprint(f"页面标题:{title}")exceptExceptionase:print(f"任务执行异常:{e}")finally:ifdriverisnotNone:driver.quit()print("浏览器驱动已释放。")if__name__=="__main__":run_basic_task()

这段代码的重点在于finally块。即使driver.get()超时、元素定位失败、网络异常,或者业务逻辑抛出错误,driver.quit()仍然会被调用。同时,driver初始值设为None,可以避免在浏览器尚未创建成功时就调用driver.quit()引发新的异常。

这种写法简单可靠,适合大多数脚本。缺点是如果项目中存在大量自动化任务,每个函数都重复编写try...finally,代码会显得冗余。

四、进阶写法:封装上下文管理器

Python 的上下文管理器可以把资源获取和释放封装到同一个对象中,使调用方只需使用with语句即可自动完成释放。这种方式更符合 Python 风格,也更适合工程化项目。

fromcontextlibimportcontextmanagerfromseleniumimportwebdriverfromselenium.webdriver.chrome.optionsimportOptions@contextmanagerdefcreate_driver(headless:bool=False):""" 浏览器驱动上下文管理器: 进入 with 块时创建驱动,离开时自动调用 driver.quit()。 """options=Options()ifheadless:options.add_argument("--headless")options.add_argument("--disable-gpu")options.add_argument("--no-sandbox")driver=webdriver.Chrome(options=options)try:yielddriverfinally:try:driver.quit()exceptExceptionase:print(f"释放浏览器驱动时出现异常:{e}")defrun_with_context_manager():"""使用上下文管理器自动释放浏览器资源"""withcreate_driver(headless=True)asdriver:driver.get("https://www.example.com")print(f"当前页面标题:{driver.title}")if__name__=="__main__":run_with_context_manager()

这种封装方式有几个明显优势:

  1. 调用方不需要关心释放细节
    业务代码只需要关注浏览器操作,不需要在每个函数里重复写try...finally。

  2. 资源生命周期更清晰
    with语句的缩进范围就是浏览器会话的有效范围,离开该范围后驱动会被自动释放。

  3. 便于统一配置
    可以在create_driver中集中管理无头模式、窗口大小、用户代理、下载目录、日志路径等配置。

  4. 释放失败不会掩盖主业务异常
    finally中对driver.quit()单独捕获异常,可以避免释放阶段的错误覆盖原本的业务异常。

需要注意的是,上下文管理器仍然依赖 Python 正常执行离开with块。如果进程被强制杀死,例如使用kill -9,或者操作系统直接终止 Python 进程,那么任何 Python 层面的清理代码都可能无法执行。因此,在长期运行的服务中,还需要结合进程管理、超时控制和监控告警。

五、生产环境推荐写法:可重试的浏览器任务

在实际项目中,浏览器自动化任务常常会受到网络波动、页面加载缓慢、元素渲染延迟、反爬策略或环境配置变化的影响。因此,更稳健的做法是把浏览器创建、任务执行、资源释放和重试机制结合起来。

importtimefromseleniumimportwebdriverfromselenium.webdriver.chrome.optionsimportOptionsfromselenium.webdriver.common.byimportByfromselenium.webdriver.support.uiimportWebDriverWaitfromselenium.webdriver.supportimportexpected_conditionsasECclassBrowserTaskRunner:""" 浏览器任务执行器: 负责创建浏览器、执行业务逻辑、释放资源,并支持简单重试。 """def__init__(self,headless:bool=True,max_retries:int=2):self.headless=headless self.max_retries=max_retriesdef_create_driver(self)->webdriver.Chrome:options=Options()ifself.headless:options.add_argument("--headless")options.add_argument("--disable-gpu")options.add_argument("--no-sandbox")options.add_argument("--disable-dev-shm-usage")returnwebdriver.Chrome(options=options)def_run_once(self,driver:webdriver.Chrome)->dict:driver.get("https://www.example.com")wait=WebDriverWait(driver,10)wait.until(EC.title_contains("Example"))return{"title":driver.title,"url":driver.current_url,"status":"success",}defrun(self)->dict|None:last_error=Noneforattemptinrange(1,self.max_retries+1):driver=Nonetry:print(f"第{attempt}次尝试启动浏览器任务。")driver=self._create_driver()result=self._run_once(driver)print("任务执行成功。")returnresultexceptExceptionase:last_error=eprint(f"第{attempt}次执行失败:{e}")finally:ifdriverisnotNone:try:driver.quit()exceptExceptionasquit_err:print(f"释放浏览器驱动失败:{quit_err}")# 重试前短暂等待,避免频繁重启浏览器time.sleep(2)print(f"任务在{self.max_retries}次重试后仍然失败:{last_error}")returnNoneif__name__=="__main__":runner=BrowserTaskRunner(headless=True,max_retries=2)result=runner.run()print("最终结果:",result)

这段代码把浏览器生命周期管理放入了BrowserTaskRunner内部。每次重试都会创建新的浏览器实例,并在finally中确保释放。这样做的好处是,即使某一次浏览器启动失败、页面加载超时或元素定位异常,也不会导致驱动进程泄漏。

六、代码解析

上述示例共同体现了几个关键设计点。

第一,浏览器驱动必须显式释放。driver.quit()是 Selenium 提供的标准释放方式,不应依赖操作系统回收或手动结束进程。

第二,释放逻辑应放在 finally 中。finally能保证无论业务代码是否抛出异常,释放动作都有机会执行。

第三,调用 driver.quit() 前应判断 driver 是否存在。如果浏览器尚未创建成功,driver可能仍为None,此时直接调用driver.quit()会引发新的异常。

第四,driver.quit() 本身也可能失败。例如浏览器已经崩溃、驱动进程异常退出,或者网络通信中断。因此,释放时最好单独捕获异常,避免掩盖主业务错误。

第五,不要将 driver.close() 误认为资源释放方法。driver.close()只关闭当前窗口,不能替代driver.quit()。

第六,长期运行的自动化任务需要更完善的治理。除了代码层面的释放,还应考虑进程监控、超时限制、日志记录、临时目录清理和失败告警。

七、实践亮点

这份方案的价值不仅在于调用了driver.quit(),更在于它把资源释放从“脚本末尾的一行代码”提升为“自动化任务的基础设施”。

  1. 安全性更强
    通过try...finally和上下文管理器,确保异常路径下也能释放浏览器和驱动资源,降低进程泄漏风险。

  2. 可维护性更高
    浏览器创建、配置、释放逻辑集中管理,业务代码不需要重复处理底层资源问题。

  3. 可扩展性更好
    可以在驱动创建阶段统一加入无头模式、代理设置、下载目录、日志配置、用户数据目录等参数。

  4. 更适合生产环境
    引入重试机制和异常隔离后,脚本在网络波动、页面加载失败或驱动启动异常时具备一定自愈能力。

  5. 便于团队协作
    将资源释放封装成统一入口后,团队成员只需调用封装好的方法,就能遵循相同的浏览器生命周期规范。

八、常见注意事项

在实际使用中,还需要注意以下问题:

  1. 确保 Selenium 版本与浏览器版本匹配
    较新版本的 Selenium 通常会自动管理驱动,但仍需保证 Chrome、Edge 等浏览器版本与驱动兼容。

  2. 服务器环境建议使用无头模式
    在没有图形界面的服务器上运行浏览器时,应开启--headless,否则可能无法启动。

  3. 避免频繁创建和销毁浏览器
    如果需要执行大量任务,可以考虑复用浏览器实例,但必须确保复用策略不会导致状态污染。

  4. 谨慎使用用户数据目录
    如果指定了固定的用户数据目录,多个脚本同时运行可能产生冲突。必要时可使用临时目录,并在退出后清理。

  5. 进程残留时应检查系统进程
    如果发现浏览器或驱动进程残留,可以通过系统任务管理器、ps、top等工具检查,并确认脚本是否正确调用了driver.quit()。

九、总结

Selenium 自动化脚本的资源释放问题看似简单,却直接影响脚本的稳定性、可维护性和运行成本。driver.quit()是关闭浏览器并释放驱动进程的标准方法,不能省略,也不能用driver.close()替代。更可靠的做法是把释放逻辑放入finally块,或通过上下文管理器、任务执行器进行封装,使浏览器生命周期与业务逻辑解耦。

对于短期脚本,使用try...finally已经足够;对于中大型项目,推荐封装统一的浏览器管理器;对于生产环境,则应进一步加入重试、超时、日志和监控机制。只有这样,才能避免浏览器进程残留、驱动占用、端口冲突和临时文件堆积等问题,使 Python 自动化任务更加稳定、可控和可持续运行。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 2:45:17

视觉处理为什么会慢?解码、预处理、模型推理、后处理和编码的性能拆解

GPU 利用率很低不一定是模型慢;利用率很高也不代表整条视频任务足够快。用户等待的是从输入视频到可播放结果的总时间,而这段时间由读取解码、预处理、推理、后处理、绘制和编码共同组成。本文用固定视频任务说明怎样定位瓶颈,而不是只报一个…

作者头像 李华
网站建设 2026/10/6 2:43:43

【自用】MySQL - 语法:DDL、DML、DQL、DCL

通用语法分类DDL数据定义语言DDL用来定义数据库对象操作数据库注:[ ]为可选项操作表注:需要先通过use进入数据库创建表示例:创建表查询表与表结构:查询创建时使用的语句:数据类型主要类型:数值类型、字符串…

作者头像 李华
网站建设 2026/10/6 2:43:10

LabVIEW 10 个月重做洗衣机高速不平衡检测

想让洗衣机足够安静,力气就得压在高速脱水的不平衡检测上,整个开发周期只有十个月。洗衣机高速不平衡测试台,LabVIEW 界面监测振动与转速曲线01 挑战:把洗衣机做到足够安静目标很明确:做出最安静的洗衣机,让…

作者头像 李华