Selenium与asyncio兼容问题:Discord Bot崩溃的解决办法咨询
我之前也踩过Selenium和asyncio配合的坑,尤其是在Discord Bot这种长时间运行的服务里,你遇到的Task was destroyed but it is pending!错误,本质上是异步任务还没完成就被销毁了,大概率和Selenium的资源没妥善处理有关。下面是我亲测有效的几个解决方案:
1. 用线程池隔离Selenium同步操作
Selenium是完全同步的库,直接在asyncio的事件循环里调用会阻塞整个循环,还容易导致任务状态异常。Python 3.9+可以用asyncio.to_thread(),把Selenium的所有操作放到单独的线程里执行,让asyncio的事件循环保持流畅:
import asyncio from selenium import webdriver from selenium.webdriver.chrome.options import Options async def run_selenium_job(): # 配置Chrome选项,无头模式能大幅减少资源占用 chrome_options = Options() chrome_options.add_argument("--headless=new") chrome_options.add_argument("--disable-gpu") chrome_options.add_argument("--no-sandbox") # 部分服务器环境需要此参数 # 把driver初始化和所有操作都放到线程中执行 driver = await asyncio.to_thread(webdriver.Chrome, options=chrome_options) try: await asyncio.to_thread(driver.get, "你的私有网站URL") # 其他Selenium操作(比如查找元素、点击)都用await asyncio.to_thread包裹 page_title = await asyncio.to_thread(driver.title) print(f"页面标题: {page_title}") finally: # 必须调用quit()而非close(),彻底关闭浏览器进程释放所有资源 await asyncio.to_thread(driver.quit)
2. 严格限制Selenium并发实例
Discord Bot可能同时收到多个命令请求,如果每个请求都开一个Selenium实例,很快会耗尽系统资源,导致任务异常。用asyncio.Semaphore来限制并发数,比如最多同时运行2个Selenium任务:
# 全局信号量,控制并发的Selenium实例数量 selenium_semaphore = asyncio.Semaphore(2) async def run_selenium_job(): async with selenium_semaphore: # 自动获取/释放信号量,避免资源过载 chrome_options = Options() chrome_options.add_argument("--headless=new") driver = await asyncio.to_thread(webdriver.Chrome, options=chrome_options) try: # 你的业务操作逻辑 finally: await asyncio.to_thread(driver.quit)
3. 确保任务异常被捕获并处理
如果Selenium操作中抛出异常但没被捕获,异步任务会一直处于pending状态,最终被销毁时就会出现你看到的错误。一定要给所有Selenium操作加上try-except-finally块,确保无论是否出错,资源都能被释放:
async def run_selenium_job(): async with selenium_semaphore: driver = None try: chrome_options = Options() chrome_options.add_argument("--headless=new") driver = await asyncio.to_thread(webdriver.Chrome, options=chrome_options) await asyncio.to_thread(driver.get, "你的私有网站URL") # 其他业务操作... except Exception as e: print(f"Selenium操作出错: {str(e)}") # 这里可以添加错误日志或通知逻辑 finally: if driver: await asyncio.to_thread(driver.quit)
4. 给耗时操作添加超时限制
如果你的Selenium任务需要执行耗时操作(比如等待页面加载、大量数据爬取),可以用Selenium的WebDriverWait设置超时,避免任务无限期挂起:
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By async def run_selenium_job(): async with selenium_semaphore: chrome_options = Options() chrome_options.add_argument("--headless=new") driver = await asyncio.to_thread(webdriver.Chrome, options=chrome_options) try: await asyncio.to_thread(driver.get, "你的私有网站URL") # 等待目标元素加载,超时10秒 await asyncio.to_thread( WebDriverWait(driver, 10).until, EC.presence_of_element_located((By.ID, "target-element")) ) finally: await asyncio.to_thread(driver.quit)
核心总结
本质上就是把Selenium的同步操作和asyncio的异步循环彻底隔离,同时严格管理浏览器资源,确保每个任务都能正常完成或被正确销毁。按照上面的方法调整后,你的Discord Bot应该能稳定运行,不会再出现任务pending被销毁的错误。
内容的提问来源于stack exchange,提问作者sb2894

