2. Python异常处理语法:try-except结构、捕获特定异常、else子句、finally子句

异常处理,说白了就是给代码穿上防弹衣。

我在RPA项目里见过太多因为一个小异常就导致整个流程崩溃的情况。比如读取Excel时文件被占用、网络请求超时、元素定位失败……这些在RPA开发中简直是家常便饭。所以,掌握Python的异常处理语法,是每个RPA工程师的必修课。

2.1 try-except基础结构

先看最基础的结构。try块里放可能出错的代码,except块里放出错后的补救措施。

try:
    # 可能出错的代码
    result = 10 / 0
except:
    # 出错后执行的代码
    print("哎呀,出错了!")

嗯,这里要注意:裸except会捕获所有异常。我个人习惯是尽量少用这种写法,因为它会吞掉你意想不到的错误,比如键盘中断(Ctrl+C)也会被捕获,导致程序无法正常退出。

避坑指南:我曾经在RPA流程里用了裸except,结果调试时想用Ctrl+C中断程序,发现根本停不下来。后来排查了半天才发现是except把KeyboardInterrupt也给吞了。所以,尽量指定异常类型。

2.2 捕获特定异常

RPA开发中,不同的异常需要不同的处理方式。比如文件找不到和网络超时,处理逻辑完全不一样。

try:
    with open("config.json", "r") as f:
        config = f.read()
except FileNotFoundError:
    print("配置文件不存在,使用默认配置")
    config = "{}"
except PermissionError:
    print("没有权限读取配置文件,请检查文件权限")
    config = "{}"
except Exception as e:
    print(f"未知错误:{e}")
    config = "{}"

你想想看,如果所有异常都用同一个except处理,你根本不知道到底是文件不存在还是权限不够。这在RPA流程里很要命——你不知道该重试还是该报错。

我在项目中遇到过这样的情况:一个RPA机器人每天早上8点准时跑,但偶尔会报错。后来发现是运维在凌晨更新了配置文件,导致文件被临时锁定。如果当时用了特定异常捕获,就能快速定位到是文件被占用,而不是去排查网络或数据库问题。

2.3 else子句

else子句可能很多人用得少,但我觉得它挺优雅的。它的作用是:当try块没有抛出异常时,执行else块

try:
    data = fetch_data_from_api()
except ConnectionError:
    print("网络连接失败,稍后重试")
    data = None
except TimeoutError:
    print("请求超时,请检查网络")
    data = None
else:
    # 只有数据获取成功才执行
    print("数据获取成功,开始处理")
    process_data(data)

为什么要用else?直接把处理逻辑写在try块里不行吗?

其实,把正常逻辑和异常处理分开,代码更清晰。try块里只放可能出错的代码,else块里放成功后的逻辑。这样别人看代码时,一眼就能分清「正常流程」和「异常流程」。

我的小技巧:在RPA流程中,我习惯把「重试逻辑」放在else块里。比如:try里尝试登录系统,成功后else里执行后续操作;如果登录失败,except里处理重试或告警。这样逻辑层次特别清楚。

2.4 finally子句

finally子句是异常处理里的「铁律」——无论是否发生异常,它都会执行

try:
    file = open("report.xlsx", "w")
    file.write(data)
except IOError:
    print("写入文件失败")
finally:
    # 无论如何都要关闭文件
    file.close()
    print("文件已关闭")

这个在RPA里太重要了。你想想,RPA机器人经常要操作各种资源:打开浏览器、连接数据库、读取文件……如果中间出了异常,资源没释放,下次运行就会出问题。

我曾经犯过一个错误:在RPA流程里打开了一个Excel文件,中间代码报错了,但没写finally来关闭文件。结果第二天机器人跑的时候,Excel文件一直被昨天的进程锁着,导致整个流程卡死。从那以后,我所有涉及资源操作的代码,都会加上finally来确保释放。

核心要点:finally子句常用于清理资源,比如关闭文件、关闭数据库连接、释放锁等。记住:finally里的代码一定会执行,即使try或except里有return语句。

2.5 综合示例:RPA中的异常处理

最后,我写一个RPA场景的完整示例,把上面几个知识点串起来。

def rpa_login_and_fetch_data():
    """
    RPA登录系统并获取数据
    """
    browser = None
    try:
        # 打开浏览器
        browser = webdriver.Chrome()
        browser.get("https://example.com/login")
        
        # 输入账号密码
        browser.find_element(By.ID, "username").send_keys("admin")
        browser.find_element(By.ID, "password").send_keys("123456")
        browser.find_element(By.ID, "login-btn").click()
        
        # 等待页面加载
        WebDriverWait(browser, 10).until(
            EC.presence_of_element_located((By.ID, "data-table"))
        )
        
    except TimeoutException:
        print("页面加载超时,可能是网络问题")
        # 发送告警通知
        send_alert("登录超时")
        return None
        
    except NoSuchElementException as e:
        print(f"元素未找到:{e}")
        # 截图保存现场
        browser.save_screenshot("error_screenshot.png")
        return None
        
    except Exception as e:
        print(f"未知错误:{e}")
        return None
        
    else:
        # 登录成功,获取数据
        print("登录成功,开始获取数据")
        data = browser.find_element(By.ID, "data-table").text
        return data
        
    finally:
        # 无论如何都要关闭浏览器
        if browser:
            browser.quit()
            print("浏览器已关闭")

这个例子涵盖了:

  • try:放可能出错的代码(打开浏览器、定位元素)
  • except:捕获特定异常(超时、元素未找到)并做针对性处理
  • else:登录成功后执行数据获取逻辑
  • finally:确保浏览器被关闭,释放资源

说白了,异常处理就是给代码留后路。RPA机器人跑在无人值守的环境里,一个异常没处理好,可能整个流程就卡死了。所以,写异常处理时,多想想「如果这里出错了,我希望程序怎么反应?」

我的建议:刚开始写RPA流程时,可以先写一个「兜底」的异常处理,确保程序不会崩溃。等流程稳定后,再逐步细化异常类型,做更精准的处理。别一开始就想完美,先跑起来再说。