首先要跟看到这个标题的朋友解释一下:我平时写自动化脚本,经常要给临时项目起个随手能打的代号,rea就是从里面蹦出来的三个字母。它的全称被我私下写成 Repeat Everything Automatically——听起来有点中二,实际上做的事情特别接地气:把那些每天都要在浏览器里重复点的按钮、重复填的表单、重复下载的报表,交给一段脚本去跑。
这个项目解决的痛点很明确:你以为点几下鼠标不算累,但当这种“点几下”每天要出现十几次甚至几十次,而且必须保持顺序不错、速度不慢的时候,人就会开始出错、烦躁、摸鱼。而脚本不会。它适合谁?适合所有工作里被网页后台折磨的人,比如运营、财务、人事、客服、数据分析岗,也适合想用浏览器自动化做点效率工具的开发者。读完这篇文章,你不需要完全掌握编程,只要照着抄,就能跑通第一个属于你自己的自动操作流程。
1. 为什么我会把一个自动化工具叫 rea
1.1 一个反复出现的真实痛点
我第一次萌生写 rea 的念头,是因为某一个普通周四下午。
当时我在处理一个每周都要重复的任务:登录某个后台 → 进列表页 → 设置筛选条件 → 点查询 → 勾选所有结果 → 点批量导出 → 等文件生成 → 下载 → 重命名。整套操作大概需要八到十分钟,如果中途网络卡一下,或者某一步点早了,就得重来。连续做了两个月之后,我发现自己看到那个列表页都想绕路走。
更让人烦躁的是,这种流程一旦涉及“必须按顺序执行”,人工操作就天然不可靠。你可能在等待下载时切到别的窗口,回来忘了刚才点没点;也可能因为多选了一个不该选的对象,导致整份报表需要重新生成。这类问题不是靠“细心”能解决的,因为人的注意力本身就是稀缺资源。
所以我把自己的需求拆了一下:第一步,把固定操作录下来;第二步,把重复流程参数化,比如日期、关键词、筛选条件;第三步,让脚本按步骤执行,并把每一步的结果记录下来。这就是 rea 最初的雏形。
1.2 为什么不自接用现成的自动化平台
有人可能会问,市面上不是有很多 RPA(机器人流程自动化)工具吗?下载一个、拖拽流程、运行,不也能做到类似效果?确实可以,但我在实际对比之后还是决定自建。
原因有三点。一是成本,很多 RPA 工具按控制端数量或运行次数收费,个人或小团队用起来不划算,而自己用开源浏览器自动化库写脚本,跑多少遍都不额外收费。二是灵活度,RPA 工具的可视化编排对于简单流程很好用,但一旦要写条件分支、循环嵌套、错误重试,反而要绕很多弯,不如代码直接。三是调试体验,脚本出问题的时候,代码方式可以打日志、截图、查看调用栈,而可视化平台往往只给你一个“执行失败”的提示,排查起来非常抓狂。
当然,我并不是说自建一定适合所有人。完全没有代码基础的话,直接学 HTML 结构、选择器、等待条件,门槛确实比拖拽高。但如果你恰好懂一点编程,或者愿意花两天时间补个基础,那 t自建脚本得到的控制力是 RPA 工具很难给的。
另外还有一个很实际的理由:很多后台系统的界面长年不变,但又有大量重复操作。这种场景用代码写一次,几年都能一直复用,边际成本几乎为零。
1.3 rea 的三大模块:录制、回放、校验
我最早想做成“录制”工具,后来发现纯录制并不可靠,因为真实页面存在各种变化。最后把 rea 拆成了三个功能模块:
- 回放核心:负责按顺序执行每一条操作指令,打开页面、输入内容、点击按钮、等待结果。
- 配置驱动:操作过程不硬编码在脚本里,而是抽成一个步骤清单,用 JSON 或简单数组描述,方便修改和扩展。
- 结果校验:每一步执行后都要确认页面真的发生了变化,比如某个元素出现、某段文本更新、下载任务完成,而不是“点击成功”就算完事。
这三个模块听起来简单,却是处理复杂网页的关键。录制只解决“第一步怎么开始”,真正让脚本能长期稳定运行,靠的是校验。没有校验的自动化脚本,就像闭着眼睛走路,运气好能到终点,运气不好直接撞墙。
2. 核心技术点拆解:让脚本像真人一样“找得到、等得起、信得过”
2.1 元素定位:三重武器和一条优先级铁律
一个网页自动化脚本运行失败,十有八九是元素定位出了问题。页面上那个按钮明明在那里,但脚本就是找不到,或者找到了多个。
我常用的定位方式有三种。第一种是 CSS 选择器,比如input[name="keyword"]、.btn-primary,胜在简洁,适合有明显特征的元素。第二种是可见文本,比如button:has-text("查询"),适合按钮上的文字会变化、但核心功能不变的场景。第三种是 XPath,比如//div[@class="result" and contains(text(),"明细")],适合页面结构复杂、没有额外标记的情况。
这三者不是平等关系,我总结出一条优先级铁律:能加>[ { "id": "open_page", "type": "goto", "url": "https://example.com/admin/list", "waitUntil": "domcontentloaded" }, { "id": "fill_keyword", "type": "fill", "selector": "input[name='keyword']", "value": "{{keyword}}" }, { "id": "click_search", "type": "click", "selector": "button:has-text('查询')" }, { "id": "wait_result", "type": "waitFor", "selector": ".result-item", "timeout": 10000 } ]
这个设计最大的好处是,用户不需要了解代码细节,也能通过改配置来调整流程。{{keyword}}是一种占位符,运行时从外部参数或环境变量中取值。这样一来,同样的流程,今天跑关键词 A,明天跑关键词 B,脚本主体一行不用改。
做一个好的配置驱动结构,关注点不是怎么写配置文件,而是怎么把“易变的部分”和“稳定的部分”分开。易变的部分包括:页面地址、搜索关键词、导出文件格式、日期范围。稳定的部分包括:固定表单结构、操作顺序、校验条件。把它们分开,脚本才能长期省心。
3. 从零跑通第一个 rea 脚本
3.1 准备环境和安装依赖
我一般用 Node.js 生态来写这类脚本,因为浏览器自动化库对它支持最好,安装起来也快。如果你对 Python 更熟,也可以用对应版本,思路完全一样。
安装步骤同样简单,开一个终端,依次执行:
npm init -y npm install playwright npx playwright install chromium第一条命令初始化项目,第二条安装浏览器自动化库,第三条下载一个可独立运行的浏览器内核。很多人会漏掉第三条,导致运行时报“找不到浏览器”,所以这里先打个预防针。
顺便说一句,安装过程可能需要一点磁盘空间,浏览器内核大概几百 MB。如果你在公司内网,网络受限,可以找运维开下载白名单。这个环节最不起眼,却是新手第一个容易卡住的地方。
3.2 最小脚本:登录、查询、导出
跑通一个最小的 rea 脚本,我拿“登录后查询并导出报表”来演示。别看流程简单,它已经覆盖了日常自动化的高频操作:填表、点击、等待、下载。
先看代码:
const { chromium } = require('playwright'); (async () => { const browser = await chromium.launch({ headless: false }); const page = await browser.newPage(); await page.goto('https://example.com/login', { waitUntil: 'domcontentloaded' }); await page.fill('#username', process.env.REA_USER || 'demo'); await page.fill('#password', process.env.REA_PASS || 'changeme'); await page.click('button[type="submit"]'); await page.waitForURL('**/dashboard'); await page.fill('input[name="keyword"]', '2025-01'); await page.click('button:has-text("查询")'); await page.waitForSelector('.result-item'); const downloadPromise = page.waitForEvent('download'); await page.click('a.btn-export'); const download = await downloadPromise; await download.saveAs('./reports/report-' + new Date().toISOString().slice(0, 10) + '.xlsx'); await browser.close(); })();这段代码的逻辑很直白:打开登录页、填账号密码、点登录、等跳转、填查询条件、点查询、等结果出现、触发下载、保存文件。里面几乎每一行都有对应目的,没有冗余。
你可能注意到我用了process.env.REA_USER,这是从环境变量读取账号信息,避免把敏感内容写死在代码里。如果你只是本地测试,也可以直接写变量值,但上传代码库时要小心。
3.3 常用加固:登录态复用、失败重试、截图日志
基础脚本能跑通之后,你需要考虑的不是“怎么跑一次”,而是“怎么跑一千次”。我强烈建议优先做三件事。
第一,登录态复用。很多后台系统登录一次后,会话能维持一段时间。每次脚本启动都重新走一遍登录流程,既慢又容易触发安全策略。更好的做法是,第一次登录成功后,把会话状态保存下来,后续运行直接加载:
// 保存会话 await context.storageState({ path: 'auth.json' }); // 下次直接使用 const context = await browser.newContext({ storageState: 'auth.json' }); const page = await context.newPage();这里有一点要注意:不同浏览器的 storageState 格式基本是标准 JSON,可以跨脚本复用。如果遇到登录状态过期,顶多重新登录一次,再刷新保存文件便好。
第二,失败重试。网络抖动、后台偶发报错,都会导致脚本中断。我通常给每个关键步骤包一个重试包装器,失败后等两秒,最多重试三次。重试时最好换一种等待策略,而不是无脑重复同样操作。
第三,截图和日志。这是很多新手最容易忽略的。在每一步关键动作之后截图存盘,甚至直接用 trace 录制功能,事后排查的效率高得多。脚本运行完自动生成一个带时间戳的目录,里面放着截图、日志、下载记录,比任何“到底哪里错了”的脑内复盘都管用。
3.4 处理 iframe 和动态弹窗这两个糟心问题
实际项目中,我碰到最多的两个“非标准场景”是 iframe 和动态弹窗。
iframe 可以理解成页面里嵌了一个独立的子页面。直接在外层页面定位 iframe 里的元素,十有八九找不到,因为脚本默认只在主文档里搜索。处理方式是先切换到对应框架,再在里面操作:
const frame = page.frame({ url: /\/admin\/approve\// }); await frame.locator('input[name="approveComment"]').fill('已通过'); await frame.locator('button:has-text("确认")').click();动态弹窗则是另一个常见坑。有些弹窗不是一开始就在页面里的,而是等几秒后才出现,直接点击大概率会落空。我的建议是,弹窗类的点击前先等待它出现,并且优先使用“可见状态”而非“存在状态”,因为有时候元素在 DOM 里存在,但被 CSS 隐藏着,点了和没点一样。
这些细节看上去琐碎,却是决定脚本“是玩具还是工具”的分水岭。能处理这些真实页面问题,等于真正掌握了 rea 的精髓。
4. 常见问题与排查技巧
4.1 运行时报错的排查速查表
我把这段时间踩过的坑汇总成一张速查表,基本覆盖了新手最常见的上报错类型。每次脚本异常时,先别急着改代码,对应下面的情况逐条排查。
| 错误表现 | 最常见原因 | 处理方向 |
|---|---|---|
| 找不到元素 | 页面还在加载 / 选择器变了 / 层级不对 | 加显式等待;换>
|