自动化

自动化脚本的健壮性设计:等待策略与异常处理

2026-06-20 · 约6分钟阅读 · Python · Selenium · 异常处理

脚本能跑通一次,不等于能稳定跑下去。自动化脚本最大的敌人是"不确定性"——网络延迟、页面加载速度、弹窗、Cookie提示、DOM更新……任何一个环节出问题,整个流程就断了。

这篇文章总结我在MSD自动化项目中积累的健壮性设计经验,目标是让脚本在无人值守的情况下也能稳定运行。

核心原则:永远不要假设页面状态

新手写Selenium脚本最容易犯的错:假设页面会按预期加载

比如这样写:

# 危险写法:假设页面已经加载完成
driver.find_element(By.XPATH, "//button[@id='submit']").click()

在生产环境,这行代码有30%的概率抛出TimeoutExceptionStaleElementReferenceException。正确做法是显式等待 + 重试机制

等待策略:三种方式对比

方式一: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")
)

异常处理:捕获 + 重试 + 日志

三层防护:

  1. 捕获具体异常:不要直接except Exception,要针对具体异常类型处理
  2. 重试机制:对于偶发性错误(如StaleElement),重试2-3次通常能解决
  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打包后在干净环境跑过吗?

健壮性不是附加功能,是交付的基本门槛。客户不关心你用了什么技术,只关心"能不能稳定跑"。

🤖
AI助手
ONLINE