Selenium WebDriver自动化测试与爬虫实战:从元素定位到反反爬策略
1. 项目概述:为什么我们需要 Selenium 与 WebDriver?
如果你是一名测试工程师、爬虫开发者,或者任何需要与网页进行自动化交互的程序员,那么“Selenium”这个名字你一定不陌生。它早已不是那个仅用于简单录制回放的测试工具,而是演变成了一个强大的浏览器自动化生态。我接触 Selenium 超过八年,从最初用它来应付繁琐的回归测试,到后来用它构建复杂的爬虫和数据采集平台,再到用它驱动内部业务流程自动化,可以说,它是我工具箱里最趁手的“瑞士军刀”之一。
这个项目标题“Selenium 攻略:从元素操作到 WebDriver 实战”,精准地概括了掌握 Selenium 的两个核心阶段:基础交互与工程化实践。第一阶段,你得学会“指挥”浏览器,找到页面上的按钮、输入框,并模拟点击、输入等操作,这是自动化的基石。第二阶段,你需要理解并驾驭 WebDriver,这是 Selenium 的灵魂,它定义了如何与不同浏览器通信的协议。只有深入 WebDriver,你才能处理复杂的页面等待、多窗口切换、文件上传下载,乃至应对网站的反自动化检测机制。
网上教程很多,但大多停留在“安装-定位-点击”的层面,对于实际项目中必然会遇到的坑——比如动态加载元素导致定位失败、页面跳转后句柄丢失、浏览器指纹被识别导致封禁——往往一笔带过。这篇攻略,我将结合我踩过的无数个坑,带你从最基础的元素操作讲起,一直深入到 WebDriver 的高级配置与实战技巧,目标是让你不仅能写出能跑的脚本,更能写出健壮、高效、可维护的自动化程序。无论你是想入门自动化测试,还是想用 Selenium 做数据采集,这里的内容都将为你铺平道路。
2. 核心基石:理解 Selenium 架构与 WebDriver 协议
在动手写第一行代码之前,我们必须先搞清楚 Selenium 到底是怎么工作的。很多人学了半天,只知道driver.find_element_by_id(),却不明白背后的通信机制,一旦遇到复杂问题就束手无策。
2.1 Selenium 组件构成:不只是 Python 库
Selenium 不是一个单一的库,而是一个由多个组件构成的生态系统:
- Selenium IDE:一个浏览器插件,用于录制和回放用户操作。适合快速创建简单脚本或学习定位器,但几乎无法用于生产环境,缺乏编程逻辑和错误处理能力。
- Selenium WebDriver:核心中的核心。它提供了一套面向各种编程语言(Python, Java, C#, JavaScript 等)的 API。这些 API 并不直接控制浏览器,而是通过一个驱动程序来发送命令。
- 浏览器驱动程序:如 ChromeDriver(用于 Chrome/Edge)、GeckoDriver(用于 Firefox)。这是理解整个架构的关键。WebDriver API 调用会转换成标准的W3C WebDriver 协议命令(通常是 HTTP/JSON 格式),并通过本地端口发送给驱动程序。驱动程序再通过浏览器提供的自动化协议(如 Chrome DevTools Protocol)来实际操控浏览器。
注意:这里常有一个误区,认为
selenium这个 Python 包就是一切。实际上,selenium包只是 WebDriver API 的 Python 语言绑定。你必须为你要控制的浏览器单独下载并配置对应的驱动程序,它们才是真正的“执行者”。
2.2 WebDriver 协议:自动化如何与浏览器对话
WebDriver 协议是一个基于 HTTP 的 RESTful 风格协议。当你执行driver.get(“http://example.com”)时,背后发生了以下事情:
- Python 客户端库将
get命令和 URL 参数序列化为一个 JSON 对象。 - 通过 HTTP POST 请求发送到本地 WebDriver 服务器(例如
http://localhost:9515/session/{session-id}/url)。 - 驱动程序(如 ChromeDriver)接收到请求,解析命令。
- 驱动程序通过 CDP 等底层协议,通知 Chrome 浏览器导航到指定 URL。
- 浏览器执行完毕,将结果(状态码、可能的新页面信息)返回给驱动程序。
- 驱动程序将结果封装成 HTTP 响应,返回给 Python 客户端。
- Python 客户端解析响应,你的代码继续执行。
理解这个过程至关重要。它解释了:
- 为什么需要启动驱动程序:它是本地服务器。
- 为什么操作是异步的:网络请求需要时间,因此才有了“等待”的概念。
- 如何调试:你可以通过驱动程序的日志,看到所有进出的命令和响应,这在排查疑难杂症时非常有用。
2.3 驱动程序的下载与配置策略
这是新手的第一道坎。以 Chrome 为例,你需要下载chromedriver。
- 版本匹配是铁律:ChromeDriver 的版本必须与你的 Chrome 浏览器主版本号完全一致。例如,Chrome 版本是 120.0.6099.110,那么你必须使用 ChromeDriver 120.x.x.x。不匹配会导致无法启动或出现各种诡异错误。
- 下载与放置:从官方仓库或国内镜像站下载后,你有几种配置方式:
- 放入系统 PATH:这是最通用的方法。将
chromedriver.exe(Windows) 或chromedriver(Mac/Linux) 放在一个目录(如/usr/local/bin),并将该目录添加到系统的环境变量 PATH 中。之后在代码中只需webdriver.Chrome()即可。 - 指定可执行文件路径:在代码中直接指定驱动程序的绝对路径。
driver = webdriver.Chrome(executable_path=‘/path/to/chromedriver’)。这种方式更明确,但不利于跨环境部署。 - 使用
webdriver-manager库(强烈推荐):这是现代 Selenium 开发的最佳实践。这个第三方库会自动检测你的浏览器版本,并下载、管理匹配的驱动程序。你只需要pip install webdriver-manager,然后在代码中:
一劳永逸地解决了版本匹配和路径问题,特别适合持续集成环境。from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service)
- 放入系统 PATH:这是最通用的方法。将
3. 元素操作的十八般武艺:定位、交互与等待
掌握了架构,我们就可以开始“指挥”浏览器了。一切自动化的前提是:找到那个正确的元素。
3.1 八大定位策略详解与选用原则
Selenium 提供了多种定位元素的方法,每种都有其适用场景。
| 定位方式 | 示例代码 | 优点 | 缺点/注意事项 |
|---|---|---|---|
| ID | find_element(By.ID, “username”) | 唯一性最好,速度最快。 | 并非所有元素都有 ID;ID 可能是动态生成的。 |
| Name | find_element(By.NAME, “password”) | 常用于表单元素,相对稳定。 | 可能不唯一,需确保页面内唯一。 |
| Class Name | find_element(By.CLASS_NAME, “btn-primary”) | 适合定位具有相同样式的元素组。 | 一个元素可能有多个 class,需完整匹配;class 常变。 |
| Tag Name | find_element(By.TAG_NAME, “input”) | 定位特定类型的标签,如所有输入框。 | 通常不唯一,需结合其他条件筛选。 |
| Link Text | find_element(By.LINK_TEXT, “忘记密码?”) | 精准定位超链接文本。 | 文本必须完全匹配;文本可能被翻译或改变。 |
| Partial Link Text | find_element(By.PARTIAL_LINK_TEXT, “忘记”) | 链接文本的部分匹配,更灵活。 | 可能匹配到多个链接。 |
| CSS Selector | find_element(By.CSS_SELECTOR, “#loginForm .submit”) | 功能强大,语法灵活,性能好。 | 语法需要学习;过于复杂的选择器可能脆弱。 |
| XPath | find_element(By.XPATH, “//input[@name=‘email’]”) | 功能最强大,可基于任何属性、文本、位置定位。 | 语法复杂;性能通常比 CSS 选择器差;绝对路径非常脆弱。 |
实操心得:定位策略优先级我的个人经验是:ID > CSS Selector > XPath。
- 有 ID 一定用 ID,快且准。
- 没有 ID,优先考虑 CSS Selector。它更简洁,浏览器原生支持,解析速度通常比 XPath 快。例如,
#content > div.main比/html/body/div[@id=‘content’]/div[@class=‘main’]更易读、更高效。 - 当 CSS 无法解决时,再用 XPath。XPath 在处理基于文本定位、复杂层级关系、轴定位(如
following-sibling::)时有不可替代的优势。例如,定位一个“包含特定文本的按钮”://button[contains(text(), ‘提交’)]。
绝对要避免使用浏览器开发者工具直接复制的“绝对 XPath”(通常以/html/body/div[1]/div[3]/...开头)。这种路径极度脆弱,页面结构稍有变动(比如中间多了一个div)就会失效。始终使用相对路径和有意义的属性来定位。
3.2 不仅仅是点击和输入:丰富的交互 API
找到元素后,就可以与之交互了。最基础的是click()和send_keys()。
username_input = driver.find_element(By.ID, “username”) password_input = driver.find_element(By.NAME, “password”) submit_button = driver.find_element(By.CSS_SELECTOR, “button[type=‘submit’]”) username_input.send_keys(“my_username”) # 输入文本 password_input.send_keys(“my_password”) submit_button.click() # 点击但交互远不止这些:
- 清除内容:
input_element.clear(),在输入前先清空是个好习惯。 - 获取元素状态与信息:
text = element.text # 获取元素可见文本 attribute = element.get_attribute(“href”) # 获取属性值 is_displayed = element.is_displayed() # 是否可见 is_enabled = element.is_enabled() # 是否可交互 is_selected = element.is_selected() # 用于复选框、单选框是否被选中 - 复杂鼠标操作:需要用到
ActionChains类,用于模拟悬停、拖放、右键点击等。from selenium.webdriver.common.action_chains import ActionChains menu = driver.find_element(By.ID, “dropdownMenu”) sub_item = driver.find_element(By.LINK_TEXT, “选项A”) # 鼠标悬停在 menu 上,然后点击出现的 sub_item actions = ActionChains(driver) actions.move_to_element(menu).click(sub_item).perform() - 键盘操作:除了
send_keys,还可以模拟快捷键。from selenium.webdriver.common.keys import Keys element.send_keys(“selenium” + Keys.TAB) # 输入后按 Tab 键 element.send_keys(Keys.CONTROL + ‘a’) # 全选 (Ctrl+A)
3.3 等待的艺术:让脚本稳定运行的关键
这是 Selenium 脚本稳定性的生命线。页面加载和元素渲染需要时间,如果脚本执行速度超过页面响应速度,就会抛出NoSuchElementException。有三种等待方式:
强制等待:
time.sleep(seconds)。这是最糟糕的方式,因为它无论页面是否就绪都死等固定时间,导致脚本要么低效,要么依然失败。仅在极少数调试场景下临时使用。隐式等待:
driver.implicitly_wait(10)。设置一个全局的超时时间。在查找任何元素时,如果元素没有立即出现,WebDriver 会轮询 DOM(默认每 0.5 秒)直到找到元素或超时。它只对find_element和find_elements方法有效。- 优点:设置一次,全局生效,代码简洁。
- 缺点:不够灵活,无法等待特定条件(如元素可点击、包含特定文本)。并且,一旦设置,会在整个 WebDriver 会话周期生效,可能在某些不需要等待的地方产生不必要的延迟。
显式等待:这是生产环境推荐的最佳实践。它允许你为某个特定的条件设置等待,条件满足则立即继续,超时则抛出异常。
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 等待最多10秒,直到ID为‘result’的元素出现 wait = WebDriverWait(driver, 10) element = wait.until(EC.presence_of_element_located((By.ID, “result”))) # 等待元素可点击 button = wait.until(EC.element_to_be_clickable((By.ID, “submitBtn”))) button.click() # 等待元素包含特定文本 wait.until(EC.text_to_be_present_in_element((By.ID, “status”), “成功”))expected_conditions模块提供了大量预定义条件,如visibility_of_element_located(元素可见)、title_contains(标题包含)等。
我的等待策略:
- 全局设置一个较短的隐式等待(如 5 秒),作为兜底。
- 在关键交互点(如点击按钮后等待新页面/弹窗/结果出现)使用显式等待,并选择最合适的条件。
- 永远不要混用隐式等待和显式等待,这会导致不可预知的超时行为。如果要用显式等待,最好将隐式等待设置为 0。
4. WebDriver 实战进阶:应对复杂场景
当你熟练掌握了元素操作,就可以挑战更复杂的自动化场景了。这些是区分“玩具脚本”和“生产级脚本”的关键。
4.1 处理多窗口、框架与弹窗
多窗口/标签页:
# 获取当前窗口句柄 main_window = driver.current_window_handle # 点击一个会打开新窗口的链接 driver.find_element(By.LINK_TEXT, “新窗口”).click() # 获取所有窗口句柄 all_windows = driver.window_handles # 切换到新窗口 for window in all_windows: if window != main_window: driver.switch_to.window(window) break # 在新窗口操作... # 切换回主窗口 driver.switch_to.window(main_window)关键:在操作新窗口前,必须切换上下文。操作完后,如果需要,记得切回来。
iframe/框架:如果元素位于 iframe 内,你必须先切换到该 iframe。
# 通过 ID、Name 或索引切换 driver.switch_to.frame(“iframe_id”) # 在 iframe 内操作元素 driver.find_element(By.TAG_NAME, “button”).click() # 操作完成后切回主文档 driver.switch_to.default_content()JavaScript 弹窗 (Alert, Confirm, Prompt):
# 点击触发 alert 的按钮 driver.find_element(By.ID, “alertBtn”).click() # 切换到 alert alert = driver.switch_to.alert print(alert.text) # 获取警告文本 alert.accept() # 点击“确定” # alert.dismiss() # 点击“取消” # 对于 prompt,还可以 send_keys # alert.send_keys(“输入的文字”)
4.2 文件上传与下载
文件上传:对于
<input type=“file”>元素,直接使用send_keys传入文件的绝对路径即可。千万不要尝试模拟点击“浏览”按钮,那会打开系统文件对话框,Selenium 无法处理。upload_element = driver.find_element(By.XPATH, “//input[@type=‘file’]”) upload_element.send_keys(“/Users/me/Desktop/test.jpg”)文件下载:这需要配置浏览器选项。以下载到指定目录为例(Chrome):
from selenium import webdriver from selenium.webdriver.chrome.options import Options chrome_options = Options() prefs = { “download.default_directory”: “/path/to/your/download/folder”, # 设置下载路径 “download.prompt_for_download”: False, # 禁止下载提示 “download.directory_upgrade”: True, “safebrowsing.enabled”: True } chrome_options.add_experimental_option(“prefs”, prefs) driver = webdriver.Chrome(options=chrome_options)下载后,你可能需要结合
os或time模块来等待文件下载完成(检查文件是否存在且大小不再变化)。
4.3 执行 JavaScript 代码
Selenium 可以通过execute_script方法直接执行 JavaScript,这能实现一些 WebDriver API 难以完成的操作。
# 滚动到页面底部 driver.execute_script(“window.scrollTo(0, document.body.scrollHeight);”) # 滚动到某个元素 element = driver.find_element(By.ID, “myElement”) driver.execute_script(“arguments[0].scrollIntoView(true);”, element) # 修改元素属性(例如,让一个隐藏的输入框可见) driver.execute_script(“document.getElementById(‘hiddenInput’).style.display = ‘block’;”) # 获取页面标题(虽然 driver.title 也可以,这里只是示例) title = driver.execute_script(“return document.title;”)注意:execute_script是异步的,但它会返回 JS 代码执行后的返回值(如果有的话)。
5. 高级配置与反反爬虫策略
当你的脚本需要长时间运行,或者目标网站有反爬机制时,基础的 WebDriver 配置就不够了。
5.1 ChromeOptions 深度配置
通过ChromeOptions,你可以对浏览器进行深度定制。
from selenium import webdriver from selenium.webdriver.chrome.options import Options import time chrome_options = Options() # 常用配置 chrome_options.add_argument(‘--no-sandbox’) # 在 Docker/无头环境中常需 chrome_options.add_argument(‘--disable-dev-shm-usage’) # 解决共享内存问题 chrome_options.add_argument(‘--disable-gpu’) # 某些环境下禁用GPU chrome_options.add_argument(‘--window-size=1920,1080’) # 设置窗口大小 # 无头模式(不显示浏览器界面) chrome_options.add_argument(‘--headless=new’) # Chrome 109+ 推荐使用 new # 禁用自动化控制提示(Chrome 正受到自动测试软件的控制) chrome_options.add_experimental_option(“excludeSwitches”, [“enable-automation”]) chrome_options.add_experimental_option(‘useAutomationExtension’, False) # 更彻底的隐藏 WebDriver 特征(重要) chrome_options.add_argument(“--disable-blink-features=AutomationControlled”) driver = webdriver.Chrome(options=chrome_options) # 通过 CDP 执行脚本,覆盖 navigator.webdriver 属性 driver.execute_cdp_cmd(“Page.addScriptToEvaluateOnNewDocument”, { “source”: “”” Object.defineProperty(navigator, ‘webdriver’, { get: () => undefined }); “”” })--disable-blink-features=AutomationControlled和 CDP 脚本是隐藏自动化特征的关键组合拳,能绕过许多基础的反爬检测。
5.2 用户数据持久化与 Cookie 管理
有时你需要保持登录状态。
# 1. 使用用户数据目录(保存Cookies、缓存等) user_data_dir = “/path/to/your/chrome/profile” chrome_options.add_argument(f”--user-data-dir={user_data_dir}”) # 首次启动需要手动登录,后续启动即可保持登录状态 # 2. 程序化操作 Cookie driver.get(“http://example.com/login”) # ... 执行登录操作 ... cookies = driver.get_cookies() # 获取当前所有cookies print(cookies) # 将 cookies 保存到文件(如 json) import json with open(‘cookies.json’, ‘w’) as f: json.dump(cookies, f) # 在新的会话中加载 cookies driver.get(“http://example.com/”) # 先访问域名 with open(‘cookies.json’, ‘r’) as f: cookies = json.load(f) for cookie in cookies: driver.add_cookie(cookie) # 添加cookie driver.refresh() # 刷新页面,使cookie生效5.3 应对智能反爬:Selenium 指纹隐藏
现代网站(特别是大型平台)会使用更高级的检测手段,如检测浏览器指纹、Canvas、WebGL 等。单纯隐藏navigator.webdriver可能不够。你需要更全面的伪装。
使用 undetected-chromedriver:这是一个第三方库,专门用于修改 Chromedriver,使其指纹更像普通浏览器。
pip install undetected-chromedriverimport undetected_chromedriver as uc driver = uc.Chrome() driver.get(“https://nowsecure.nl”) # 一个著名的反爬测试网站它能自动处理很多反爬问题,是简单有效的方案。
手动深度伪装(进阶):结合 CDP 命令,修改更多属性。
driver.execute_cdp_cmd(“Page.addScriptToEvaluateOnNewDocument”, { “source”: “”” // 覆盖 plugins Object.defineProperty(navigator, ‘plugins’, { get: () => [1, 2, 3, 4, 5], }); // 覆盖 languages Object.defineProperty(navigator, ‘languages’, { get: () => [‘zh-CN’, ‘zh’, ‘en’], }); // 覆盖 platform Object.defineProperty(navigator, ‘platform’, { get: () => ‘Win32’, }); “”” })你还可以通过
chrome_options.add_argument设置合理的 User-Agent 等。
核心原则:没有一劳永逸的方案。反爬与反反爬在不断对抗。你需要根据目标网站的具体情况调整策略,并做好请求频率控制、IP 代理池等更外围的防护措施。
6. 工程化实践:从脚本到框架
当你的自动化任务变得复杂,就需要考虑工程化了。
6.1 Page Object Model:让代码可维护
POM 是一种设计模式,将页面封装成类,页面的元素定位和操作封装成类的方法。这样,测试脚本(业务逻辑)就和页面细节分离开了。
# login_page.py from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class LoginPage: def __init__(self, driver): self.driver = driver self.wait = WebDriverWait(driver, 10) # 定位器 USERNAME_INPUT = (By.ID, “username”) PASSWORD_INPUT = (By.NAME, “password”) SUBMIT_BUTTON = (By.CSS_SELECTOR, “button[type=‘submit’]”) ERROR_MSG = (By.CLASS_NAME, “error-message”) # 页面操作方法 def enter_username(self, username): element = self.wait.until(EC.visibility_of_element_located(self.USERNAME_INPUT)) element.clear() element.send_keys(username) def enter_password(self, password): self.driver.find_element(*self.PASSWORD_INPUT).send_keys(password) def click_submit(self): self.driver.find_element(*self.SUBMIT_BUTTON).click() def get_error_message(self): try: return self.driver.find_element(*self.ERROR_MSG).text except: return None # test_login.py from login_page import LoginPage def test_valid_login(): driver = webdriver.Chrome() driver.get(“http://example.com/login”) login_page = LoginPage(driver) login_page.enter_username(“testuser”) login_page.enter_password(“pass123”) login_page.click_submit() # ... 后续断言 ... driver.quit()好处:元素定位符只在一处维护;业务逻辑清晰;便于复用。
6.2 日志记录与失败截图
健壮的脚本必须有完善的日志和故障排查手段。
import logging from datetime import datetime from selenium.webdriver.support.events import EventFiringWebDriver, AbstractEventListener # 设置日志 logging.basicConfig(level=logging.INFO, format=‘%(asctime)s - %(name)s - %(levelname)s - %(message)s’) logger = logging.getLogger(__name__) # 自定义监听器,用于在操作失败时截图 class MyListener(AbstractEventListener): def on_exception(self, exception, driver): logger.error(f”操作异常: {exception}”) # 截图,以时间戳命名 timestamp = datetime.now().strftime(“%Y%m%d_%H%M%S”) screenshot_path = f”./screenshots/exception_{timestamp}.png” driver.save_screenshot(screenshot_path) logger.info(f”异常截图已保存至: {screenshot_path}”) # 使用带监听器的驱动 driver = webdriver.Chrome() event_driver = EventFiringWebDriver(driver, MyListener()) # 之后使用 event_driver 进行操作,异常时会自动截图6.3 集成到 CI/CD 与云测平台
自动化脚本最终可以集成到持续集成流水线中。
- 本地 Jenkins/GitLab CI:在 Pipeline 中配置相应的 Python 环境和浏览器驱动,定时或触发式运行测试套件。
- 云测平台:如 Sauce Labs, BrowserStack。它们提供了海量的浏览器/操作系统组合。你需要使用它们提供的远程 WebDriver 地址。
# 示例:连接 BrowserStack from selenium import webdriver desired_cap = { ‘browser’: ‘Chrome’, ‘browser_version’: ‘latest’, ‘os’: ‘Windows’, ‘os_version’: ‘10’, ‘name’: ‘My Test’ # 测试名称 } driver = webdriver.Remote( command_executor=‘https://USERNAME:ACCESS_KEY@hub.browserstack.com/wd/hub’, desired_capabilities=desired_cap )
7. 常见问题排查与性能优化
即使掌握了所有技巧,在实际运行中还是会遇到各种问题。这里记录一些高频问题的排查思路。
7.1 典型异常与解决方案速查表
| 异常信息 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
NoSuchElementException | 1. 元素定位器写错。 2. 页面尚未加载完成。 3. 元素在 iframe 内。 4. 元素是动态生成的。 | 1. 在浏览器控制台用$$(‘你的CSS’)或$x(‘你的XPath’)验证定位器。2. 添加合适的显式等待。 3. 使用 driver.switch_to.frame()切换到正确的 iframe。4. 等待元素出现或使用更稳定的定位方式(如等其父元素出现)。 |
ElementNotInteractableException | 1. 元素不可见(display:none, visibility:hidden)。 2. 元素被其他元素遮挡。 3. 元素尚未处于可交互状态(如 disabled)。 | 1. 检查元素样式,或使用EC.visibility_of_element_located等待。2. 使用 ActionChains或 JS 点击。3. 等待 EC.element_to_be_clickable。 |
StaleElementReferenceException | 你之前找到的元素,其对应的 DOM 节点已经失效(页面刷新、元素被重新渲染)。 | 这是最常见的坑之一。解决方案是重新查找元素。避免在页面可能刷新的操作后,继续使用旧的元素对象。 |
TimeoutException | 显式等待的条件在指定时间内未满足。 | 1. 增加等待时间。 2. 检查等待条件是否合理(例如,等待的元素定位器是否正确)。 3. 检查页面是否真的加载出了预期内容(网络问题、JS错误)。 |
SessionNotCreatedException | 浏览器与驱动程序版本不匹配。 | 100% 检查版本匹配。使用webdriver-manager或手动下载对应版本。 |
InvalidSelectorException | 提供的 CSS 选择器或 XPath 语法错误。 | 在浏览器开发者工具中测试你的选择器。注意转义特殊字符。 |
7.2 脚本性能优化技巧
- 减少不必要的等待:用精确的显式等待替代固定的
time.sleep和过长的隐式等待。 - 使用更快的定位器:ID > CSS Selector > XPath。在循环中查找元素时,这点性能差异会被放大。
- 批量操作元素:使用
find_elements获取列表后循环,比多次调用find_element效率高。 - 谨慎使用
execute_script:虽然强大,但频繁的 JS 执行也会影响性能。优先使用原生 WebDriver API。 - 复用浏览器会话:对于一系列相关操作,尽量在一个
driver会话内完成,避免反复启动/关闭浏览器,这是最耗时的操作。
7.3 调试技巧:让问题无处遁形
开启驱动程序日志:
from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options import logging service = Service(executable_path=‘chromedriver’, log_path=‘./chromedriver.log’, service_args=[‘--verbose’]) driver = webdriver.Chrome(service=service)查看
chromedriver.log文件,里面有所有协议通信的详细信息。在失败时暂停:在关键断言或操作后加入
input(“按回车继续...”)或time.sleep(10),方便你观察页面状态。活用
page_source和截图:当元素找不到时,立即打印driver.page_source或截图,看看你看到的页面和脚本看到的页面是否一致。使用浏览器开发者工具:在无头模式下,可以通过
driver.get_screenshot_as_file()截图。更好的方法是,在关键步骤前暂时禁用无头模式,亲眼观察浏览器的行为。
