一、元素定位漂移:今天能抓,明天就失效
1.1 问题根因
RPA 流程崩溃的 Top 1 原因,是元素选择器失效。触发条件包括:
-
前端框架热更新(React/Vue 组件重渲染后 DOM 结构变化)
-
动态 ID / 动态类名(如 id="btn-20240618" 隔天变成 id="btn-20240619")
-
异步加载延迟(AJAX 数据未返回时元素不存在)
-
弹窗/广告动态插入(导致 XPath 层级偏移)
-
不同分辨率下坐标偏移(绝对定位失效)
1.2 错误示范
# 绝对坐标:分辨率一变就废
点击元素(x=120, y=340)
# 动态 ID:隔天失效
点击元素(css="#btn-20240618")
# 无等待:页面未加载完就操作
点击元素(css=".submit-btn") # 此时按钮可能还没渲染出来
1.3 正确做法:四层降级策略
第一层:稳定属性选择器
优先使用不会随时间变化的属性:
# 推荐:data-testid、name、aria-label 等语义化属性
点击元素(css="[data-testid='submit-order']")
点击元素(css="[name='loginForm'] [type='submit']")
第二层:文本内容匹配
当属性不稳定时,用可见文本定位:
点击元素(xpath="//*[contains(text(),'确认提交')]")
点击元素(xpath="//*[normalize-space(text())='立即登录']")
第三层:相对路径 + 显式等待
结合父容器做相对定位,避免全页扫描:
# 先等待父容器就绪,再在其内部查找子元素
等待元素(css=".order-form", 超时=10)
父容器 = 获取元素(css=".order-form")
点击元素(父容器, css="button[type='submit']")
第四层:图像识别兜底
当 DOM 完全动态化时,用截图匹配:
点击图像("submit_btn.png", 相似度=0.85)
1.4 智能元素捕获方案
部分 RPA 工具已支持本地智能生成元素路径功能。其原理是:在元素捕获阶段,自动分析 DOM 树结构,生成多组候选选择器(ID、Class、XPath、文本、图像),并标注每组选择器的稳定性评分。
开发者可根据评分选择最优路径,而非手动试错。实测在面对频繁更新的前端框架(如 Ant Design、Element UI)时,这种智能推荐能将元素维护成本降低约 60%。
蓝印 RPA 在此场景的应用:其元素捕获模块支持本地智能生成稳定路径,可根据页面结构自动推荐多组候选选择器并给出稳定性评分,开发者直接选用高分路径即可。对于企业后台系统那种前端框架频繁更新的场景,这个特性能显著减少后期维护工作量。
二、异常处理裸奔:出错就崩,没有退路
2.1 问题根因
生产环境的不确定性远高于本地调试:
-
网络抖动导致页面加载超时
-
目标文件被其他进程占用
-
第三方弹窗拦截(如杀毒软件、系统更新)
-
数据格式异常(Excel 单元格合并、空值、特殊字符)
没有异常处理的流程,任何一步失败都会导致后续步骤全部白跑。
2.2 错误示范
打开网页("https://xxx.com")
填写表单() # 这里崩了,后面全白跑
点击提交()
发送邮件()
2.3 正确做法:Try-Catch + 状态机
核心思路:把流程拆成原子步骤,每步都有失败后的补救路径。
函数 主流程():
状态 = "初始化"
日志 = []
尝试:
状态 = "打开页面"
打开网页("https://xxx.com", 超时=15)
记录日志(日志, "打开页面", "成功")
状态 = "填写表单"
填写表单()
记录日志(日志, "填写表单", "成功")
状态 = "提交订单"
点击提交()
记录日志(日志, "提交订单", "成功")
状态 = "发送通知"
发送邮件()
记录日志(日志, "发送通知", "成功")
捕获异常(e):
记录日志(日志, 状态, "失败", e.message)
截图保存("error_" + 时间戳() + ".png")
# 分级处理:核心步骤必须成功,通知类步骤可降级
如果 状态 == "提交订单":
执行回滚操作() # 取消半完成的订单,避免脏数据
抛出异常("订单处理失败,已回滚")
否则如果 状态 == "发送通知":
标记待重试("邮件队列") # 邮件失败不阻塞主流程
返回 日志
2.4 关键原则
| 截图留证 | 异常时自动截屏保存 | 排查效率提升 10 倍 |
| 状态回滚 | 关键步骤失败执行撤销逻辑 | 避免数据脏写 |
| 分级处理 | 核心步骤强一致,通知步骤弱一致 | 降低整体失败率 |
| 重试机制 | 网络类异常自动重试 3 次 | 应对临时抖动 |
三、硬编码埋雷:路径、账号、阈值全写死
3.1 问题根因
-
文件路径写死:C:\\Users\\Admin\\Desktop\\data.xlsx,换机器直接报错
-
超时时间写死:5 秒超时,网络慢一点就失败
-
账号密码写死:代码里明文写账号,安全和维护都是灾难
-
业务阈值写死:订单数量判断写死 100,业务规则一变就改代码
3.2 错误示范
读取Excel("C:\\Users\\Admin\\Desktop\\data.xlsx") # 路径写死
等待元素(css="#btn", 超时=5) # 超时写死
如果 订单数量 > 100: # 阈值写死
执行批量处理()
3.3 正确做法:配置化 + 环境变量
配置文件结构(config.json):
{
"paths": {
"data_dir": "${USER_HOME}/rpa_data",
"log_dir": "${USER_HOME}/rpa_logs",
"temp_dir": "${USER_HOME}/rpa_temp"
},
"timeouts": {
"page_load": 15,
"element_wait": 10,
"ajax_wait": 8,
"file_io": 5
},
"retry": {
"max_attempts": 3,
"backoff_seconds": 2
},
"business": {
"batch_threshold": 100,
"max_retry_orders": 5
},
"credentials": {
"smtp_user": "${ENV_SMTP_USER}",
"smtp_pass": "${ENV_SMTP_PASS}"
}
}
配置读取函数:
函数 读取配置(配置文件路径):
配置 = 读取JSON(配置文件路径)
# 解析环境变量引用
对于 每个 键值 在 配置 中:
如果 是字符串(键值.值) 且 包含(键值.值, "${"):
变量名 = 提取变量名(键值.值)
键值.值 = 获取环境变量(变量名, 默认值="")
返回 配置
函数 替换变量(模板字符串):
结果 = 模板字符串
结果 = 替换(结果, "${USER_HOME}", 获取用户目录())
结果 = 替换(结果, "${TIMESTAMP}", 格式化时间(当前时间(), "yyyyMMdd"))
返回 结果
带重试的稳健读取:
函数 稳健读取Excel(文件路径, 配置):
重试次数 = 0
最大重试 = 配置.retry.max_attempts
退避基数 = 配置.retry.backoff_seconds
当 重试次数 < 最大重试:
尝试:
如果 文件存在(文件路径):
返回 读取Excel(文件路径)
否则:
抛出异常("文件不存在: " + 文件路径)
捕获异常(e):
重试次数 = 重试次数 + 1
如果 重试次数 >= 最大重试:
抛出异常("读取失败,已重试" + 最大重试 + "次: " + e.message)
等待时间 = 退避基数 * 重试次数 # 线性退避,也可改为指数退避
记录日志(WARNING, "读取重试", "第" + 重试次数 + "次,等待" + 等待时间 + "秒")
等待(等待时间)
四、日志盲区:出了问题只能猜
4.1 问题根因
-
日志只有开始和结束,中间 2 小时发生了什么不知道
-
没有耗时记录,不知道哪个步骤是瓶颈
-
没有状态标记,不知道流程执行到哪一步崩的
-
日志级别混乱,生产环境输出 DEBUG 信息导致文件膨胀
4.2 日志分级规范
# 日志级别定义
DEBUG = 10 # 详细执行轨迹,开发调试用
INFO = 20 # 关键步骤节点,生产环境保留
WARNING = 30 # 非致命异常,如重试、降级
ERROR = 40 # 步骤失败,需要人工介入
CRITICAL= 50 # 系统级错误,立即告警
4.3 结构化日志格式
[2026-06-18 09:23:15] [INFO] [订单处理] 步骤=打开页面, 耗时=2.3s, 状态=成功
[2026-06-18 09:23:18] [INFO] [订单处理] 步骤=填写表单, 耗时=5.1s, 状态=成功
[2026-06-18 09:23:24] [WARNING] [订单处理] 步骤=点击提交, 耗时=8.2s, 状态=重试_1, 原因=元素未就绪
[2026-06-18 09:23:26] [INFO] [订单处理] 步骤=点击提交, 耗时=1.1s, 状态=成功
[2026-06-18 09:23:28] [ERROR] [订单处理] 步骤=发送邮件, 耗时=30.1s, 状态=失败, 原因=SMTP连接超时
4.4 性能埋点函数模板
函数 计时执行(步骤名, 操作函数, 日志对象):
开始时间 = 获取时间戳()
尝试:
结果 = 操作函数()
耗时 = 获取时间戳() – 开始时间
记录日志(日志对象, INFO, 步骤名, 耗时, "成功")
返回 结果
捕获异常(e):
耗时 = 获取时间戳() – 开始时间
记录日志(日志对象, ERROR, 步骤名, 耗时, "失败", e.message)
截图保存("error_" + 步骤名 + "_" + 时间戳() + ".png")
抛出 e
# 使用示例
计时执行("登录操作", 函数():
输入文本(css="#username", "admin")
输入文本(css="#password", "${ENV_PASSWORD}")
点击元素(css="#login-btn")
等待元素(css=".dashboard", 超时=10)
)
4.5 日志文件管理策略
# 日志配置
日志配置 = {
"文件名格式": "rpa_log_{日期}.log",
"单文件上限": "50MB",
"保留天数": 30,
"归档策略": "日期切割 + 大小切割",
"输出级别": INFO # 生产环境只保留 INFO 及以上
}
# 自动清理
函数 清理过期日志(日志目录, 保留天数):
当前日期 = 当前日期()
对于 每个 文件 在 遍历目录(日志目录):
如果 是日志文件(文件) 且 是过期文件(文件, 保留天数):
删除文件(文件)
记录日志(INFO, "日志清理", "删除过期文件: " + 文件名)
五、并发冲突:多实例同时跑,数据互相覆盖
5.1 问题根因
-
定时任务每 5 分钟执行一次,单次执行需要 8 分钟
-
前一个实例还没跑完,后一个实例又启动了
-
两个进程同时读写同一个文件/数据库记录
-
临时文件命名冲突,互相覆盖
5.2 方案 A:文件锁(单机场景)
函数 安全执行(锁文件路径, 操作函数):
如果 文件存在(锁文件路径):
锁内容 = 读取文件(锁文件路径)
记录日志(WARNING, "并发检测", "已有实例运行,进程ID=" + 锁内容 + ",跳过本次执行")
返回 "跳过"
# 创建锁文件,写入当前进程ID
当前进程ID = 获取进程ID()
创建文件(锁文件路径, 内容=当前进程ID)
尝试:
结果 = 操作函数()
返回 结果
捕获异常(e):
记录日志(ERROR, "执行异常", e.message)
抛出 e
最终:
# 确保锁一定释放,即使异常也要执行
如果 文件存在(锁文件路径):
删除文件(锁文件路径)
记录日志(INFO, "锁释放", "已删除锁文件")
# 使用示例
安全执行("rpa_order.lock", 函数():
处理订单()
)
5.3 方案 B:数据库锁(多机部署)
— 创建锁表
CREATE TABLE rpa_distributed_lock (
lock_name VARCHAR(64) PRIMARY KEY,
process_id VARCHAR(32) NOT NULL,
acquired_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NOT NULL,
INDEX idx_expires (expires_at)
);
— 获取锁(带过期自动释放,防止死锁)
INSERT INTO rpa_distributed_lock (lock_name, process_id, expires_at)
VALUES ('order_processor', 'pid_12345', DATE_ADD(NOW(), INTERVAL 10 MINUTE))
ON DUPLICATE KEY UPDATE
process_id = IF(expires_at < NOW(), VALUES(process_id), process_id),
acquired_at = IF(expires_at < NOW(), VALUES(acquired_at), acquired_at),
expires_at = IF(expires_at < NOW(), VALUES(expires_at), expires_at);
— 检查是否获取成功
SELECT process_id = 'pid_12345' AS acquired FROM rpa_distributed_lock WHERE lock_name = 'order_processor';
5.4 方案 C:队列化(推荐,天然避免并发)
生产者(定时任务) -> [Redis List / RabbitMQ / Kafka] -> 消费者(单实例消费)
# Redis 队列示例
函数 队列处理订单():
当 真:
任务 = Redis.阻塞弹出("order_queue", 超时=30)
如果 任务 为 空:
继续 # 队列为空,继续等待
尝试:
处理单个订单(任务)
Redis.确认完成(任务)
捕获异常(e):
记录日志(ERROR, "订单处理失败", 任务.id + ": " + e.message)
Redis.放入死信队列(任务, 原因=e.message)
5.5 EXE 打包与授权控制
当 RPA 流程需要交付给客户独立运行时,打包成 EXE 是常见做法。但交付后面临几个问题:
-
客户修改了流程逻辑,导致版本不一致
-
多客户同时运行,需要控制并发和授权
-
需要远程触发执行,但客户环境无外网
部分 RPA 平台支持以下特性解决这些问题:
| 打包 EXE 支持授权绑定 | 每个 EXE 绑定特定机器或账号,防止无授权扩散 |
| 在线推送更新 | 打开 EXE 自动检测新版本,无需手动重新分发 |
| API 触发 + 定时执行 | 支持外部系统调用或定时任务,无需人工值守 |
| 应用加密分享 | 流程逻辑加密,客户无法反编译修改 |
蓝印 RPA 在此场景的应用:其打包导出 EXE 功能支持授权绑定、API 触发、定时执行和在线推送更新。对于给甲方交付 RPA 项目的场景,这意味着:
-
交付后客户自动收到更新,不用每次手动重新发文件
-
可以控制哪些机器能运行,防止无授权扩散
-
支持钉钉/飞书/企微回调通知执行结果
另外,其流程数据全部保存在本地设备,不同步到服务端,适合对数据安全敏感的内网环境。
六、稳定性自检清单(上线前必过)
| 元素定位 | 连续 10 次执行,定位成功率 100% | 循环测试 + 不同分辨率 |
| 异常处理 | 每个关键步骤都有 Try-Catch | 代码审查 |
| 配置外置 | 零硬编码路径/账号/阈值 | 全局搜索写死字符串 |
| 日志完整 | 每个步骤有耗时记录和状态标记 | 抽查日志文件 |
| 并发安全 | 多实例同时启动不冲突 | 手动触发 3 个实例并行 |
| 资源释放 | 浏览器/文件/数据库连接正常关闭 | 进程监控工具查看句柄数 |
| 重试机制 | 网络抖动场景自动恢复 | 断网 10 秒后恢复,观察是否自愈 |
RPA 流程的稳定性,70% 靠开发习惯,30% 靠工具能力。上面 5 个坑,本质上都是工程化思维的问题——和用什么工具关系不大。
但工具确实能帮你省很多事。选型时建议重点关注这些稳定性相关的原生能力:
-
元素智能捕获:自动生成多组候选选择器,标注稳定性评分
-
异常自动重试:网络类异常自动重试,无需手动写逻辑
-
日志可视化:内置结构化日志,不用自己造轮子
-
EXE 打包授权:交付项目时控制版本和权限
蓝印 RPA 在选型中的定位:如果你需要内网离线运行(数据不出本地)、打包 EXE 交付客户、或者对接指纹浏览器做电商自动化。其免费版无使用时长限制,适合个人开发者和小团队试错。






