Python Selenium 漫步指南:从入门到精通
Python Selenium 完全指南:从入门到精通
📚 目录
- 环境准备与基础入门
- 元素定位与交互操作
- 等待机制与异常处理
- 面向对象封装与框架设计
- 进阶技巧与最佳实践
- 性能优化与调试技巧
- 实战案例分析
环境准备与基础入门
1. 安装 Selenium 与浏览器驱动
安装 Selenium
# 使用pip安装最新版本
pip install selenium
# 安装特定版本
pip install selenium==4.10.0
# 在虚拟环境中安装(推荐)
python -m venv selenium_env
source selenium_env/bin/activate # Linux/Mac
selenium_env\Scripts\activate.bat # Windows
pip install selenium
安装浏览器驱动
从Selenium 4.0开始,提供了自动管理驱动的功能,但了解手动安装方法仍然很重要:
Chrome浏览器:
- 访问 ChromeDriver 下载页面
- 下载与本地Chrome版本匹配的驱动程序
- 将驱动添加到系统PATH中或在代码中指定路径
Firefox浏览器:
- 访问 GeckoDriver 下载页面
- 下载适用于你操作系统的版本
- 将驱动添加到系统PATH中或在代码中指定路径
Edge浏览器:
- 访问 Microsoft Edge Driver 下载页面
- 下载与本地Edge版本匹配的驱动程序
Safari浏览器:
- Safari驱动已内置于macOS中
- 需要在Safari浏览器中启用开发者模式
2. Selenium 4.x 新特性
Selenium 4.x引入了许多重要的改进和新功能:
- 相对定位器:允许基于其他元素的位置来查找元素
- Service对象:用于更好地管理驱动程序服务
- WebDriver Manager:自动管理驱动程序的下载和设置
- CDP(Chrome DevTools Protocol)支持:允许访问浏览器特定的功能
3. WebDriver初始化方法
使用Selenium Manager(推荐,Selenium 4.x)
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
# 自动管理驱动
driver = webdriver.Chrome()
传统方法(指定驱动路径)
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
# 指定驱动路径
service = Service(executable_path='/path/to/chromedriver')
driver = webdriver.Chrome(service=service)
配置浏览器选项
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
# 创建Chrome选项对象
chrome_options = Options()
chrome_options.add_argument("--headless") # 无头模式
chrome_options.add_argument("--window-size=1920,1080") # 设置窗口大小
chrome_options.add_argument("--disable-gpu") # 禁用GPU加速
chrome_options.add_argument("--disable-extensions") # 禁用扩展
chrome_options.add_argument("--proxy-server='direct://'") # 代理设置
chrome_options.add_argument("--proxy-bypass-list=*") # 绕过代理
chrome_options.add_argument("--start-maximized") # 启动时最大化窗口
chrome_options.add_experimental_option("prefs", {
"download.default_directory": "/path/to/download/directory", # 设置下载目录
"download.prompt_for_download": False, # 禁用下载提示
"download.directory_upgrade": True,
"safebrowsing.enabled": True
})
# 初始化WebDriver
driver = webdriver.Chrome(options=chrome_options)
4. 基础浏览器操作
from selenium import webdriver
# 初始化WebDriver
driver = webdriver.Chrome()
# 窗口操作
driver.maximize_window() # 最大化窗口
driver.set_window_size(1920, 1080) # 设置窗口大小
driver.set_window_position(0, 0) # 设置窗口位置
# 导航操作
driver.get('https://www.example.com') # 打开URL
driver.back() # 后退
driver.forward() # 前进
driver.refresh() # 刷新页面
# 页面信息
title = driver.title # 获取页面标题
url = driver.current_url # 获取当前URL
page_source = driver.page_source # 获取页面源代码
# Cookie操作
driver.add_cookie({"name": "key", "value": "value"}) # 添加Cookie
cookies = driver.get_cookies() # 获取所有Cookies
driver.delete_cookie("key") # 删除特定Cookie
driver.delete_all_cookies() # 删除所有Cookies
# 关闭操作
driver.close() # 关闭当前标签页
driver.quit() # 关闭浏览器,释放资源
5. 常见浏览器配置
无头模式(Headless)
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
chrome_options = Options()
chrome_options.add_argument("--headless")
driver = webdriver.Chrome(options=chrome_options)
使用代理
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
chrome_options = Options()
chrome_options.add_argument('--proxy-server=http://proxyserver:port')
driver = webdriver.Chrome(options=chrome_options)
禁用图片加载(提高性能)
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
chrome_options = Options()
prefs = {"profile.managed_default_content_settings.images": 2}
chrome_options.add_experimental_option("prefs", prefs)
driver = webdriver.Chrome(options=chrome_options)
元素定位与交互操作
1. 元素定位基础
Selenium提供了多种定位元素的方法,每种都有其适用场景:
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("https://www.example.com")
# 1. 通过ID定位(最推荐,高效且唯一)
element = driver.find_element(By.ID, "login-button")
# 2. 通过Name属性定位
element = driver.find_element(By.NAME, "username")
# 3. 通过Class Name定位(不唯一时返回第一个匹配元素)
element = driver.find_element(By.CLASS_NAME, "login-form")
# 4. 通过Tag Name定位
element = driver.find_element(By.TAG_NAME, "button")
# 5. 通过Link Text定位(完全匹配)
element = driver.find_element(By.LINK_TEXT, "Forgot Password?")
# 6. 通过Partial Link Text定位(部分匹配)
element = driver.find_element(By.PARTIAL_LINK_TEXT, "Forgot")
# 7. 通过CSS选择器定位(强大且灵活)
element = driver.find_element(By.CSS_SELECTOR, "#login-form .submit-button")
# 8. 通过XPath定位(最强大但可能较慢)
element = driver.find_element(By.XPATH, "//div[@id='login-form']//button")
2. 高级定位策略
XPath进阶用法
# 绝对路径(从根节点开始)
element = driver.find_element(By.XPATH, "/html/body/div/form/input")
# 相对路径(从任意节点开始)
element = driver.find_element(By.XPATH, "//input[@name='username']")
# 使用contains()函数
element = driver.find_element(By.XPATH, "//button[contains(@class, 'login')]")
# 使用text()函数
element = driver.find_element(By.XPATH, "//a[text()='Forgot Password?']")
element = driver.find_element(By.XPATH, "//a[contains(text(), 'Forgot')]")
# 使用AND和OR操作符
element = driver.find_element(By.XPATH, "//input[@type='text' and @name='username']")
element = driver.find_element(By.XPATH, "//button[@type='submit' or @type='button']")
# 通过父子关系定位
element = driver.find_element(By.XPATH, "//form[@id='login-form']/input")
parent = driver.find_element(By.XPATH, "//input[@id='username']/..")
# 通过兄弟关系定位
element = driver.find_element(By.XPATH, "//input[@id='username']/following-sibling::input")
element = driver.find_element(By.XPATH, "//input[@id='password']/preceding-sibling::input")
# 按索引定位
element = driver.find_element(By.XPATH, "(//input[@type='text'])[2]")
# 使用轴(axes)
element = driver.find_element(By.XPATH, "//input[@id='username']/ancestor::form")
element = driver.find_element(By.XPATH, "//form/descendant::input")
CSS选择器进阶用法
# 基本选择器
element = driver.find_element(By.CSS_SELECTOR, "#login-button") # ID选择器
element = driver.find_element(By.CSS_SELECTOR, ".login-form") # Class选择器
element = driver.find_element(By.CSS_SELECTOR, "input") # 标签选择器
# 属性选择器
element = driver.find_element(By.CSS_SELECTOR, "input[name='username']")
element = driver.find_element(By.CSS_SELECTOR, "input[name^='user']") # 以user开头
element = driver.find_element(By.CSS_SELECTOR, "input[name$='name']") # 以name结尾
element = driver.find_element(By.CSS_SELECTOR, "input[name*='erna']") # 包含erna
# 组合选择器
element = driver.find_element(By.CSS_SELECTOR, "form input[type='text']")
element = driver.find_element(By.CSS_SELECTOR, "form > input") # 直接子元素
element = driver.find_element(By.CSS_SELECTOR, "label + input") # 紧邻兄弟元素
element = driver.find_element(By.CSS_SELECTOR, "label ~ input") # 通用兄弟元素
# 伪类选择器
element = driver.find_element(By.CSS_SELECTOR, "input:first-child")
element = driver.find_element(By.CSS_SELECTOR, "input:last-child")
element = driver.find_element(By.CSS_SELECTOR, "input:nth-child(2)")
相对定位器(Selenium 4.x新特性)
from selenium.webdriver.support.relative_locator import locate_with
# 获取参考元素
username_field = driver.find_element(By.ID, "username")
# 使用相对定位器
password_field = driver.find_element(locate_with(By.TAG_NAME, "input").below(username_field))
login_button = driver.find_element(locate_with(By.TAG_NAME, "button").below(password_field))
remember_me = driver.find_element(locate_with(By.TAG_NAME, "input").to_right_of(password_field))
forgot_password = driver.find_element(locate_with(By.TAG_NAME, "a").above(login_button))
3. 查找多个元素
# 查找所有符合条件的元素
elements = driver.find_elements(By.CSS_SELECTOR, ".product-item")
# 遍历元素列表
for element in elements:
name = element.find_element(By.CLASS_NAME, "product-name").text
price = element.find_element(By.CLASS_NAME, "product-price").text
print(f"产品名称: {name}, 价格: {price}")
4. 元素交互操作
# 输入操作
element.send_keys("test@example.com") # 输入文本
element.send_keys(Keys.CONTROL, 'a') # 键盘组合键(全选)
element.send_keys(Keys.BACK_SPACE) # 退格键
# 点击操作
element.click() # 点击元素
element.submit() # 提交表单(适用于表单元素内)
# 清除操作
element.clear() # 清除文本输入框
# 获取元素属性和状态
value = element.get_attribute("value") # 获取属性值
text = element.text # 获取元素文本内容
tag = element.tag_name # 获取标签名
size = element.size # 获取元素大小
location = element.location # 获取元素位置
is_enabled = element.is_enabled() # 元素是否启用
is_selected = element.is_selected() # 元素是否选中(复选框、单选按钮等)
is_displayed = element.is_displayed() # 元素是否可见
# 特殊元素操作
# 下拉菜单
from selenium.webdriver.support.select import Select
select = Select(driver.find_element(By.ID, "dropdown"))
select.select_by_visible_text("Option 1") # 通过文本选择
select.select_by_value("option1") # 通过值选择
select.select_by_index(1) # 通过索引选择
options = select.options # 获取所有选项
first_option = select.first_selected_option # 获取当前选中选项
select.deselect_all() # 取消所有选择(多选下拉框)
# 复选框和单选按钮
checkbox = driver.find_element(By.ID, "checkbox")
ifnot checkbox.is_selected():
checkbox.click()
5. 元素查找最佳实践
- 性能优化顺序:ID > Name > CSS > XPath
- 避免使用:
- 绝对XPath路径(容易失效)
- 基于视觉位置的选择器
- 多级嵌套CSS选择器
- 推荐使用:
- 有意义的ID和名称属性
- 数据测试属性(如
data-testid) - 短而明确的CSS选择器
- 建议添加:
- 页面加载和元素的等待机制
- 查找元素的超时和重试机制
- 详细的错误处理机制
等待机制与异常处理
1. 等待策略
在Web自动化中,页面加载和元素渲染需要时间,等待机制至关重要。
隐式等待(Implicit Wait)
# 设置隐式等待时间(全局设置)
driver.implicitly_wait(10) # 等待最多10秒直到元素出现
隐式等待会在查找元素时自动等待一段时间直到元素出现,如果在指定时间内未找到元素,则抛出异常。
显式等待(Explicit Wait)
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# 等待元素可见
element = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.ID, "element_id"))
)
# 等待元素可点击
element = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.ID, "button_id"))
)
# 等待页面标题包含特定文本
WebDriverWait(driver, 10).until(
EC.title_contains("Home Page")
)
# 等待元素消失
WebDriverWait(driver, 10).until(
EC.invisibility_of_element_located((By.CLASS_NAME, "loading"))
)
# 等待警告框出现
WebDriverWait(driver, 10).until(
EC.alert_is_present()
)
# 等待元素的文本内容满足条件
WebDriverWait(driver, 10).until(
EC.text_to_be_present_in_element((By.ID, "status"), "Success")
)
# 等待元素的属性值满足条件
WebDriverWait(driver, 10).until(
EC.text_to_be_present_in_element_attribute((By.ID, "input"), "value", "text")
)
自定义等待条件
from selenium.webdriver.support.ui import WebDriverWait
# 自定义等待条件
def element_has_class(element, class_name):
return class_name in element.get_attribute("class").split()
# 使用自定义等待条件
element = driver.find_element(By.ID, "myElement")
WebDriverWait(driver, 10).until(lambda driver: element_has_class(element, "active"))
流畅等待(FluentWait)
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException
# 创建FluentWait实例
wait = WebDriverWait(
driver,
timeout=30,
poll_frequency=2, # 每2秒检查一次
ignored_exceptions=[NoSuchElementException, StaleElementReferenceException]
)
# 使用FluentWait
element = wait.until(EC.element_to_be_clickable((By.ID, "myElement")))
2. 异常处理
Selenium操作可能会触发各种异常,合理的异常处理可以提高脚本的健壮性。
常见异常类型
from selenium.common.exceptions import (
NoSuchElementException, # 元素未找到
TimeoutException, # 等待超时
ElementNotVisibleException, # 元素不可见
ElementNotInteractableException, # 元素不可交互
StaleElementReferenceException, # 元素已过时(DOM已更新)
WebDriverException, # WebDriver通用异常
InvalidSelectorException, # 无效的选择器
UnexpectedAlertPresentException, # 意外的警告框
NoAlertPresentException, # 没有警告框
SessionNotCreatedException, # 会话创建失败
ElementClickInterceptedException # 元素点击被拦截
)
基本异常处理
try:
element = driver.find_element(By.ID, "non_existent_element")
element.click()
except NoSuchElementException:
print("元素未找到")
except ElementNotInteractableException:
print("元素不可交互")
except Exception as e:
print(f"发生其他异常: {e}")
重试机制
def retry_click(driver, by, value, max_attempts=3, wait_time=1):
"""
尝试多次点击元素
"""
from time import sleep
for attempt in range(max_attempts):
try:
element = driver.find_element(by, value)
element.click()
returnTrue
except (NoSuchElementException, ElementNotInteractableException,
ElementClickInterceptedException, StaleElementReferenceException) as e:
if attempt == max_attempts - 1:
print(f"无法点击元素,错误: {e}")
returnFalse
sleep(wait_time)
returnFalse
处理StaleElementReferenceException
def get_fresh_element(driver, by, value):
"""
获取一个新鲜的元素引用,避免StaleElementReferenceException
"""
try:
return driver.find_element(by, value)
except StaleElementReferenceException:
# 重新查找元素
return driver.find_element(by, value)
使用装饰器处理异常
import functools
from time import sleep
def retry(max_attempts=3, wait_time=1):
"""
函数重试装饰器
"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except (NoSuchElementException, ElementNotInteractableException,
StaleElementReferenceException) as e:
if attempt == max_attempts - 1:
raise e
sleep(wait_time)
return wrapper
return decorator
# 使用装饰器
@retry(max_attempts=5, wait_time=2)
def click_element(driver, by, value):
driver.find_element(by, value).click()
3. 等待策略最佳实践
- 避免使用
time.sleep():不灵活且低效 - 优先使用显式等待:更精确,可控性更强
- 结合使用隐式等待和显式等待:隐式等待作为全局保护,显式等待针对特定场景
- 设置合理的超时时间:不要过长或过短
- 捕获并处理超时异常:提供适当的恢复机制或用户友好的错误信息
- 为不同网络环境调整等待策略:可配置的超时参数
面向对象封装与框架设计
1. 页面对象模型(Page Object Model, POM)
页面对象模型是一种设计模式,将页面的元素和操作封装在类中,使测试代码更加清晰和可维护。
基本POM结构
class BasePage:
"""
所有页面的基类
"""
def __init__(self, driver):
self.driver = driver
def find_element(self, locator):
return self.driver.find_element(*locator)
def find_elements(self, locator):
return self.driver.find_elements(*locator)
def click(self, locator):
self.find_element(locator).click()
def input_text(self, locator, text):
element = self.find_element(locator)
element.clear()
element.send_keys(text)
def get_text(self, locator):
return self.find_element(locator).text
def is_element_present(self, locator):
try:
self.find_element(locator)
returnTrue
except NoSuchElementException:
returnFalse
def wait_for_element(self, locator, timeout=10):
try:
WebDriverWait(self.driver, timeout).until(
EC.presence_of_element_located(locator)
)
returnTrue
except TimeoutException:
returnFalse
class LoginPage(BasePage):
"""
登录页面对象
"""
# 页面元素定位器
_username_field = (By.ID, "username")
_password_field = (By.ID, "password")
_login_button = (By.ID, "login_button")
_error_message = (By.CLASS_NAME, "error-message")
def __init__(self, driver):
super().__init__(driver)
self.driver.get("https://example.com/login")
def enter_username(self, username):
self.input_text(self._username_field, username)
return self
def enter_password(self, password):
self.input_text(self._password_field, password)
return self
def click_login(self):
self.click(self._login_button)
# 根据登录结果返回不同的页面对象
if"dashboard"in self.driver.current_url:
return DashboardPage(self.driver)
return self
def login(self, username, password):
self.enter_username(username)
self.enter_password(password)
return
