Python Selenium 完全指南:从入门到精通

article/2025/8/17 21:42:58

Python Selenium 完全指南:从入门到精通

📚 目录

  1. 环境准备与基础入门
  2. 元素定位与交互操作
  3. 等待机制与异常处理
  4. 面向对象封装与框架设计
  5. 进阶技巧与最佳实践
  6. 性能优化与调试技巧
  7. 实战案例分析

环境准备与基础入门

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 Optionschrome_options = Options()
chrome_options.add_argument("--headless")
driver = webdriver.Chrome(options=chrome_options)
使用代理
from selenium import webdriver
from selenium.webdriver.chrome.options import Optionschrome_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 Optionschrome_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 Bydriver = 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").textprice = element.find_element(By.CLASS_NAME, "product-price").textprint(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")
if not checkbox.is_selected():checkbox.click()

5. 元素查找最佳实践

  1. 性能优化顺序:ID > Name > CSS > XPath
  2. 避免使用
    • 绝对XPath路径(容易失效)
    • 基于视觉位置的选择器
    • 多级嵌套CSS选择器
  3. 推荐使用
    • 有意义的ID和名称属性
    • 数据测试属性(如data-testid)
    • 短而明确的CSS选择器
  4. 建议添加
    • 页面加载和元素的等待机制
    • 查找元素的超时和重试机制
    • 详细的错误处理机制

等待机制与异常处理

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 sleepfor attempt in range(max_attempts):try:element = driver.find_element(by, value)element.click()return Trueexcept (NoSuchElementException, ElementNotInteractableException, ElementClickInterceptedException, StaleElementReferenceException) as e:if attempt == max_attempts - 1:print(f"无法点击元素,错误: {e}")return Falsesleep(wait_time)return False
处理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 sleepdef 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 esleep(wait_time)return wrapperreturn decorator# 使用装饰器
@retry(max_attempts=5, wait_time=2)
def click_element(driver, by, value):driver.find_element(by, value).click()

3. 等待策略最佳实践

  1. 避免使用time.sleep():不灵活且低效
  2. 优先使用显式等待:更精确,可控性更强
  3. 结合使用隐式等待和显式等待:隐式等待作为全局保护,显式等待针对特定场景
  4. 设置合理的超时时间:不要过长或过短
  5. 捕获并处理超时异常:提供适当的恢复机制或用户友好的错误信息
  6. 为不同网络环境调整等待策略:可配置的超时参数

面向对象封装与框架设计

1. 页面对象模型(Page Object Model, POM)

页面对象模型是一种设计模式,将页面的元素和操作封装在类中,使测试代码更加清晰和可维护。

基本POM结构
class BasePage:"""所有页面的基类"""def __init__(self, driver):self.driver = driverdef 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).textdef is_element_present(self, locator):try:self.find_element(locator)return Trueexcept NoSuchElementException:return Falsedef wait_for_element(self, locator, timeout=10):try:WebDriverWait(self.driver, timeout).until(EC.presence_of_element_located(locator))return Trueexcept TimeoutException:return Falseclass 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 selfdef enter_password(self, password):self.input_text(self._password_field, password)return selfdef click_login(self):self.click(self._login_button)# 根据登录结果返回不同的页面对象if "dashboard" in self.driver.current_url:return DashboardPage(self.driver)return selfdef login(self, username, password):self.enter_username(username)self.enter_password(password)return self.click_login()def get_error_message(self):if self.is_element_present(self._error_message):return self.get_text(self._error_message)return ""class DashboardPage(BasePage):"""仪表盘页面对象"""_welcome_message = (By.ID, "welcome")_logout_button = (By.ID, "logout")def is_loaded(self):return self.wait_for_element(self._welcome_message)def get_welcome_message(self):return self.get_text(self._welcome_message)def logout(self):self.click(self._logout_button)return LoginPage(self.driver)
使用POM进行测试
def test_login_success():driver = webdriver.Chrome()try:login_page = LoginPage(driver)dashboard_page = login_page.login("valid_user", "valid_password")assert dashboard_page.is_loaded()assert "Welcome" in dashboard_page.get_welcome_message()finally:driver.quit()def test_login_failure():driver = webdriver.Chrome()try:login_page = LoginPage(driver)result_page = login_page.login("invalid_user", "invalid_password")assert isinstance(result_page, LoginPage)assert "Invalid credentials" in result_page.get_error_message()finally:driver.quit()

