Jupyter Notebook与SeleniumBase:交互式Web自动化测试与数据抓取实战

Jupyter Notebook与SeleniumBase:交互式Web自动化测试与数据抓取实战
1. 项目概述为什么要在Jupyter里玩转SeleniumBase如果你做过Web自动化测试或者数据抓取大概率对Selenium不陌生。但传统Selenium脚本的开发调试流程常常让人头疼写一段代码运行看报错改代码再运行……这种“黑盒”循环效率低下尤其是在定位元素、调试交互逻辑时一个微小的定位器错误就可能让你折腾半天。而Jupyter Notebook的交互式、单元格执行特性恰好能把这个“黑盒”变成“透明盒”。你可以逐行、逐块地执行代码实时查看浏览器状态、页面元素和变量值调试体验直接提升几个数量级。SeleniumBase在此基础上更进一步。它不是一个简单的Selenium封装而是一个全功能的测试框架自带断言库、报告生成、自动等待、可视化命令等“瑞士军刀”。当SeleniumBase遇上Jupyter产生的化学反应是交互式Web测试开发。这意味着你可以像做数据分析一样以探索式、迭代式的方法来构建和调试你的浏览器自动化脚本。无论是快速验证一个登录流程还是调试一个复杂的拖拽验证码绕过逻辑你都可以在Jupyter中即时看到每一步的效果并立即调整。这个组合特别适合几类人测试开发工程师可以快速原型化测试用例数据工程师或分析师需要交互式地探索和抓取网页结构爬虫开发者用于调试反爬策略和交互逻辑以及任何想学习或教学Web自动化的人因为整个过程可视化、可中断、可追溯。2. 环境搭建在Jupyter中配置SeleniumBase实战在Jupyter中使用SeleniumBase核心是解决浏览器驱动和显示问题。本地Jupyter和云环境如Google Colab的配置略有不同我们分别来看。2.1 本地Jupyter环境配置本地环境的好处是网络稳定资源可控。假设你已安装Python和Jupyter。首先安装SeleniumBase。打开你的终端或Jupyter Notebook内的代码单元格使用pip安装。我强烈建议创建一个独立的虚拟环境来做这件事避免包冲突。# 创建并激活虚拟环境以conda为例venv同理 conda create -n seleniumbase-jupyter python3.9 conda activate seleniumbase-jupyter # 安装SeleniumBase。它会自动处理ChromeDriver等依赖。 pip install seleniumbase安装完成后启动Jupyter Notebook。jupyter notebook在第一个单元格中尝试导入SeleniumBase并启动一个最简单的浏览器会话验证环境是否正常。from seleniumbase import SB with SB() as sb: sb.open(https://www.baidu.com) print(页面标题是:, sb.get_title()) sb.save_screenshot(baidu_homepage.png)如果运行后能打印出“百度一下你就知道”并且当前目录下生成了截图baidu_homepage.png那么恭喜本地基础环境就绪。注意首次运行SB()时SeleniumBase可能会自动下载匹配你Chrome浏览器版本的chromedriver。如果遇到驱动问题可以手动指定驱动路径或者使用SB(ucTrue)参数它会尝试使用undetected-chromedriver兼容性更好。2.2 云端Colab环境配置详解在Google Colab上运行意味着你拥有一个随时可得的、带GPU的Linux环境但需要解决无图形界面和浏览器安装的问题。上面的参考文章给出了一个配置示例我们可以将其优化得更健壮。在Colab的新笔记本中第一个单元格执行以下命令来搭建环境# 1. 更新系统包并安装必要的图形和字体库为可能的可视化或截图需求做准备 !sudo apt-get update !sudo apt-get install -y wget gnupg unzip xvfb x11-utils fonts-noto-color-emoji # 2. 安装Google Chrome稳定版 !wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - !echo deb [archamd64] http://dl.google.com/linux/chrome/deb/ stable main | sudo tee /etc/apt/sources.list.d/google-chrome.list !sudo apt-get update !sudo apt-get install -y google-chrome-stable # 3. 获取ChromeDriver版本号确保与安装的Chrome版本匹配是关键 import re, subprocess # 获取已安装的Chrome版本 chrome_version_output subprocess.check_output([google-chrome-stable, --version]).decode() chrome_version re.search(r(\d\.\d\.\d\.\d), chrome_version_output).group(1) major_version chrome_version.split(.)[0] print(f已安装Chrome版本: {chrome_version}, 主版本号: {major_version}) # 4. 下载对应版本的ChromeDriver !wget -q https://storage.googleapis.com/chrome-for-testing-public/{major_version}.0.0/linux64/chromedriver-linux64.zip !unzip -q chromedriver-linux64.zip !mv chromedriver-linux64/chromedriver /usr/local/bin/ !chmod x /usr/local/bin/chromedriver # 5. 安装SeleniumBase及其可能用到的额外包如用于OCR的pytesseract !pip install seleniumbase pillow pytesseract opencv-python-headless -q这个脚本比简单的一句!pip install seleniumbase更可靠因为它显式地控制了Chrome和ChromeDriver的版本匹配这是云端环境最常见的问题根源。接下来在代码单元格中我们需要处理Colab无显示的问题。通常我们使用headless无头模式但有些交互测试可能需要虚拟显示缓冲区Xvfb。import os from seleniumbase import SB # 启动虚拟显示如果后续需要非严格的headless模式可以启用 # from pyvirtualdisplay import Display # display Display(visible0, size(1920, 1080)) # display.start() # 使用SeleniumBase设置ucTrue和headlessTrue是Colab下的最佳实践 with SB(ucTrue, headlessTrue, browserchrome) as sb: sb.open(https://www.google.com) title sb.get_title() print(f页面标题: {title}) # 截图会自动保存在/content目录下Colab的工作目录 sb.save_screenshot(google_in_colab.png) print(截图已保存。)实操心得在Colab中ucTrue参数几乎总是需要的。它启用了undetected-chromedriver能更好地模拟真实浏览器环境避免被一些网站的基础反爬检测到。另外所有生成的文件截图、日志默认在/content目录你可以使用Colab的文件浏览器侧边栏查看和下载。3. 核心交互Jupyter单元格与SeleniumBase的协同工作流Jupyter的核心优势是“单元格”。我们将自动化脚本分解成逻辑块每个块完成一个特定任务并立即看到结果。这彻底改变了测试脚本的开发方式。3.1 探索式元素定位与断言传统脚本中写一个find_element如果定位失败脚本就停了。在Jupyter里你可以先交互式地“探索”页面。假设你要测试一个登录页面。首先打开页面。# 单元格 1: 打开登录页 with SB(ucTrue, headlessFalse) as sb: # 本地调试时可设headlessFalse看浏览器操作 sb.open(https://example.com/login) sb.save_screenshot(login_page.png) # 不要退出我们保留sb对象在下一个单元格使用等等这样不行。注意with SB() as sb:上下文管理器退出时浏览器会关闭。为了交互我们需要手动管理浏览器生命周期。这是Jupyter中使用SeleniumBase的一个关键技巧。# 单元格 1: 启动浏览器并打开页面 from seleniumbase import SB import time # 手动实例化SB对象不使用上下文管理器以便跨单元格保持会话 sb SB(ucTrue, headlessFalse, demo_modeTrue) # demo_mode会让操作变慢并高亮显示 sb.open(https://example.com/login) print(浏览器已打开页面加载完毕。) # sb对象现在存在于内核内存中我们可以在后续单元格中使用它。# 单元格 2: 探索页面元素 # 检查页面标题 print(当前标题:, sb.get_title()) # 尝试查找用户名输入框使用多种定位策略 try: # 方法1: 通过CSS Selector username_css sb.find_element(input#username, timeout5) print(通过CSS Selector找到用户名输入框。) except Exception as e: print(fCSS Selector定位失败: {e}) try: # 方法2: 通过XPath username_xpath sb.find_element(//input[nameusername], timeout5) print(通过XPath找到用户名输入框。) except Exception as e: print(fXPath定位失败: {e}) # 更直观的方法使用SeleniumBase的演示模式自动高亮元素 sb.demo_mode True # 如果初始化时未开启 sb.highlight(input#username) # 这会在页面上高亮该元素方便肉眼确认 time.sleep(2) # 暂停一下看看高亮效果# 单元格 3: 执行登录操作 # 假设我们确认了定位器是 input#username 和 input#password sb.type(input#username, test_user) sb.type(input#password, test_pass_123) sb.click(button[typesubmit]) # 点击登录按钮 # 等待登录后页面跳转或元素出现 sb.wait_for_element(#dashboard, timeout10) print(登录成功进入仪表盘。) sb.save_screenshot(after_login.png)# 单元格 4: 进行断言验证 # 验证登录后URL包含特定路径 assert dashboard in sb.get_current_url(), 登录后未跳转到仪表盘页面 print(URL断言通过。) # 验证页面中存在欢迎文本 welcome_text sb.get_text(h1.welcome-msg) assert test_user in welcome_text, f欢迎信息中未包含用户名实际内容: {welcome_text} print(文本断言通过。) # 验证某个元素是可见的例如退出按钮 assert sb.is_element_visible(a#logout), 退出按钮不可见 print(元素可见性断言通过。)# 单元格 5: 清理与退出 # 所有调试完成后记得关闭浏览器释放资源 sb.quit() print(浏览器会话已结束。)这种工作流让你能步步为营每一步都验证结果极大降低了调试复杂度。demo_mode和highlight函数是你的“可视化调试器”。3.2 实时调试与状态检查当操作未按预期发生时你需要检查当前页面状态。SeleniumBase在Jupyter里提供了几个利器。获取页面源代码sb.get_page_source()。你可以将其输出到单元格或者保存为HTML文件后打开查看。执行JavaScriptsb.execute_script(“return document.readyState”)或sb.execute_script(“return arguments[0].innerHTML”, element)来获取动态内容。检查浏览器日志sb.get_log(‘browser’)可以获取控制台日志对调试前端错误很有帮助。例如在点击一个按钮后页面没反应你可以插入一个单元格# 调试单元格检查控制台错误和网络状态通过JS console_logs sb.get_log(browser) for entry in console_logs: if entry[level] SEVERE: print(f发现严重错误: {entry[message]}) # 检查是否有JS错误导致的元素属性问题 element sb.find_element(#myButton) is_disabled sb.execute_script(return arguments[0].disabled;, element) print(f按钮禁用状态: {is_disabled})4. 高级应用构建可复用的测试模块与数据驱动在Jupyter中探索和调试成功后你会得到一堆有效的代码单元格。下一步就是将它们组织成可复用的函数或类便于集成到正式的测试套件中。4.1 将探索代码模块化我们以登录测试为例创建一个模块。# 在Jupyter的一个单元格中定义一个测试类或函数 class LoginTest: def __init__(self, sb_instance): self.sb sb_instance def navigate_to_login(self, url): self.sb.open(url) self.sb.wait_for_element_ready(body) def perform_login(self, username, password): self.sb.type(input#username, username) self.sb.type(input#password, password) self.sb.click(button[typesubmit]) def verify_login_success(self, expected_username): self.sb.wait_for_element(#dashboard, timeout10) welcome_text self.sb.get_text(h1.welcome-msg) assert expected_username in welcome_text, f登录验证失败欢迎语: {welcome_text} print(f用户 {expected_username} 登录验证成功。) def run_full_test(self, url, username, password): self.navigate_to_login(url) self.perform_login(username, password) self.verify_login_success(username)然后在另一个单元格中实例化并运行# 初始化浏览器和测试对象 sb SB(ucTrue, headlessTrue) # 生产环境用headless test LoginTest(sb) try: test.run_full_test(https://example.com/login, alice, securePass123) print(完整登录测试通过) except Exception as e: print(f测试失败: {e}) sb.save_screenshot(test_failure.png) # 失败时自动截图 finally: sb.quit()4.2 利用Jupyter进行数据驱动测试探索数据驱动测试DDT意味着用多组数据运行同一测试逻辑。在Jupyter中你可以用Pandas轻松管理测试数据并直观地看到每组数据的结果。首先在一个单元格中准备测试数据可以用列表、字典或直接读取CSV。import pandas as pd test_data [ {username: alice, password: correct_pw, should_pass: True}, {username: alice, password: wrong_pw, should_pass: False}, {username: , password: correct_pw, should_pass: False}, # 空用户名 {username: alice, password: , should_pass: False}, # 空密码 ] df pd.DataFrame(test_data) df接下来编写一个灵活的测试函数能处理成功和失败的预期。def data_driven_login_test(sb, url, username, password, expected_success): 执行单次登录并根据预期验证结果 sb.open(url) sb.type(input#username, username) sb.type(input#password, password) sb.click(button[typesubmit]) if expected_success: # 预期成功等待跳转到成功页面 try: sb.wait_for_element(#dashboard, timeout5) print(f用例 [{username}/{password}] 通过 (如预期成功)) return True except Exception: print(f用例 [{username}/{password}] 失败 (预期成功但未登录)) sb.save_screenshot(ffail_expected_success_{username}.png) return False else: # 预期失败应停留在登录页或有错误提示 try: # 检查是否出现了错误提示元素 sb.wait_for_element(.error-message, timeout5) print(f用例 [{username}/{password}] 通过 (如预期失败并显示错误)) return True except Exception: # 如果没有错误提示但也没有跳转到成功页也可能算通过取决于业务 # 这里简单检查URL是否还是登录页 if login in sb.get_current_url(): print(f用例 [{username}/{password}] 通过 (如预期失败停留在登录页)) return True else: print(f用例 [{username}/{password}] 失败 (预期失败但似乎登录成功了)) sb.save_screenshot(ffail_expected_failure_{username}.png) return False最后在一个循环中运行所有测试用例。这里有个关键点对于数据驱动测试通常每个用例需要一个干净的浏览器会话。我们可以在循环内为每组数据重新启动浏览器。results [] for index, row in df.iterrows(): print(f\n--- 开始测试用例 {index1} ---) # 为每个用例创建新的浏览器实例确保隔离 sb SB(ucTrue, headlessTrue) try: test_passed data_driven_login_test( sb, https://example.com/login, row[username], row[password], row[should_pass] ) results.append(test_passed) finally: sb.quit() # 确保每个用例结束后关闭浏览器 # 汇总结果 df[实际结果] [通过 if r else 失败 for r in results] df[测试通过] results print(\n 数据驱动测试汇总 ) print(df)在Jupyter中运行这段代码你会得到一个清晰的结果表格哪组数据通过哪组失败一目了然。这种交互式探索对于设计测试用例边界值非常有效。5. 集成外部工具与处理复杂场景Web测试中常遇到验证码、文件上传、下拉异步加载等复杂场景。Jupyter的交互性让你可以方便地集成各种Python库来应对。5.1 处理验证码以滑动验证为例参考文章中提到了用YOLO模型识别滑动验证码。在Jupyter中我们可以更细致地拆解这个过程。这里我们简化流程重点展示交互式调试的思路。假设我们需要绕过一个滑动验证码。步骤是1) 截图验证码区域2) 图像识别计算滑动距离3) 生成拟人滑动轨迹4) 执行滑动。# 单元格1 打开带验证码的页面并截取验证码图片 from seleniumbase import SB import time sb SB(ucTrue, headlessFalse, demo_modeTrue) # 调试时关闭无头模式 sb.open(https://example.com/with-captcha) time.sleep(3) # 等待页面和验证码加载 # 定位验证码区域。这里假设验证码在一个iframe里并且有特定ID。 sb.switch_to_frame(captcha-iframe) captcha_element sb.find_element(#captcha-image) # 使用SeleniumBase的save_element_as_image_file方法如果可用或者用element.screenshot captcha_element.screenshot(captcha_screenshot.png) sb.switch_to_default_content() print(验证码截图已保存。)# 单元格2 使用图像处理库如OpenCV分析截图计算滑动距离 import cv2 import numpy as np # 这是一个极度简化的示例。真实情况需要模板匹配或机器学习模型。 # 假设我们已知滑块缺口的模板图template.png img cv2.imread(captcha_screenshot.png) template cv2.imread(template.png) # 你事先准备好的缺口小图 h, w template.shape[:2] # 使用模板匹配 result cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc cv2.minMaxLoc(result) # max_loc是匹配位置左上角我们通常需要滑块的移动距离 # 假设缺口在匹配位置的右侧某个固定偏移量 slide_distance max_loc[0] some_offset - slider_start_x # 需要根据实际页面计算 print(f计算出的滑动距离像素: {slide_distance}) # 在图上标出匹配区域方便视觉验证Jupyter中可以直接显示 img_with_rect img.copy() top_left max_loc bottom_right (top_left[0] w, top_left[1] h) cv2.rectangle(img_with_rect, top_left, bottom_right, (0, 0, 255), 2) cv2.imwrite(captcha_with_match.png, img_with_rect) print(匹配结果已标注并保存请检查captcha_with_match.png确认计算是否正确。)# 单元格3 生成拟人化滑动轨迹并执行 import random def generate_human_track(distance): 生成模拟人手滑动的轨迹列表一系列x轴偏移量 track [] current 0 # 轨迹模拟先加速后减速 mid distance * 0.6 t 0.02 # 每小段的时间间隔 v 0 # 初始速度 while current distance: if current mid: a random.uniform(2, 4) # 加速段加速度 else: a -random.uniform(3, 6) # 减速段减速度 v0 v v v0 a * t s v0 * t 0.5 * a * t * t s round(s) current s if current distance: s distance - sum(track) # 最后一段调整到精确距离 current distance track.append(s) # 加入一些微小的随机抖动使其更不像机器 track [x random.randint(-1, 1) for x in track] return track slide_track generate_human_track(slide_distance) print(f生成的滑动轨迹每步位移: {slide_track[:10]}... 共{len(slide_track)}步) # 只打印前10步# 单元格4 执行滑动操作 from selenium.webdriver.common.action_chains import ActionChains # 定位滑块按钮 slider_knob sb.find_element(.slider-button) action ActionChains(sb.driver) action.click_and_hold(slider_knob).perform() for move_x in slide_track: # 加入微小的垂直抖动 move_y random.randint(-1, 1) action.move_by_offset(move_x, move_y).perform() action.pause(random.uniform(0.005, 0.02)) # 随机暂停模拟人手停顿 action.release().perform() print(滑动操作执行完毕。) time.sleep(2) # 等待结果验证 # 检查是否成功例如错误提示消失或成功信息出现 if sb.is_element_visible(.success-text): print(验证码绕过成功) else: print(验证码可能未通过。) sb.save_screenshot(after_slide_failed.png)在Jupyter中你可以反复执行单元格2和3调整图像识别参数或轨迹生成算法直到captcha_with_match.png显示匹配正确且滑动轨迹看起来自然。这种即时反馈的调试方式是处理这类复杂、需要调参的任务的绝佳选择。5.2 文件上传与下载处理文件上传是Web测试中的另一个常见难点。SeleniumBase提供了choose_file方法简化了操作。# 单元格 测试文件上传 sb.open(https://example.com/upload) upload_element sb.find_element(input[typefile]) # 使用choose_file方法参数是选择器或元素和本地文件路径 sb.choose_file(input[typefile], /path/to/your/test_file.pdf) print(文件路径已填入。) # 有时页面是自定义的上传按钮需要先点击触发系统文件选择框。 # 对于这种情况通常需要借助AutoIT或PyAutoGUI但在无头环境中很麻烦。 # 更佳实践是让开发在测试环境中提供直接设置input值的接口或者使用send_keys。 # sb.find_element(“input[type‘file’]”).send_keys(“/path/to/file”) 是Selenium标准做法SeleniumBase的choose_file内部也是这么做的。 # 等待上传完成可能有个进度条或成功提示 sb.wait_for_element(.upload-success, timeout30) print(文件上传成功提示出现。)对于文件下载需要设置浏览器偏好设置指定下载路径并监控该路径下文件是否出现。# 单元格 配置下载并触发下载 from seleniumbase import SB import os download_dir /tmp/selenium_downloads if not os.path.exists(download_dir): os.makedirs(download_dir) # 通过ChromeOptions设置下载参数SeleniumBase内部处理 sb SB(ucTrue, headlessTrue, download_folderdownload_dir) # 使用download_folder参数 sb.open(https://example.com/download-report) sb.click(#download-csv-button) # 等待文件下载完成这是一个简化方法实际中可能需要轮询目录 import time timeout 30 start_time time.time() file_found False while time.time() - start_time timeout: files os.listdir(download_dir) if files and any(f.endswith(.csv) for f in files): downloaded_file [f for f in files if f.endswith(.csv)][0] print(f文件已下载: {downloaded_file}) file_found True break time.sleep(1) if not file_found: print(文件下载可能未在指定时间内完成。)6. 问题排查、性能优化与最佳实践即使环境搭好了脚本写好了在实际运行中还是会遇到各种“坑”。下面是一些常见问题的排查思路和优化建议。6.1 常见问题与解决方案速查表问题现象可能原因排查步骤与解决方案ElementNotVisibleException或NoSuchElementException1. 元素尚未加载完成。2. 元素在iframe或shadow DOM内。3. 定位器写错了。1. 使用sb.wait_for_element_visible(selector, timeout)替代find_element。2. 检查并切换到正确的iframesb.switch_to_frame(frame_id_or_index)。3. 在Jupyter中用sb.highlight(selector)或sb.is_element_present(selector)验证定位器。脚本在本地运行正常在Colab/服务器失败1. 浏览器驱动版本不匹配。2. 无头模式或屏幕分辨率差异导致元素不可交互。3. 网络环境或防火墙限制。1. 确保Colab中Chrome与ChromeDriver版本严格匹配见2.2节脚本。2. 尝试设置浏览器窗口大小sb.set_window_size(1920, 1080)。或在Colab中先禁用headless模式调试。3. 使用ucTrue参数并增加sb.open()后的等待时间sb.sleep(5)。操作如点击、输入没有效果1. 元素被遮挡。2. 页面有未关闭的弹窗如cookie同意。3. 元素是只读或禁用的。1. 使用sb.scroll_to(selector)滚动到元素可见区域。2. 在操作前先检查并关闭可能的弹窗。3. 用JS检查元素状态sb.execute_script(“return arguments[0].disabled”, element)。验证码或反爬机制触发网站检测到自动化脚本特征。1.始终使用ucTrue这是最重要的措施。2. 启用--disable-blink-featuresAutomationControlled等选项SeleniumBase的SB已内置部分。3. 模拟人类行为随机延迟、移动鼠标轨迹、使用不同的User-Agent可通过sb.driver.execute_cdp_cmd设置。性能慢脚本运行时间长1. 隐式或显式等待时间设置过长。2. 不必要的页面加载或资源请求。3. 网络延迟。1. 使用显式等待sb.wait_for_*而非固定sleep并设置合理的超时时间。2. 通过CDP命令禁用图片、CSS等sb.driver.execute_cdp_cmd(‘Network.setBlockedURLs’, {“urls”: [“*.jpg”, “*.png”, “*.css”]})谨慎使用可能影响页面布局。3. 考虑在本地或网络更好的环境运行。Jupyter内核崩溃或浏览器无响应1. 浏览器未正确关闭资源泄露。2. 内存不足尤其在Colab免费版。1.务必使用try...finally或在单元格最后调用sb.quit()。2. 对于长时间运行的脚本定期重启浏览器会话。3. 在Colab中检查运行时类型升级到高RAM版本。6.2 Jupyter专用最佳实践与性能优化分而治之善用单元格将长脚本按功能拆分成多个单元格。例如环境检查、登录模块、业务测试模块、清理模块。这样某个模块出错只需重新运行该单元格及其后续单元格无需从头开始。状态持久化与恢复有时测试中间状态很重要。可以利用Jupyter的变量持久性在关键步骤后将状态如cookies、URL保存到变量甚至导出为文件。# 保存cookies cookies sb.driver.get_cookies() import pickle with open(session_cookies.pkl, wb) as f: pickle.dump(cookies, f) # 在另一个会话中恢复 sb.open(https://example.com) # 先打开域名 for cookie in cookies: sb.driver.add_cookie(cookie) sb.refresh() # 刷新页面此时应处于登录状态可视化调试辅助除了demo_mode和highlight在复杂的鼠标操作如拖放前可以用sb.save_screenshot(“before_action.png”)保存状态操作后再保存一张方便对比。利用Magics命令Jupyter的%time、%%timeit魔法命令可以帮你测量某段代码或某个单元格的执行时间用于性能分析。%%timeit # 这个单元格的代码会被多次运行以计算平均耗时 sb.open(“https://example.com”) sb.wait_for_element(“body”)优雅地处理中断在Jupyter中按“停止”按钮中断执行时浏览器进程可能不会自动退出。建议在笔记本开头添加一个清理单元格或使用atexit模块注册清理函数。import atexit browsers_to_cleanup [] def cleanup_browsers(): for sb_instance in browsers_to_cleanup: try: sb_instance.quit() except: pass atexit.register(cleanup_browsers) # 之后每次创建SB实例都加入列表 sb SB(headlessTrue) browsers_to_cleanup.append(sb)将SeleniumBase与Jupyter Notebook结合本质上是在创造一个高度可视化和可交互的Web自动化“实验室”。它把测试脚本开发从“编写-编译-运行-看日志”的线性流程变成了“探索-验证-调整-固化”的迭代式循环。对于快速原型验证、复杂交互调试、数据驱动测试用例设计以及自动化测试教学来说这套组合的效率提升是颠覆性的。我个人的习惯是任何新的Web自动化任务都会先在Jupyter里用SeleniumBase跑通所有关键步骤和异常分支确保逻辑无误后再将打磨好的代码模块迁移到正式的pytest或unittest测试套件中。这个过程能帮你避开至少80%因环境、定位和异步加载导致的诡异问题。