crontab调度的Selenium脚本偶发Chromedriver启动失败问题排查
问题描述
通过crontab调度搭载Chromedriver的Selenium脚本自动运行,脚本会生成多个进程,每个进程独立启动Chromedriver实例。但存在约50%概率所有进程的Chromedriver均启动失败,手动启动脚本则完全正常。
相关信息
代码结构
main.py
import multiprocessing as mp if __name__ == '__main__': pool = mp.Pool() result = pool.starmap_async(book_job, parameter_list) pool.close() pool.join()
book.py
import func_timeout # type: ignore from func_timeout import func_set_timeout import traceback import logging from selenium import webdriver from selenium.webdriver.chrome.service import Service def book_job(): #...... try: book_result = book() return message + book_result except Exception: traceback.print_exc() return message + 'book failed' #...... def book(): #...... try: driver = wait_book_page() except func_timeout.exceptions.FunctionTimedOut: logging.error("wait book page timeout") driver.quit() return "Time out to wait book page enabled" #...... @func_set_timeout(120) def wait_book_page(): options = webdriver.ChromeOptions() options.add_argument('--headless') options.add_argument('--disable-gpu') options.add_argument('--no-sandbox') try: webdriver_service = Service('/usr/bin/chromedriver') driver = webdriver.Chrome(service=webdriver_service,options=options) logging.info('chromedriver started') except Exception: traceback.print_exc() raise Exception('chromedriver started failed') #......
错误信息
Traceback (most recent call last): File "/home/***/book.py", line 93, in wait_book_page driver = webdriver.Chrome(service=webdriver_service,options=options) File "/home/***/miniconda3/lib/python3.9/site-packages/selenium/webdriver/chrome/webdriver.py", line 81, in __init__ super().__init__( File "/home/***/miniconda3/lib/python3.9/site-packages/selenium/webdriver/chromium/webdriver.py", line 103, in __init__ self.service.start() File "/home/***/miniconda3/lib/python3.9/site-packages/selenium/webdriver/common/service.py", line 106, in start self.assert_process_still_running() File "/home/***/miniconda3/lib/python3.9/site-packages/selenium/webdriver/common/service.py", line 119, in assert_process_still_running raise WebDriverException(f"Service {self.path} unexpectedly exited. Status code was: {return_code}") selenium.common.exceptions.WebDriverException: Message: Service /usr/bin/chromedriver unexpectedly exited. Status code was: 1 Traceback (most recent call last): File "/home/***/main.py", line 145, in book_job book_result = book(session, venue_list, time, space_num) File "/home/***/book.py", line 25, in book driver = wait_book_page(session, base_url+venue_list[0]) File "/home/***/miniconda3/lib/python3.9/site-packages/func_timeout/dafunc.py", line 185, in <lambda> return wraps(func)(lambda args, kwargs : func_timeout(defaultTimeout, func, args=args, kwargs=kwargs)) File "/home/***/miniconda3/lib/python3.9/site-packages/func_timeout/dafunc.py", line 108, in func_timeout raise_exception(exception) File "/home/***/miniconda3/lib/python3.9/site-packages/func_timeout/py3_raise.py", line 7, in raise_exception raise exception[0] from None File "/home/***/book.py", line 103, in wait_book_page raise Exception('chromedriver started failed')
crontab配置
59 8 * * * username DISPLAY=:0 /home/***/miniconda3/bin/python /home/***/main.py >> /home/***/out.log 2>&1
环境信息
- ubuntu22.04.1 LTS
- Linux 5.15.0-52-generic
- ChromeDriver 111.0.5563.146 (2b5ec366bb5b68dacca9719d5d83cb7e61fb6431-refs/branch-- heads/5563@{#1238})
- conda 23.1.0
- python 3.9.12
已尝试重装chromium和chromedriver、在另一台机器部署环境,问题依旧;手动执行与cron相同账号和命令无问题。
排查思路与建议
1. 环境变量差异问题
crontab的执行环境变量远少于用户手动登录的环境,即使指定了DISPLAY=:0,仍可能缺少关键变量:
- 在crontab命令前追加完整的环境变量导入,比如先执行
source /home/***/.bashrc(如果是bash)或source /home/***/.profile,确保conda环境和Chrome相关的路径、变量都能被正确加载:59 8 * * * username source /home/***/.bashrc && DISPLAY=:0 /home/***/miniconda3/bin/python /home/***/main.py >> /home/***/out.log 2>&1 - 或者在脚本开头显式设置环境变量,比如添加
PATH、LD_LIBRARY_PATH等,确保Chromedriver能找到依赖库。
2. 并发启动资源竞争问题
多进程同时启动Chromedriver可能触发系统资源限制或端口冲突:
- 限制进程池的并发数,比如
mp.Pool(processes=4)(根据机器CPU核心数调整,不要一次性启动过多进程),避免瞬间占用过多内存、CPU或端口; - 给每个Chromedriver实例指定单独的端口,通过
options.add_argument(f'--remote-debugging-port={port}'),端口可以用动态生成的(比如从9222开始递增,确保不重复)。
3. Chromedriver启动权限与依赖问题
- 检查
/usr/bin/chromedriver的执行权限,确保cron用户拥有可执行权限:ls -l /usr/bin/chromedriver,如果权限不足,执行sudo chmod +x /usr/bin/chromedriver; - 手动在cron环境下测试Chromedriver能否单独启动:写一个简单的测试脚本,用cron调度执行,输出详细日志,看是否有依赖缺失的提示(比如libnss3等库),Ubuntu下可以通过
apt install libnss3 libgconf-2-4 libatk-bridge2.0-0 libcups2 libxkbcommon0补全常见依赖。
4. 日志与调试优化
- 在
wait_book_page函数中,捕获Chromedriver启动时的标准输出和错误,Selenium的Service类可以设置日志输出:
这样能看到Chromedriver启动失败的具体原因,而不是只看到Selenium抛出的通用错误;webdriver_service = Service('/usr/bin/chromedriver') webdriver_service.log_path = '/home/***/chromedriver.log' driver = webdriver.Chrome(service=webdriver_service, options=options) - 在crontab中增加更详细的日志,比如把环境变量也输出到日志:
59 8 * * * username env >> /home/***/env.log && source /home/***/.bashrc && DISPLAY=:0 /home/***/miniconda3/bin/python /home/***/main.py >> /home/***/out.log 2>&1,对比手动执行和cron执行的环境变量差异。
5. 超时机制与进程清理问题
- 当前
func_timeout的超时处理中,如果Chromedriver启动失败,可能没有正确清理残留进程,导致后续启动受影响;在wait_book_page的异常块中,确保即使driver未成功初始化,也尝试清理相关进程; - 可以在脚本开头添加清理残留Chromedriver进程的逻辑,比如执行
ps aux | grep chromedriver | grep -v grep | awk '{print $2}' | xargs kill -9(注意只清理当前脚本产生的进程,避免误杀其他进程)。
内容的提问来源于stack exchange,提问作者Hibanana
相关产品推荐
相关产品推荐

