用 Codex 让浏览器替你干活:真·自动填表/下载/留档
你是不是也干过这种事:
- 报销系统一堆字段,复制粘贴到手酸
- 税务/申报网站来回跳转,生怕漏一个选项
- 每周固定去后台下载报表,再转发给同事
很多人会让模型“告诉你怎么操作”。但更爽的方式是:让浏览器自己点、自己填、自己下载、自己截图存档。
Codex 适合当这个“自动化工程师”。你负责说清楚规则和步骤,它把脚本写出来,你一运行就完事。
下面这套方案偏实战:用 Playwright 控制浏览器,用 Codex 帮你生成/修改脚本。跑通一次,后面换网站、换表单,都是同一套路。
你将得到什么(讲人话版)
跑完这篇,你会有:
- 一个可复用的浏览器自动化项目骨架
- 一份能“登录 → 打开页面 → 填表 → 提交 → 下载文件 → 截图留档”的脚本
- 一套和 Codex 沟通的提示词模板(让它少写废代码)
- 一份避坑清单(验证码、风控、隐私、稳定性)
场景举个更具体的:
每个月报销日,你把 Excel 里的“日期/金额/事由/发票号”丢给脚本,脚本自动录入,提交前截图给你确认。你只需要点一下“确认提交”。这才叫省心。😎
方案选择:别纠结,先用这套最稳的
这里用 Playwright + Node.js。
原因很简单:
- Playwright 对表单、弹窗、多标签页、下载处理很成熟
- 跨平台好用(Windows/macOS/Linux 都行)
- 你用 Codex 写 JS/TS 脚本,迭代速度快
如果你更熟 Python,也可以走 playwright-python,套路一致。
环境准备(10 分钟搞定)
你需要安装:
- Node.js(建议 18+)
- 一个终端(macOS 用 Terminal,Windows 用 PowerShell)
新建项目:
mkdir codex-browser-worker
cd codex-browser-worker
npm init -y
npm i playwright dotenv
npx playwright install
加一个 .env 用来放账号信息(别硬编码到脚本里,容易社死):
touch .env
.env 示例:
APP_USERNAME=你的账号
APP_PASSWORD=你的密码
先跑一个“能动”的脚本(确认链路没问题)
创建 run.js:
require('dotenv').config();
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.screenshot({ path: 'proof.png', fullPage: true });
await browser.close();
})();
运行:
node run.js
你会看到浏览器打开网页并截图。OK,底座稳了。
让 Codex 真正“接管浏览器干活”的方式
Codex 不需要直接“附体浏览器”。你给它明确的网页操作流程,它输出 Playwright 脚本。
你要做的就两件事:
- 把网页任务拆成机器能执行的步骤
- 把关键元素(输入框、按钮)定位方式讲清楚
最常见的失败原因:
- 你只说“帮我登录”,但没给登录页结构
- 你只说“填这个表”,但字段和规则没讲
解决方案:让 Codex 生成脚本前,你先把信息喂完整。
Codex 提示词模板(复制就能用)
把下面这段直接丢给 Codex,再补上你自己的页面信息:
提示词:Playwright 自动化脚本生成模板
你是一个写 Playwright 自动化的工程师。请用 Node.js 写一个可运行脚本。
目标:自动完成网页登录、进入某页面、填写表单、提交前截图、点击提交、下载回执并保存文件名。
要求:
- 使用 dotenv 读取账号密码(APP_USERNAME/APP_PASSWORD)
- headless=false 方便我观察
- 每一步都加清晰的注释
- 对关键步骤加等待:优先用 locator.waitFor / expect,而不是 sleep
- 所有 selector 优先用 role/text/label,其次再用 css
- 出错要打印当前 URL,并截图保存到 errors/ 目录
页面信息如下:
1) 登录页 URL:...
2) 用户名输入框:label 文本是“账号”
3) 密码输入框:label 文本是“密码”
4) 登录按钮:按钮文本是“登录”
5) 登录后需要点击菜单“报销管理”进入列表
6) 新建按钮文本“新增报销”
7) 表单字段:日期、金额、事由(限制 30 字)、发票号
8) 提交前点击“预览”并截图
9) 提交按钮文本“确认提交”
10) 提交后页面会出现“下载回执”按钮,下载文件保存到 downloads/
Codex 生成后,你拿来直接跑,哪里不对就把报错和页面截图再丢回去让它改。
一个可直接改造的示例:登录 + 填表 + 下载
把这份存成 expense.js,把 URL 和字段名改成你自己的。
require('dotenv').config();
const fs = require('fs');
const path = require('path');
const { chromium, expect } = require('playwright');
const mk = (p) => { if (!fs.existsSync(p)) fs.mkdirSync(p, { recursive: true }); };
(async () => {
mk('errors');
mk('downloads');
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext({
acceptDownloads: true
});
const page = await context.newPage();
try {
// 1) 打开登录页
await page.goto('https://YOUR_SITE/login', { waitUntil: 'domcontentloaded' });
// 2) 登录
await page.getByLabel('账号').fill(process.env.APP_USERNAME || '');
await page.getByLabel('密码').fill(process.env.APP_PASSWORD || '');
await page.getByRole('button', { name: '登录' }).click();
// 3) 等待登录完成:用页面上的某个稳定元素做锚点
await page.getByRole('navigation').waitFor();
// 4) 进入报销管理
await page.getByRole('link', { name: '报销管理' }).click();
await page.waitForURL(/expense/i);
// 5) 新增报销
await page.getByRole('button', { name: '新增报销' }).click();
// 6) 填表(示例值,你可以改成从 JSON/Excel 读取)
await page.getByLabel('日期').fill('2026-05-13');
await page.getByLabel('金额').fill('128.50');
await page.getByLabel('事由').fill('客户会议打车');
await page.getByLabel('发票号').fill('INV-20260513-001');
// 7) 预览 + 截图留档
await page.getByRole('button', { name: '预览' }).click();
await page.waitForTimeout(300); // 只给弹窗动画一点时间,别依赖它做等待
await page.screenshot({ path: 'preview.png', fullPage: true });
// 8) 提交
await page.getByRole('button', { name: '确认提交' }).click();
// 9) 下载回执
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: '下载回执' }).click();
const download = await downloadPromise;
const savePath = path.join('downloads', await download.suggestedFilename());
await download.saveAs(savePath);
console.log('完成,回执已保存:', savePath);
} catch (err) {
const ts = Date.now();
console.error('出错:', err);
console.error('当前 URL:', page.url());
await page.screenshot({ path: path.join('errors', `error-${ts}.png`), fullPage: true });
throw err;
} finally {
await context.close();
await browser.close();
}
})();
运行:
node expense.js
脚本跑不通很正常,别慌。把报错信息 + errors/ 里的截图发给 Codex,让它按页面真实结构改 selector。
进阶:让脚本一次填多条(从 JSON 读数据)
你可以准备一个 data.json:
[
{"date":"2026-05-01","amount":"56.00","reason":"停车费","invoice":"A001"},
{"date":"2026-05-03","amount":"128.50","reason":"打车","invoice":"A002"}
]
然后让 Codex 帮你把 expense.js 改成循环填报。
提示词就一句:
把脚本改成读取 data.json,循环提交;每条提交前截图保存为 preview-序号.png;失败要记录失败条目的数据。
你会发现:这类“明确改动”的需求,Codex 写得很快。
避坑清单(不看真的会踩)
1) 验证码/短信校验
- 纯自动化很难绕过。
- 实用做法:脚本卡在验证码前,把页面停住,你手动过一下,脚本继续跑。
2) 风控把你当机器人
- 别把点击速度调成“机关枪”。
- 少用固定
sleep,多用等待页面元素出现。 - 公司系统更敏感的话,尽量在工作时间、正常节奏跑。
3) selector 一直变
- 优先用
getByRole/getByLabel/getByText。 - 页面一改版,CSS 类名可能全换,但按钮文本/label 往往更稳。
4) 账号密码安全
.env不要提交到 git。- 截图里可能带敏感信息,别随手发群里。
5) 提交动作别“无脑自动”
强烈建议:
- 提交前截图
- 或弹出确认(哪怕你手动点一下)
报税/付款这类事情,自动化可以省时间,但别把自己坑了。
你该怎么把“Claude 干的活”迁到 Codex
你之前让 Claude 做填表/报税这类事,本质是:它擅长读规则、整理步骤。
Codex 更适合接手“把步骤变成脚本”。
迁移路线很顺:
- 让 Claude 把业务规则写成清单(字段、约束、异常情况)
- 把清单 + 页面结构交给 Codex,让它产出 Playwright 脚本
- 以后同类网站,复用同一套脚本骨架,改 selector 和流程就行
你想要的是“一套工具解决大部分重复劳动”。这条路走通了,你会明显感觉:很多活不需要你亲自坐在屏幕前耗着。
你可以直接抄的“提需求方式”(越具体越省时间)
给 Codex 的输入,建议包含这些:
- 目标(要下载什么、要提交什么、要输出什么文件)
- 页面路径(从登录到目标页怎么点)
- 字段规则(必填/长度/格式/默认值)
- 成功信号(出现某个提示、跳转到某个 URL、出现某个按钮)
- 失败处理(截图、重试、记录失败数据)
写到这份上,Codex 基本不会“瞎猜”。
如果你愿意,把你要自动化的网站流程(不含敏感信息)按步骤贴出来,我也能帮你把提示词整理成一份更好用的版本。