Python Selenium复用同一Chrome浏览器窗口方案及报错解决
问题背景
当前使用Python结合Selenium循环轮询服务器执行指定任务,为提升处理效率做了两项优化:
- Chrome驱动初始化时配置
options.add_argument(f"user-data-dir={script_directory}\\profile"),指定固定用户数据目录,避免每次运行重复登录 - 尝试复用同一个浏览器窗口,替代原有每次执行任务就关闭后重新打开浏览器的逻辑
原有实现代码
#!/usr/bin/env python import pathlib import time import urllib.parse import requests from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By USER = "..." PASS = "..." def upload_to_server(link, redirect, unique_hash): try: requests.get( "https://www.example.com/crons.php?cronUploadToServer=1&link={0}&redirect={1}&hash={2}".format(link, redirect, unique_hash)) except Exception as e: print(e) def download_from_server(): try: server = requests.get("https://www.example.com/crons.php?cronDownloadFromServer=1") return server.text.strip() except Exception as e: print(e) # 销毁Chrome实例 def tear_down(_driver): _driver.quit() _driver.close() def check_for_tasks(): if download_from_server() == "NO_TASKS": print("--> NO TASKS") else: # 初始化Chrome驱动 def init_driver(using_linux, proxy): script_directory = pathlib.Path().absolute() try: options = Options() options.headless = False options.add_argument('start-maximized') options.add_argument('--disable-popup-blocking') options.add_argument('--disable-notifications') options.add_argument('--log-level=3') options.add_argument('--ignore-certificate-errors') options.add_argument('--ignore-ssl-errors') options.add_argument(f"user-data-dir={script_directory}\\profile") options.add_experimental_option("excludeSwitches", ["enable-automation"]) options.add_experimental_option("detach", True) prefs = {'profile.default_content_setting_values.notifications': 2} options.add_experimental_option('prefs', prefs) if proxy == "0.0.0.0:0": print("--> PROXY DISABLED ...") else: print("--> PROXY: " + str(proxy) + " ...") options.add_argument('--proxy-server=%s' % proxy) if using_linux: return webdriver.Chrome(options=options) else: return webdriver.Chrome(options=options) except Exception as e: print(e) # 创建会话 driver = init_driver(False, "0.0.0.0:00") # 访问起始地址 driver.get('https://www.example.com/logon') # 跳转链接逻辑 def topcashback_click(_driver): try: _driver.get('https://www.example.com/Earn.aspx?mpurl=shein&mpID=17233') if "redirect.aspx?mpurl=shein" in _driver.current_url: return _driver.current_url else: return False except Exception as e: print(e) # 检查是否已登录 if ">Account</span>" in driver.page_source: print("--> LOGGED IN (ALREADY) ...") driver.get('https://www.SITE.CO.UK/Earn.aspx?mpurl=shein&mpID=17233') try: server = download_from_server() data_from_server = server.split('|') link = topcashback_click(driver) print("--> LINK --> " + link) time.sleep(4) if link != driver.current_url: print("--> LINK (REDIRECT) --> " + driver.current_url) upload_to_server(urllib.parse.quote_plus(link), urllib.parse.quote_plus( driver.current_url.replace('https://www.example.com', data_from_server[0])), data_from_server[1]) print("--> LINK UPLOADED TO THE DB ...") except Exception as e: print(e) else: # 首次登录逻辑 def topcashback_login(_driver): _driver.get('https://www.example.com/logon') time.sleep(1) _driver.find_element(By.XPATH, '//*[@id="txtEmail"]').send_keys(USER) time.sleep(1) _driver.find_element(By.XPATH, '//*[@id="loginPasswordInput"]').send_keys(PASS) time.sleep(1) _driver.find_element(By.XPATH, '//*[@id="Loginbtn"]').click() time.sleep(5) if ">Account</span>" in _driver.page_source: return True else: return False def topcashback_click(_driver): try: _driver.get('https://www.SITE.CO.UK/Earn.aspx?mpurl=shein&mpID=17233') if "redirect.aspx?mpurl=shein" in _driver.current_url: return _driver.current_url else: return False except Exception as e: print(e) if topcashback_login(driver): try: print("--> LOGGED IN ...") server = download_from_server() data_from_server = server.split('|') link = topcashback_click(driver) print("--> LINK --> " + link) time.sleep(4) if link != driver.current_url: print("--> LINK (REDIRECT) --> " + driver.current_url) upload_to_server(urllib.parse.quote_plus(link), urllib.parse.quote_plus( driver.current_url.replace('https://www.example.com', data_from_server[0])), data_from_server[1]) print("--> LINK UPLOADED TO THE DB ...") except Exception as e: print(e) else: print("--> ERROR --> DEBUG TIME ...") tear_down(driver) if __name__ == "__main__": while True: check_for_tasks() time.sleep(2)
故障现象
第二项优化运行时抛出如下错误:
driver.get('https://www.example.com/logon') AttributeError: 'NoneType' object has no attribute 'get'
初步判断报错是代码未成功关联首次启动的浏览器窗口,反而尝试打开新窗口触发,目标是实现首个浏览器窗口持续打开、循环任务中复用同一浏览器实例。
问题根因
init_driver函数将所有初始化逻辑包裹在try-except块中,只要初始化过程抛出异常,函数没有显式返回值,默认返回None,后续调用driver.get()时自然触发NoneType属性错误- 驱动初始化逻辑写在
check_for_tasks的任务分支内,外层无限循环每次轮询到存在任务时,都会重新执行初始化逻辑,没有做实例复用判断,必然重复触发驱动启动,和复用窗口的目标相悖 - 存在两处逻辑bug直接导致初始化失败:一是配置了
detach=True,驱动退出后浏览器不会自动关闭,残留进程会锁死指定的user-data-dir目录,下次初始化直接失败返回None;二是tear_down方法先调用_driver.quit()终止浏览器进程,再调用_driver.close()会二次抛出错误。
修复方案
- 将driver初始化逻辑移到循环外层,全局只执行一次初始化,每次轮询任务先判断driver实例是否存活,仅在实例异常退出时才重新初始化
- 调整
init_driver的异常处理逻辑,初始化失败直接抛出错误,不要吞掉异常静默返回None;移除detach=True配置,避免残留进程锁死用户数据目录 - 修正
tear_down方法的执行顺序,先关闭窗口再退出驱动,避免二次报错 - 每次执行任务前校验driver当前会话是否有效,会话失效再重建实例,禁止每次轮询都新建驱动实例
核心修改后的代码框架参考:
# 全局声明驱动实例,移到循环外 driver = None def init_driver(using_linux, proxy): script_directory = pathlib.Path().absolute() options = Options() options.headless = False options.add_argument('start-maximized') options.add_argument('--disable-popup-blocking') options.add_argument('--disable-notifications') options.add_argument('--log-level=3') options.add_argument('--ignore-certificate-errors') options.add_argument('--ignore-ssl-errors') options.add_argument(f"user-data-dir={script_directory}\\profile") options.add_experimental_option("excludeSwitches", ["enable-automation"]) # 移除detach配置避免锁死用户数据目录 prefs = {'profile.default_content_setting_values.notifications': 2} options.add_experimental_option('prefs', prefs) if proxy != "0.0.0.0:0": print("--> PROXY: " + str(proxy) + " ...") options.add_argument('--proxy-server=%s' % proxy) else: print("--> PROXY DISABLED ...") return webdriver.Chrome(options=options) def tear_down(_driver): # 修正执行顺序,避免二次报错 try: _driver.close() except: pass _driver.quit() def get_valid_driver(): global driver # 校验实例是否有效 if driver is None: driver = init_driver(False, "0.0.0.0:00") return driver try: # 测试会话是否存活 driver.current_url return driver except: tear_down(driver) driver = init_driver(False, "0.0.0.0:00") return driver if __name__ == "__main__": while True: # 每次获取有效驱动实例,不会重复新建 current_driver = get_valid_driver() check_for_tasks(current_driver) time.sleep(2)
内容的提问来源于stack exchange,提问作者logosapp
相关产品推荐
相关产品推荐

