自动化脚本的健壮性设计:等待策略与异常处理
脚本能跑通一次,不等于能稳定跑下去。自动化脚本最大的敌人是"不确定性"——网络延迟、页面加载速度、弹窗、Cookie提示、DOM更新……任何一个环节出问题,整个流程就断了。
这篇文章总结我在MSD自动化项目中积累的健壮性设计经验,目标是让脚本在无人值守的情况下也能稳定运行。
核心原则:永远不要假设页面状态
新手写Selenium脚本最容易犯的错:假设页面会按预期加载。
比如这样写:
# 危险写法:假设页面已经加载完成
driver.find_element(By.XPATH, "//button[@id='submit']").click()
在生产环境,这行代码有30%的概率抛出TimeoutException或StaleElementReferenceException。正确做法是显式等待 + 重试机制。
等待策略:三种方式对比
方式一:time.sleep(不推荐)
最简单也最脆弱。设3秒,网络慢的时候不够;设10秒,每次都等10秒,效率极低。
方式二:WebDriverWait(推荐)
显式等待,直到条件满足才继续执行。智能、高效。
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# 最多等10秒,直到按钮可点击
button = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.XPATH, "//button[@id='submit']"))
)
button.click()
方式三:自定义等待条件(高级)
有些场景EC内置条件不够用,比如"等待某个元素的值发生变化":
from selenium.webdriver.support.ui import WebDriverWait
class element_value_changes:
def __init__(self, locator, old_value):
self.locator = locator
self.old_value = old_value
def __call__(self, driver):
elem = driver.find_element(*self.locator)
return elem.get_attribute("value") != self.old_value
# 等待输入框的值发生变化
WebDriverWait(driver, 10).until(
element_value_changes((By.ID, "price"), "0")
)
异常处理:捕获 + 重试 + 日志
三层防护:
- 捕获具体异常:不要直接
except Exception,要针对具体异常类型处理 - 重试机制:对于偶发性错误(如StaleElement),重试2-3次通常能解决
- 详细日志:出错时记录URL、页面截图、元素状态,方便事后排查
import traceback
from selenium.common.exceptions import (
StaleElementReferenceException,
TimeoutException,
ElementClickInterceptedException
)
def safe_click(driver, xpath, max_retries=3):
for attempt in range(max_retries):
try:
elem = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.XPATH, xpath))
)
elem.click()
return True
except StaleElementReferenceException:
if attempt == max_retries - 1:
driver.save_screenshot(f"error_stale_{attempt}.png")
raise
time.sleep(0.5)
except ElementClickInterceptedException:
# 被遮挡,尝试JS点击
elem = driver.find_element(By.XPATH, xpath)
driver.execute_script("arguments[0].click();", elem)
return True
except TimeoutException:
driver.save_screenshot(f"error_timeout.png")
raise
return False
弹窗处理:隐性弹窗的应对
生产环境最头疼的是随机弹窗——Cookie提示、广告弹窗、系统通知,随时可能出现,打断你的脚本。
解决方案:在每次关键操作前,先检查并关闭弹窗:
def dismiss_popups(driver):
popup_selectors = [
"//button[contains(text(),'同意')]",
"//button[contains(text(),'关闭')]",
"//div[@class='modal-close']",
"//span[@class='close-btn']",
]
for selector in popup_selectors:
try:
btn = driver.find_element(By.XPATH, selector)
if btn.is_displayed():
btn.click()
time.sleep(0.3)
except:
pass
总结:健壮性自检清单
交付脚本前,用这个清单过一遍:
- 所有元素操作都用了WebDriverWait?
- StaleElement有没有重试机制?
- 有没有处理弹窗的逻辑?
- 出错时有没有截图和日志?
- 有没有模拟慢网络环境测试过?
- PyInstaller打包后在干净环境跑过吗?
健壮性不是附加功能,是交付的基本门槛。客户不关心你用了什么技术,只关心"能不能稳定跑"。