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

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'

初步判断报错是代码未成功关联首次启动的浏览器窗口,反而尝试打开新窗口触发,目标是实现首个浏览器窗口持续打开、循环任务中复用同一浏览器实例。

问题根因
  1. init_driver函数将所有初始化逻辑包裹在try-except块中,只要初始化过程抛出异常,函数没有显式返回值,默认返回None,后续调用driver.get()时自然触发NoneType属性错误
  2. 驱动初始化逻辑写在check_for_tasks的任务分支内,外层无限循环每次轮询到存在任务时,都会重新执行初始化逻辑,没有做实例复用判断,必然重复触发驱动启动,和复用窗口的目标相悖
  3. 存在两处逻辑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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 00:06:32