2. 测试框架集成

与unittest集成
import unittest
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManagerclass TestLogin(unittest.TestCase):def setUp(self):self.driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))self.driver.maximize_window()self.login_page = LoginPage(self.driver)def tearDown(self):self.driver.quit()def test_valid_login(self):dashboard_page = self.login_page.login("valid_user", "valid_password")self.assertTrue(dashboard_page.is_loaded())self.assertIn("Welcome", dashboard_page.get_welcome_message())def test_invalid_login(self):result_page = self.login_page.login("invalid_user", "invalid_password")self.assertIsInstance(result_page, LoginPage)self.assertIn("Invalid credentials", result_page.get_error_message())if __name__ == "__main__":unittest.main()
与pytest集成
import pytest
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager@pytest.fixture
def driver():# 设置driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))driver.maximize_window()yield driver# 清理driver.quit()@pytest.fixture
def login_page(driver):return LoginPage(driver)def test_valid_login(login_page):dashboard_page = login_page.login("valid_user", "valid_password")assert dashboard_page.is_loaded()assert "Welcome" in dashboard_page.get_welcome_message()def test_invalid_login(login_page):result_page = login_page.login("invalid_user", "invalid_password")assert isinstance(result_page, LoginPage)assert "Invalid credentials" in result_page.get_error_message()
与Behave(BDD)集成
# features/login.feature
Feature: User LoginAs a userI want to be able to login to the applicationSo that I can access my accountScenario: Successful login with valid credentialsGiven the user is on the login pageWhen the user enters "valid_user" as usernameAnd the user enters "valid_password" as passwordAnd the user clicks the login buttonThen the user should be redirected to the dashboardAnd the dashboard should display a welcome messageScenario: Failed login with invalid credentialsGiven the user is on the login pageWhen the user enters "invalid_user" as usernameAnd the user enters "invalid_password" as passwordAnd the user clicks the login buttonThen the user should remain on the login pageAnd an error message should be displayed
# steps/login_steps.py
from behave import given, when, then
from pages.login_page import LoginPage
from pages.dashboard_page import DashboardPage@given('the user is on the login page')
def step_impl(context):context.login_page = LoginPage(context.driver)@when('the user enters "{username}" as username')
def step_impl(context, username):context.login_page.enter_username(username)@when('the user enters "{password}" as password')
def step_impl(context, password):context.login_page.enter_password(password)@when('the user clicks the login button')
def step_impl(context):context.result_page = context.login_page.click_login()@then('the user should be redirected to the dashboard')
def step_impl(context):assert isinstance(context.result_page, DashboardPage)@then('the dashboard should display a welcome message')
def step_impl(context):assert "Welcome" in context.result_page.get_welcome_message()@then('the user should remain on the login page')
def step_impl(context):assert isinstance(context.result_page, LoginPage)@then('an error message should be displayed')
def step_impl(context):assert "Invalid credentials" in context.result_page.get_error_message()

3. 高级框架设计模式

工厂模式
class PageFactory:"""页面对象工厂类"""@staticmethoddef get_page(page_name, driver):pages = {"login": LoginPage,"dashboard": DashboardPage,"profile": ProfilePage,"settings": SettingsPage}if page_name.lower() not in pages:raise ValueError(f"不支持的页面: {page_name}")return pages[page_name.lower()](driver)
单例模式(驱动管理器)
class WebDriverManager:"""WebDriver管理器(单例模式)"""_instance = Nonedef __new__(cls):if cls._instance is None:cls._instance = super(WebDriverManager, cls).__new__(cls)cls._instance.driver = Nonereturn cls._instancedef get_driver(self, browser="chrome"):if self.driver is None:if browser.lower() == "chrome":self.driver = webdriver.Chrome()elif browser.lower() == "firefox":self.driver = webdriver.Firefox()else:raise ValueError(f"不支持的浏览器: {browser}")self.driver.maximize_window()self.driver.implicitly_wait(10)return self.driverdef quit(self):if self.driver:self.driver.quit()self.driver = None
策略模式(等待策略)
from abc import ABC, abstractmethodclass WaitStrategy(ABC):"""等待策略基类"""@abstractmethoddef wait_for(self, driver, locator):passclass VisibilityStrategy(WaitStrategy):"""等待元素可见策略"""def wait_for(self, driver, locator, timeout=10):return WebDriverWait(driver, timeout).until(EC.visibility_of_element_located(locator))class ClickableStrategy(WaitStrategy):"""等待元素可点击策略"""def wait_for(self, driver, locator, timeout=10):return WebDriverWait(driver, timeout).until(EC.element_to_be_clickable(locator))class PresenceStrategy(WaitStrategy):"""等待元素存在策略"""def wait_for(self, driver, locator, timeout=10):return WebDriverWait(driver, timeout).until(EC.presence_of_element_located(locator))# 使用策略模式的高级页面基类
class AdvancedBasePage:def __init__(self, driver):self.driver = driverself.wait_strategies = {"visible": VisibilityStrategy(),"clickable": ClickableStrategy(),"present": PresenceStrategy()}def find_element(self, locator, strategy="present"):return self.wait_strategies[strategy].wait_for(self.driver, locator)

