You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Selenium多线程异常:两个WebDriver实例仅单实例执行操作

多线程下Selenium WebDriver实例冲突问题排查与解决

我编写了一个用于创建Selenium WebDriver实例并爬取数据的类,单线程运行一切正常,但切换到多线程后,所有操作仅在一个浏览器中执行,另一个浏览器虽启动但无任何动作。

多线程代码

def run_downloader(account, date_range, company_name, invoice_type):
    downloader = InvoiceDownloaderEnchanted(account=account,
                                            date_range=date_range,
                                            company_name=company_name,
                                            invoice_type=invoice_type)
    downloader.start_webdriver()
    downloader.process()


if __name__ == '__main__':
    with ThreadPoolExecutor(max_workers=2) as executor:
        executor.map(run_downloader,
                     repeat((cfg.USERNAME, cfg.PASSWORD)),
                     repeat(('01/08/2022', '31/08/2022')),
                     repeat('Company Name'),
                     [InvoiceType.INWARD, InvoiceType.OUTWARD])

WebDriver类初始化代码

class InvoiceDownloaderEnchanted:
    def __init__(self,
                 account,
                 company_name: str,
                 invoice_type: InvoiceType,
                 date_range: tuple | None = None,
                 target_url: str | None = cfg.WEBSITE_URL
                 ):
        self.account = account
        self.company_name = company_name
        self.invoice_type = invoice_type
        self.date_range = date_range
        self.target_url = target_url
        self.driver = None
       
    def start_webdriver(self):
        chrome_options = webdriver.ChromeOptions()
        chrome_options.add_argument("--disable-gpu")
        chrome_prefs = cfg.CHROME_PREFS
        chrome_prefs['download.default_directory'] = self.download_folder
        print(chrome_prefs['download.default_directory'])
        chrome_options.add_experimental_option('prefs', chrome_prefs)
        chrome_options.add_experimental_option("excludeSwitches", ['enable-automation'])
        chrome_options.add_argument('--disable-blink-features=AutomationControlled')
        chrome_options.add_argument("--remote-debugging-port=9222")
        user_agent = UserAgent().random
        chrome_options.add_argument(f'user-agent={user_agent}')
        self.driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()),
                                       options=chrome_options)
        print(self.driver)
        self.driver.maximize_window()

    def process(self):
        # Do scrapping stuffs

调试发现的问题

  • 打印chrome_prefs['download.default_directory']时两个实例的下载目录不同(符合预期),但chrome_options.experimental_options中的该目录却相同。
  • 两个WebDriver实例的driver.capabilities['chrome']['userDataDir']不同:
<selenium.webdriver.chrome.webdriver.WebDriver (session="93cef277d1796c241cb75f729dec222f")>
<selenium.webdriver.chrome.webdriver.WebDriver (session="de08b1c6baad30587717082e41c294b5")>
  • 有时运行代码时甚至只打开一个浏览器。怀疑是两个WebDriver实例引用了同一个变量,但不知道如何解决。

问题根源

  1. 共享配置对象:chrome_prefs = cfg.CHROME_PREFS直接引用全局配置字典,多线程下修改该字典会导致所有线程共享同一实例,造成配置污染。
  2. 固定远程调试端口:--remote-debugging-port=9222指定固定端口,第二个Chrome实例无法绑定该端口,导致WebDriver会话初始化失败,最终所有操作落到第一个实例上。

解决方法

  • 复制配置字典:创建全局配置的副本,避免线程间干扰:
    # 替换原chrome_prefs = cfg.CHROME_PREFS
    chrome_prefs = cfg.CHROME_PREFS.copy()
    chrome_prefs['download.default_directory'] = self.download_folder
    
  • 移除或动态分配远程调试端口:
    • 若不需要远程调试功能,直接删除该行:
      # chrome_options.add_argument("--remote-debugging-port=9222")
      
    • 若需要远程调试,动态生成可用端口:
      import socket
      
      def get_free_port():
          with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
              s.bind(('', 0))
              return s.getsockname()[1]
      
      # 在start_webdriver中使用
      chrome_options.add_argument(f"--remote-debugging-port={get_free_port()}")
      
  • 确保线程完全隔离:每个线程的InvoiceDownloaderEnchanted实例不共享任何全局或类级变量。

修改后的start_webdriver方法示例

def start_webdriver(self):
    chrome_options = webdriver.ChromeOptions()
    chrome_options.add_argument("--disable-gpu")
    # 复制全局配置,避免线程污染
    chrome_prefs = cfg.CHROME_PREFS.copy()
    chrome_prefs['download.default_directory'] = self.download_folder
    print(chrome_prefs['download.default_directory'])
    chrome_options.add_experimental_option('prefs', chrome_prefs)
    chrome_options.add_experimental_option("excludeSwitches", ['enable-automation'])
    chrome_options.add_argument('--disable-blink-features=AutomationControlled')
    # 移除固定端口或使用动态端口
    # chrome_options.add_argument(f"--remote-debugging-port={get_free_port()}")
    user_agent = UserAgent().random
    chrome_options.add_argument(f'user-agent={user_agent}')
    self.driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()),
                                   options=chrome_options)
    print(self.driver)
    self.driver.maximize_window()

内容的提问来源于stack exchange,提问作者dinhit

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 05:05:25