影刀RPA 异常处理的五种模式:try-catch之外的思路
作者:林焱
什么情况用这个
几乎所有影刀教程教你异常处理时都是同一句话:“用try-catch包起来就行了”。但实际开发中,try-catch只是异常处理的最后一步,远不是全部。有些异常你应该让它直接中断流程(不处理比错误处理更安全),有些异常只要打个日志跳过就好,有些异常需要重试,还有些异常代表你的流程逻辑本身有问题。
把所有的异常都用try-catch一把梭,结果就是流程表面上不报错了,但实际数据要么丢了要么错了——这是最危险的"静默失败"。
这篇文章拆解五种异常处理模式,让你知道面对不同类型的错误应该采取什么策略。
怎么做
模式一:重试模式——临时故障自动恢复
适用场景:网络波动、服务暂时不可用、元素偶发加载慢。
importtimeimportrandomdefretry_on_failure(func,max_retries=3,base_delay=1):""" 带退避的重试 func: 可能失败的操作函数 max_retries: 最大重试次数 base_delay: 基础延迟秒数 """forattemptinrange(max_retries):try:returnfunc()exceptExceptionase:ifattempt==max_retries-1:# 最后一次重试也失败了,抛出raiseException(f"重试{max_retries}次后仍然失败:{e}")# 指数退避:1秒→2秒→4秒delay=base_delay*(2**attempt)# 加随机抖动避免"惊群效应"delay+=random.uniform(0,0.5)print(f"第{attempt+1}次失败,{delay:.1f}秒后重试:{e}")time.sleep(delay)# 使用示例defclick_submit_btn():btn=driver.find_element(By.CSS_SELECTOR,"#submit")btn.click()# 最多重试3次retry_on_failure(click_submit_btn,max_retries=3)重试模式的陷阱:
- 重试前确认失败原因:网络超时可以重试,业务逻辑错误重试一百次也没用
- 重试要设上限:无限重试等于流程永远不结束
- 耗时操作(API调用、文件下载)重试间隔要更长
模式二:跳过模式——非关键失败继续执行
店群矩阵自动化突破运营极限!
适用场景:批量处理中的个别数据异常、某条记录格式不对但不影响大局。
defprocess_with_skip(items,process_func):"""逐个处理,失败就跳过并记录"""results={"success":[],"skipped":[],"total":len(items)}foridx,iteminenumerate(items):try:result=process_func(item)results["success"].append({"index":idx,"data":item,"result":result})exceptExceptionase:# 记录跳过的results["skipped"].append({"index":idx,"data":item,"error":str(e)})print(f"⚠️ 跳过第{idx}条:{e}")continue# 输出统计print(f"处理完成:{len(results['success'])}成功,{len(results['skipped'])}跳过")# 如果有太多跳过,可能需要告警skip_rate=len(results["skipped"])/len(items)ifskip_rate>0.1:# 超过10%异常print(f"⚠️ 跳过率过高:{skip_rate:.1%},建议人工检查")returnresults跳过模式的关键:一定要记录跳过了哪些、为什么跳过。下次运行前先检查上次的跳过记录。
模式三:降级模式——主方案失败,启用备用方案
适用场景:有备选的操作路径。
defclick_element_with_fallback(main_selector,fallback_selectors):""" 主选择器找不到时,尝试备选选择器 按优先级从高到低排列 """selectors=[main_selector]+fallback_selectors errors=[]fori,selectorinenumerate(selectors):try:element=driver.find_element(By.CSS_SELECTOR,selector)ifelement.is_displayed():element.click()returnTrueelse:errors.append(f"{selector}: 不可见")exceptExceptionase:errors.append(f"{selector}:{e}")# 所有方案都失败了raiseException(f"所有选择器都失败:{'; '.join(errors)}")# 使用click_element_with_fallback("#new-submit-btn",# 首选:新版按钮["#old-submit-btn",# 备选1:旧版按钮"button.submit"]# 备选2:通用选择器)降级的更多场景:
# 数据采集的降级:API → DOM解析 → 截图OCRdefcollect_data_with_fallback():"""三级降级采集"""# 方案A:调API(最快)try:returncollect_via_api()except:pass# 方案B:解析DOM(备选)try:returncollect_via_dom()except:pass# 方案C:截图OCR(最后手段)returncollect_via_ocr()模式四:快死模式——严重错误立即中断
适用场景:数据完整性问题、关键依赖不可用、继续执行可能造成更大损失。
classFatalError(Exception):"""致命错误——应该立即中断流程"""passdefcheck_prerequisites():"""流程前置条件检查——不满足则立即中断"""# 检查关键文件ifnotos.path.exists("D:/data/source.xlsx"):raiseFatalError("源数据文件不存在,无法继续")# 检查数据库连接try:conn=sqlite3.connect("D:/data/business.db")conn.close()except:raiseFatalError("数据库不可用,无法继续")# 检查磁盘空间(至少100MB)importshutil free_space=shutil.disk_usage("D:/").freeiffree_space<100*1024*1024:raiseFatalError(f"磁盘空间不足:{free_space/1024/1024:.0f}MB")# 主流程入口try:check_prerequisites()exceptFatalErrorase:# 致命错误:记录后通知,不重试不降级send_alert(f"流程中断:{e}")raise# 让流程终止快速失败原则:不满足前置条件立即失败,比跑了半小时才失败要好一百倍。
模式五:防护模式——防御性编程
在操作前先检查,避免异常发生:
defsafe_divide(a,b,default=0):"""防除零错误"""returna/bifb!=0elsedefaultdefsafe_list_access(lst,index,default=None):"""防索引越界"""returnlst[index]if0<=index<len(lst)elsedefaultdefsafe_dict_access(d,key,default=None):"""防键不存在"""returnd.get(key,default)defsafe_file_write(filepath,content):"""安全写文件——目录不存在先创建"""os.makedirs(os.path.dirname(filepath),exist_ok=True)# 先写到临时文件,成功后再替换tmp_path=filepath+".tmp"withopen(tmp_path,"w",encoding="utf-8")asf:f.write(content)# 原子替换os.replace(tmp_path,filepath)defsafe_element_text(element,default=""):"""安全获取元素文本"""try:returnelement.text.strip()except:returndefault防御性编程的思想是:把检查做在操作前面,而不是等出了异常再去处理。
有什么坑
坑一:try-catch包了太多代码
# ❌ 一个try-catch包了所有逻辑try:open_page()login()collect_data()save_to_excel()send_email()exceptExceptionase:print(f"出错了:{e}")问题:你不知道具体是哪一步出错,也不知道当前的数据状态是什么。
解决:每个独立步骤有自己的异常处理,粒度要合理:
open_page()# 这一步失败无法继续 → 不try,直接中断login_with_retry()# 网络问题 → 重试模式collect_with_skip()# 个别数据问题 → 跳过模式save_to_excel()# 文件写入 → try-catch + 备份send_email()# 通知 → 失败只记日志,不中断主流程坑二:静默吞掉所有异常
# ❌ 最危险的写法try:critical_operation()except:pass# 啥也不做,假装没出错后果:流程表面上正常结束了,实际上关键数据没处理。这是自动化中最隐蔽的Bug。
temu店群自动化报活动案例
解决:每个except至少做三件事之一:记录日志、重试、向上抛出。
坑三:在except中又抛出另一个异常掩盖了原始错误
# ❌ 原始错误被掩盖try:process_data()exceptExceptionase:raiseException("数据处理失败")# 丢了原始错误信息e解决:
try:process_data()exceptExceptionase:raiseException(f"数据处理失败:{e}")frome# 保留原始异常链坑四:重试中使用了固定延迟
# ❌ 固定延迟重试foriinrange(3):try:api_call()breakexcept:time.sleep(2)# 每次都等2秒问题:当大量流程同时重试时,都用固定延迟会在同一时刻"撞车"。
解决:使用指数退避+随机抖动。
坑五:没有区分异常类型
# ❌ 笼统捕获所有异常try:do_something()exceptException:# 所有异常都按同一种方式处理pass解决:区分处理:
fromselenium.common.exceptionsimportTimeoutException,NoSuchElementExceptiontry:do_something()exceptTimeoutException:# 超时 → 重试retry()exceptNoSuchElementException:# 元素不存在 → 可能是页面变了send_alert("页面结构可能变化")raiseexceptValueError:# 数据格式错误 → 跳过这条skip_and_log()exceptExceptionase:# 未知异常 → 记录并向上抛log_and_raise(e)总结:异常处理的正确姿势不是"把所有东西包在try-catch里",而是分场景选择合适的处理模式。网络波动→重试,个别数据异常→跳过,关键依赖不可用→快死,可选路径→降级,操作前检查→防护。记住:一个被静默吞掉的异常,比你看到的报错危险100倍。