4. 配置与日志管理

配置管理
import json
import osclass ConfigManager:"""配置管理器"""_instance = Nonedef __new__(cls):if cls._instance is None:cls._instance = super(ConfigManager, cls).__new__(cls)cls._instance.config = {}cls._instance.load_config()return cls._instancedef load_config(self, config_file="config.json"):if os.path.exists(config_file):with open(config_file, "r") as f:self.config = json.load(f)else:# 默认配置self.config = {"browser": "chrome","implicit_wait": 10,"explicit_wait": 20,"base_url": "https://example.com","headless": False,"screenshots_dir": "screenshots","logs_dir": "logs"}def get(self, key, default=None):return self.config.get(key, default)
日志管理
import logging
import os
from datetime import datetimeclass LogManager:"""日志管理器"""_instance = Nonedef __new__(cls):if cls._instance is None:cls._instance = super(LogManager, cls).__new__(cls)cls._instance.setup_logger()return cls._instancedef setup_logger(self):config = ConfigManager().get("logs", {})logs_dir = config.get("dir", "logs")log_level = config.get("level", "INFO")if not os.path.exists(logs_dir):os.makedirs(logs_dir)log_file = os.path.join(logs_dir, f"test_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")# 设置日志级别映射level_map = {"DEBUG": logging.DEBUG,"INFO": logging.INFO,"WARNING": logging.WARNING,"ERROR": logging.ERROR,"CRITICAL": logging.CRITICAL}# 设置根日志记录器self.logger = logging.getLogger("selenium_framework")self.logger.setLevel(level_map.get(log_level.upper(), logging.INFO))# 文件处理器file_handler = logging.FileHandler(log_file)file_handler.setLevel(level_map.get(log_level.upper(), logging.INFO))# 控制台处理器console_handler = logging.StreamHandler()console_handler.setLevel(level_map.get(log_level.upper(), logging.INFO))# 日志格式formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')file_handler.setFormatter(formatter)console_handler.setFormatter(formatter)# 添加处理器self.logger.addHandler(file_handler)self.logger.addHandler(console_handler)def get_logger(self):return self.logger

进阶技巧与最佳实践

1. 高级交互操作

ActionChains高级操作
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys# 基本鼠标操作
def perform_hover(driver, element):"""执行悬停操作"""ActionChains(driver).move_to_element(element).perform()def perform_right_click(driver, element):"""执行右键点击操作"""ActionChains(driver).context_click(element).perform()def perform_double_click(driver, element):"""执行双击操作"""ActionChains(driver).double_click(element).perform()def perform_drag_and_drop(driver, source_element, target_element):"""执行拖放操作"""ActionChains(driver).drag_and_drop(source_element, target_element).perform()def perform_drag_and_drop_by_offset(driver, element, x_offset, y_offset):"""执行偏移拖放操作"""ActionChains(driver).drag_and_drop_by_offset(element, x_offset, y_offset).perform()# 组合键盘操作
def perform_ctrl_click(driver, element):"""执行Ctrl+点击操作(多选)"""ActionChains(driver).key_down(Keys.CONTROL).click(element).key_up(Keys.CONTROL).perform()def perform_shift_click(driver, element):"""执行Shift+点击操作(范围选择)"""ActionChains(driver).key_down(Keys.SHIFT).click(element).key_up(Keys.SHIFT).perform()def perform_select_all(driver):"""执行全选操作(Ctrl+A)"""ActionChains(driver).key_down(Keys.CONTROL).send_keys('a').key_up(Keys.CONTROL).perform()def perform_copy(driver):"""执行复制操作(Ctrl+C)"""ActionChains(driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()def perform_paste(driver):"""执行粘贴操作(Ctrl+V)"""ActionChains(driver).key_down(Keys.CONTROL).send_keys('v').key_up(Keys.CONTROL).perform()# 链式组合操作
def perform_complex_action(driver, element1, element2):"""执行复杂组合操作"""ActionChains(driver)\.move_to_element(element1)\.pause(1)  # 暂停1秒.click()\.move_to_element(element2)\.click()\.perform()
处理JavaScript事件
def trigger_js_event(driver, element, event_name):"""触发JavaScript事件"""js_code = f"arguments[0].dispatchEvent(new Event('{event_name}'));"driver.execute_script(js_code, element)def focus_element(driver, element):"""使元素获取焦点"""driver.execute_script("arguments[0].focus();", element)def blur_element(driver, element):"""使元素失去焦点"""driver.execute_script("arguments[0].blur();", element)def scroll_to_element(driver, element):"""滚动到元素位置"""driver.execute_script("arguments[0].scrollIntoView({behavior: 'smooth', block: 'center'});", element)def scroll_to_top(driver):"""滚动到页面顶部"""driver.execute_script("window.scrollTo(0, 0);")def scroll_to_bottom(driver):"""滚动到页面底部"""driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

2. 窗口与标签页管理

def switch_to_window_by_title(driver, title):"""切换到指定标题的窗口"""current_window = driver.current_window_handlefor window in driver.window_handles:driver.switch_to.window(window)if title in driver.title:return True# 如果没有找到匹配标题的窗口,切回原窗口driver.switch_to.window(current_window)return Falsedef switch_to_window_by_url(driver, url_part):"""切换到URL包含指定部分的窗口"""current_window = driver.current_window_handlefor window in driver.window_handles:driver.switch_to.window(window)if url_part in driver.current_url:return True# 如果没有找到匹配URL的窗口,切回原窗口driver.switch_to.window(current_window)return Falsedef close_all_windows_except_current(driver):"""关闭除当前窗口外的所有窗口"""current_window = driver.current_window_handlefor window in driver.window_handles:if window != current_window:driver.switch_to.window(window)driver.close()driver.switch_to.window(current_window)def open_new_tab(driver, url=None):"""打开新标签页"""driver.execute_script("window.open();")driver.switch_to.window(driver.window_handles[-1])if url:driver.get(url)def handle_popup_window(driver, action="accept"):"""处理弹出窗口"""try:if action.lower() == "accept":driver.switch_to.alert.accept()elif action.lower() == "dismiss":driver.switch_to.alert.dismiss()elif action.lower() == "text":return driver.switch_to.alert.textelse:raise ValueError(f"不支持的操作: {action}")return Trueexcept:return False

3. iframe处理

def switch_to_frame_by_index(driver, index):"""通过索引切换到iframe"""try:driver.switch_to.frame(index)return Trueexcept:return Falsedef switch_to_frame_by_name_or_id(driver, name_or_id):"""通过名称或ID切换到iframe"""try:driver.switch_to.frame(name_or_id)return Trueexcept:return Falsedef switch_to_frame_by_element(driver, element):"""通过元素切换到iframe"""try:driver.switch_to.frame(element)return Trueexcept:return Falsedef switch_to_parent_frame(driver):"""切换到父iframe"""try:driver.switch_to.parent_frame()return Trueexcept:return Falsedef switch_to_default_content(driver):"""切换到主文档"""try:driver.switch_to.default_content()return Trueexcept:return Falsedef get_iframe_count(driver):"""获取页面中iframe的数量"""return len(driver.find_elements(By.TAG_NAME, "iframe"))def execute_in_iframe(driver, iframe_locator, action_func):"""在iframe中执行操作"""driver.switch_to.frame(driver.find_element(*iframe_locator))try:result = action_func(driver)return resultfinally:driver.switch_to.default_content()

4. 文件上传与下载

文件上传
def upload_file(driver, file_input_locator, file_path):"""上传文件(适用于<input type="file">元素)"""try:file_input = driver.find_element(*file_input_locator)file_input.send_keys(file_path)return Trueexcept Exception as e:print(f"文件上传失败: {e}")return Falsedef upload_file_without_input(driver, upload_button_locator, file_path):"""上传文件(适用于没有可见<input type="file">的情况)使用JS创建一个隐藏的文件输入元素"""try:# 创建一个隐藏的文件输入元素js_script = """const input = document.createElement('input');input.type = 'file';input.style.display = 'none';input.id = 'hidden-file-input';document.body.appendChild(input);return input;"""file_input = driver.execute_script(js_script)# 设置文件路径file_input.send_keys(file_path)# 触发上传按钮的点击事件upload_button = driver.find_element(*upload_button_locator)driver.execute_script("arguments[0].click();", upload_button)# 移除隐藏的文件输入元素driver.execute_script("document.getElementById('hidden-file-input').remove();")return Trueexcept Exception as e:print(f"文件上传失败: {e}")return False
文件下载
import os
import time
from pathlib import Pathdef setup_chrome_download_path(download_dir):"""设置Chrome浏览器的下载路径"""options = webdriver.ChromeOptions()prefs = {"download.default_directory": download_dir,"download.prompt_for_download": False,"download.directory_upgrade": True,"safebrowsing.enabled": True}options.add_experimental_option("prefs", prefs)return optionsdef wait_for_download_to_complete(download_dir, timeout=60, check_interval=1):"""等待下载完成"""start_time = time.time()while time.time() - start_time < timeout:# 检查是否有部分下载的文件(.crdownload, .part等)downloading_files = list(Path(download_dir).glob("*.crdownload")) + list(Path(download_dir).glob("*.part"))if not downloading_files:# 找出最近下载的文件downloaded_files = list(Path(download_dir).glob("*"))if downloaded_files:downloaded_files.sort(key=lambda x: x.stat().st_mtime, reverse=True)return str(downloaded_files[0])time.sleep(check_interval)raise TimeoutError("文件下载超时")def download_file(driver, download_button_locator, download_dir, timeout=60):"""下载文件"""try:# 确保下载目录存在os.makedirs(download_dir, exist_ok=True)# 点击下载按钮download_button = driver.find_element(*download_button_locator)download_button.click()# 等待下载完成downloaded_file = wait_for_download_to_complete(download_dir, timeout)return downloaded_fileexcept Exception as e:print(f"文件下载失败: {e}")return None

5. 截图与日志

import os
import time
from datetime import datetimedef take_screenshot(driver, directory="screenshots", filename=None):"""截取屏幕截图"""try:# 确保目录存在os.makedirs(directory, exist_ok=True)# 如果未指定文件名,使用时间戳生成if not filename:timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")filename = f"screenshot_{timestamp}.png"# 拼接完整路径file_path = os.path.join(directory, filename)# 截图driver.save_screenshot(file_path)return file_pathexcept Exception as e:print(f"截图失败: {e}")return Nonedef take_element_screenshot(driver, element, directory="screenshots", filename=None):"""截取元素截图"""try:# 确保目录存在os.makedirs(directory, exist_ok=True)# 如果未指定文件名,使用时间戳生成if not filename:timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")filename = f"element_screenshot_{timestamp}.png"# 拼接完整路径file_path = os.path.join(directory, filename)# 截取元素截图element.screenshot(file_path)return file_pathexcept Exception as e:print(f"元素截图失败: {e}")return Nonedef screenshot_on_failure(func):"""失败时自动截图的装饰器"""def wrapper(*args, **kwargs):try:return func(*args, **kwargs)except Exception as e:# 假设第一个参数是self,第二个参数是driverdriver = args[1] if len(args) > 1 else Noneif driver:timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")filename = f"failure_{func.__name__}_{timestamp}.png"take_screenshot(driver, filename=filename)raise ereturn wrapper

6. 高级断言与验证

def verify_element_text(driver, locator, expected_text, contains=False):"""验证元素文本"""try:element = driver.find_element(*locator)actual_text = element.textif contains:assert expected_text in actual_text, f"期望文本包含'{expected_text}',实际文本为'{actual_text}'"else:assert actual_text == expected_text, f"期望文本为'{expected_text}',实际文本为'{actual_text}'"return Trueexcept AssertionError as e:print(f"验证失败: {e}")return Falsedef verify_element_attribute(driver, locator, attribute, expected_value, contains=False):"""验证元素属性"""try:element = driver.find_element(*locator)actual_value = element.get_attribute(attribute)if contains:assert expected_value in actual_value, f"期望属性'{attribute}'包含'{expected_value}',实际值为'{actual_value}'"else:assert actual_value == expected_value, f"期望属性'{attribute}'为'{expected_value}',实际值为'{actual_value}'"return Trueexcept AssertionError as e:print(f"验证失败: {e}")return Falsedef verify_element_visible(driver, locator, timeout=10):"""验证元素可见"""try:WebDriverWait(driver, timeout).until(EC.visibility_of_element_located(locator))return Trueexcept TimeoutException:print(f"元素在{timeout}秒内未可见: {locator}")return Falsedef verify_element_not_visible(driver, locator, timeout=10):"""验证元素不可见"""try:WebDriverWait(driver, timeout).until(EC.invisibility_of_element_located(locator))return Trueexcept TimeoutException:print(f"元素在{timeout}秒内仍然可见: {locator}")return Falsedef verify_url(driver, expected_url, contains=False, timeout=10):"""验证URL"""try:if contains:WebDriverWait(driver, timeout).until(lambda d: expected_url in d.current_url)else:WebDriverWait(driver, timeout).until(lambda d: d.current_url == expected_url)return Trueexcept TimeoutException:print(f"URL验证失败,期望URL{'' if contains else '为'}{expected_url},实际URL为{driver.current_url}")return Falsedef verify_title(driver, expected_title, contains=False, timeout=10):"""验证页面标题"""try:if contains:WebDriverWait(driver, timeout).until(lambda d: expected_title in d.title)else:WebDriverWait(driver, timeout).until(lambda d: d.title == expected_title)return Trueexcept TimeoutException:print(f"标题验证失败,期望标题{'' if contains else '为'}{expected_title},实际标题为{driver.title}")return False

性能优化与调试技巧

1. 性能测试与优化

测量页面加载时间
def measure_page_load_time(driver, url):"""测量页面加载时间"""start_time = time.time()driver.get(url)# 等待页面完全加载WebDriverWait(driver, 60).until(lambda d: d.execute_script("return document.readyState") == "complete")end_time = time.time()load_time = end_time - start_timereturn load_time
使用Performance API获取详细性能数据
def get_performance_metrics(driver):"""获取浏览器性能指标"""# 使用Navigation Timing APInavigation_timing = driver.execute_script("""var performance = window.performance;var timingObj = performance.timing;var loadTime = timingObj.loadEventEnd - timingObj.navigationStart;var dnsTime = timingObj.domainLookupEnd - timingObj.domainLookupStart;var tcpTime = timingObj.connectEnd - timingObj.connectStart;var serverTime = timingObj.responseEnd - timingObj.requestStart;var domTime = timingObj.domComplete - timingObj.domLoading;return {'loadTime': loadTime,'dnsTime': dnsTime,'tcpTime': tcpTime,'serverTime': serverTime,'domTime': domTime,'firstPaint': timingObj.responseStart - timingObj.navigationStart,'ttfb': timingObj.responseStart - timingObj.requestStart};""")return navigation_timingdef get_resource_timing(driver):"""获取资源加载时间"""resources = driver.execute_script("""var resources = window.performance.getEntriesByType('resource');return resources.map(function(resource) {return {'name': resource.name,'startTime': resource.startTime,'duration': resource.duration,'initiatorType': resource.initiatorType,'size': resource.transferSize};});""")return resources
优化执行速度
def optimize_chrome_for_performance():"""优化Chrome浏览器以提高性能"""options = webdriver.ChromeOptions()# 禁用不必要的浏览器功能options.add_argument("--disable-extensions")options.add_argument("--disable-gpu")options.add_argument("--disable-dev-shm-usage")options.add_argument("--disable-browser-side-navigation")options.add_argument("--disable-infobars")options.add_argument("--disable-notifications")options.add_argument("--disable-popup-blocking")# 减少内存使用options.add_argument("--disable-features=site-per-process")options.add_argument("--process-per-site")# 禁用图片加载以提高速度prefs = {"profile.managed_default_content_settings.images": 2,"profile.default_content_setting_values.notifications": 2,"profile.default_content_setting_values.geolocation": 2}options.add_experimental_option("prefs", prefs)# 使用无头模式options.add_argument("--headless")return options

2. 高级调试技巧

获取浏览器控制台日志
def get_browser_logs(driver):"""获取浏览器控制台日志"""logs = driver.get_log('browser')return logsdef print_browser_logs(driver):"""打印浏览器控制台日志"""logs = driver.get_log('browser')for log in logs:print(f"[{log['level']}] {log['message']}")

在这里插入图片描述


http://www.hkcw.cn/article/OcJmiLDtpG.shtml

相关文章

人工智能与机器学习:Python从零实现性回归模型

🧠 向所有学习者致敬! “学习不是装满一桶水,而是点燃一把火。” —— 叶芝 我的博客主页: https://lizheng.blog.csdn.net 🌐 欢迎点击加入AI人工智能社区! 🚀 让我们一起努力,共创AI未来! 🚀 前言 在 AI 的热潮中,很容易忽视那些让它得以实现的基础数学和技…

【Pytorch安装】深度学习环境配置详细教程!(Pytorch GPU和CPU版本 Python,Anaconda3 和 Pycharm 的安装)

目录 一、前言 1、简介 2、准备 二、Python安装 1、下载安装包 &#xff08;1&#xff09;搜索官网 &#xff08;2&#xff09;下载最新版本 &#xff08;3&#xff09;选择其他版本下载 2、安装 &#xff08;1&#xff09;双击进入安装 &#xff08;2&#xff09;查…

【Python实战】——Python+Opencv是实现车牌自动识别

&#x1f349;CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一&#xff5c;统计学&#xff5c;干货分享          擅长Python、Matlab、R等主流编程软件          累计十余项国家级比赛奖项&#xff0c;参与研究经费10w、40w级横向 文…

PyCharm 创建第一个 Python 项目

PyCharm 启动的时候其实已经提供了很多快捷方式&#xff1a; 我们点击"新建脚本"这个按钮&#xff0c;PyCharm 就会帮我们创建一个 script.py 文件&#xff0c;并自动初始化代码和环境&#xff1a; 我们可以尝试修改脚本文件&#xff0c;并运行查看输出结果&#xff…

五子棋Python源代码

图形化界面 库&#xff1a;numpy&#xff0c;pygame 效果&#xff1a; 源代码&#xff1a; #调用pygame库 import pygame import sys #调用常用关键字常量 from pygame.locals import QUIT,KEYDOWN import numpy as np #初始化pygame pygame.init() #获取对显示系统的访问&a…

Ubantu 20.04 安装 Mujoco210、mujoco-py、gym及报错解决

1. 安装Mujoco 1.1 官网下载Mujoco210安装包 Mujoco2.1.0下载链接 选第一个 1.2 创建文件夹并解压安装包 mkdir ~/.mujoco创建好后&#xff0c;点击显示隐藏文件可以找到 找到刚刚下载的压缩包所在位置&#xff08;一般在下载目录下&#xff09;&#xff0c;右键选择 在终…

CUDA重大更新:原生Python可直接编写高性能GPU程序

NVIDIA 在2025年GTC大会上宣布了一项具有里程碑意义的技术更新&#xff1a;CUDA并行计算平台正式支持原生Python编程。这一突破性进展将消除Python开发者进入GPU加速计算领域的主要技术壁垒&#xff0c;无需再依赖C/C语言作为中介。 此次更新意味着数千万Python开发者现在可以…

Python的pandas库基础知识(超详细教学)

目录 一、配置环境 二、序列和数据表 2.1 初始化 2.2 获取数值 2.3 获取索引 2.4 索引取内容 2.5 索引改变取值 2.6 字典生成序列 2.7 计算取值出现次数 2.8 数据表 2.9 数据表添加新变量 2.10 获取列名 2.11 根据列名获取数据 2.12 输出固定行 2.13 输出多行…

男子把矿泉水带到高原一觉醒来大变样 当地的水就没事

男子把矿泉水带到高原,一觉醒来大变样,当地的水就没事!矿泉水被带到高原后出现的物理变化主要与高原环境的气压和温度条件有关,具体表现如下:1.瓶体膨胀现象高原地区平均气压仅为平地的60%-70%,密封瓶装水在运输过程中因外部气压骤降,内部气体体积膨胀导致瓶身变形,可能…

年轻人,花1000多亿干这个!押注谷子经济

年轻人,花1000多亿干这个!押注谷子经济。“六一”前夕,义乌国际商贸城一区内,印着动漫主人公的巨幅海报已经换上,最新的盲盒、徽章以及各种周边好物被摆在了最显眼的位置。2000多家玩具批发商户汇聚于此,谷子浓度越来越高。“谷子”由英文“Goods”音译而来,这个词囊括了…

“端午躲好午,不愁没好运”怎么躲午? 五种方法保平安

“端午躲好午,不愁没好运”怎么躲午? 五种方法保平安!艾旗迎百福,蒲剑斩千邪。今年的端午节比往年早一些,大街小巷已经有很多售卖艾蒿的商贩。端午节是中国传统的四大佳节之一,有着悠久的历史和丰富的文化习俗。进入农历五月后,阳光变得炙热,雨水增多,这个时节是各种毒…

基于python合成100X100的透明背景图片和图标

一、实现思路 1. 加载图像 使用 Image.open() 加载图标图像。 Image.new() 创建一个新的透明背景图像。 2. 调整大小 使用 resize() 方法调整图标图像的大小&#xff0c;确保它不会超过背景图像的一半大小。这里使用 Image.Resampling.LANCZOS 作为过滤器&…

新能源电池壳冲压车间看板实施

车间漫游 这个厂区就一个车间&#xff0c;面积还好&#xff0c;但是被机器和仓库区的产品塞得满满当当。我观察车间布局&#xff0c;为了出货方便&#xff0c;仓库区就在大门旁&#xff0c;叉车到门口货车的转运很快速方便。到车间后&#xff0c;对接人还没来&#xff0c;我们…

秋招Day10 - JVM - 内存管理

JVM组织架构主要有三个部分&#xff1a;类加载器、运行时数据区和字节码执行引擎 类加载器&#xff1a;负责从文件系统、网络或其他来源加载class文件&#xff0c;将class文件中的二进制数据加载到内存中运行时数据区&#xff1a;运行时的数据存放的区域&#xff0c;分为方法区…

茅台经销商被罚后起诉市监局续:法院重审一审撤销处罚决定

“贵州茅台经销商被罚后起诉市监局”一案近日有了最新进展。澎湃新闻从当事人及其代理律师处了解到,贵州省都匀市人民法院日前对该案作出重审一审判决,认定案涉处罚决定适用法律错误、程序违法,决定撤销黔南州市场监督管理局此前作出的行政处罚决定。澎湃新闻此前报道,郭亮…

花生壳里塞冰淇淋卖28一个 真果壳制成引争议

花生壳里塞冰淇淋卖28一个 真果壳制成引争议!近日,一位女子发布视频称,在苏州一家餐厅遇到了价格高昂的冰淇淋。她提到,一份装在花生壳里的冰淇淋售价28元,分量却少得惊人。尽管服务员会帮忙切开,仪式感十足,但她认为并不值这个价格。记者随后走访了位于苏州市姑苏区美罗…

严重或可能导致死亡!不要空腹吃荔枝 了解“荔枝病”真相

最近,话题#荔枝病突然成为热门话题。广东一名女子因过量食用荔枝后,次日出现头晕不适、持续性鼻出血等症状,最终被诊断为“荔枝病”。科普博主表示,此病严重时会引发休克甚至死亡。荔枝病也称为低血糖急性脑炎综合征,发病人群以4至11岁的儿童较为多见。一旦发生,会导致人…

工信部曝光:“腾讯支付”有诈!冒名诈骗需警惕

工信部反诈专班近日发布消息称,有用户举报发现了一款名为“腾讯支付”的理财APP。经与腾讯官方核实,这款APP并非大众熟知的“微信支付”。实际上,微信支付仅支持在微信应用内使用,并没有独立的APP。所谓的“腾讯支付”APP冒充腾讯集团名义,恶意使用腾讯名称,虚构腾讯股权…

郑州最长寿奶奶根本闲不住:我115岁,成大人了

5月28日河南新密,“我115岁,成大人了!”郑州最长寿奶奶根本闲不住,推车散步,眼神好,能爬4楼,还爱洗碗。70岁小女儿:每次回家喊一声妈,很幸福。责任编辑:zx0002

男子家暴被捕后与弟弟弟媳造假借条起诉妻子追债 三人因虚假诉讼被判刑

被丈夫殴打导致4根肋骨骨折后,刘颖报了警,并向法院起诉离婚。而丈夫的弟弟也起诉了她——要她偿还三百多万“债务”。几年下来,刘颖和丈夫的“夫妻官司”牵涉了离婚案、故意伤害案、民间借贷案。其后,刘颖的丈夫李某涛,以及他的弟弟、弟媳三人,均被湖南邵阳的一审法院以虚…