with one click
web-form-fill
网络表单填报技能 — 从信息搜集到浏览器填报的完整工作流,确保框架受控组件正确写入、页面切换前暂存、最终提交由用户确认。
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
网络表单填报技能 — 从信息搜集到浏览器填报的完整工作流,确保框架受控组件正确写入、页面切换前暂存、最终提交由用户确认。
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
软件著作权登记全流程:从代码仓/目录生成程序鉴别材料(源程序文档)、 软件操作手册、申请填报信息 Markdown,并可辅助在线填报。 当用户需要申请软件著作权、生成软著材料时触发。
维护 business-developer 的 SQLite 追踪数据库,记录已探索的创作者(模式一)和已互动的帖子(模式二),避免重复追踪和重复互动。
通过自媒体平台搜索内容,在评论区以留言/回复/私信等方式拓展潜在客户或进行品牌宣传。用于 HEARTBEAT 定时任务。
维护 business-developer 的 SQLite 情报采集数据库,记录已采集的信息内容,避免重复采集,支持按日查询已采集情报。
定时监控特定信源(自媒体账号/网页),按预设标准提取商业情报,生成简报或报告。用于 cron 定时任务。
通过自媒体平台按搜集策略探索潜在客户——策略 A 分析帖子发布者画像,策略 B 从评论区挖掘潜客。用于 HEARTBEAT 定时任务。
| name | web-form-fill |
| description | 网络表单填报技能 — 从信息搜集到浏览器填报的完整工作流,确保框架受控组件正确写入、页面切换前暂存、最终提交由用户确认。 |
| metadata | {"openclaw":{"emoji":"📝"}} |
面向在线表单填报场景:申报系统、比赛报名、补贴申请、信息登记等。核心原则:先看后填、模拟真人、切页必存、提交由人。
依赖技能:browser-guide(浏览器操作最佳实践)
打开浏览器后,先完整浏览整个表单,不要急着填。
form-fields-{项目简称}.md),保持与表单相同的页面/段落结构markdown 文件结构示例:
# XX大赛申报 — 字段清单
## 概要
- 项目名称 [必填] [text] [限50字]
- 行业分类 [必填] [级联下拉] 一级→二级
- 科技创新领域 [必填] [下拉单选]
- 参赛渠道 [必填] [下拉多选]
- 以投代评 [必填] [radio: 是/否]
- 服务需求 [多选] 最多3项
- 期望融资金额 [必填] [number] 万元
- 股权释放比例 [必填] [number] %
## 参赛项目情况
- 项目名称 [必填] [text] (概要中已填,此处联动)
- 前沿技术热点归属 [必填] [text] 最多5个关键词,逗号分隔
- 项目主要技术、产品及服务 [必填] [富文本] 2000字以内,可插入图片
- 项目产品市场分析及竞争优势 [必填] [富文本] 2000字以内
- 商业模式 [必填] [富文本] 1000字以内
## 企业基本信息表
...
## 附件
- 营业执照 [必填] [PDF/图片]
- ...
回到 markdown 文件,逐项填写准备好的内容:
问用户的典型场景:
信息齐全后,打开浏览器逐页填报。
核心原则:每个字段都要「尝试 → 验证 → 失败则降级重试」。不同网站拦截程度不同,没有一种方法能通吃所有情况。
对每个文本字段,按以下顺序逐级尝试,每级尝试后立即验证,验证通过则停止,失败则清空重来:
| 级别 | 方法 | 原理 | 适用场景 | 失效场景 |
|---|---|---|---|---|
| A | CDP Input.insertText | 走浏览器真实输入管线,一次性插入整段文本 | 大多数原生 input 和简单受控组件:Vue/React/Svelte v-model、原生 input | 有输入 mask 的字段、拦截 insertText 的站点、Slate/wangEditor 等 contenteditable 编辑器可能不吃 |
| S | JS dispatch beforeinput 事件 | 从 JS 层手动构建 InputEvent('beforeinput', {inputType:'insertText'}) 并 dispatch,走 Slate 的原生事件管道 | Slate/wangEditor/Tiptap 等 contenteditable 编辑器 — 这些编辑器监听 beforeinput 而非 input,CDP insertText 不一定能触发 | 非 Slate 的 contenteditable、原生 input |
| B | CDP 逐字 Input.dispatchKeyEvent | 模拟逐键按下,每个字符发 keyDown → char → keyUp | 输入 mask 字段、只监听 keydown/keyup 的组件、insertText 被拦截的站点 | 监听 composition 事件的中文输入场景、非常长的文本(太慢) |
| C | JS Clipboard 粘贴 | document.execCommand('insertText', false, text) | 部分 contenteditable 编辑器、允许粘贴的输入框 | 禁止粘贴的站点、已废弃 execCommand 的浏览器、Slate 不感知 execCommand 的 DOM 变更 |
| D | JS native setter + dispatchEvent | 用 Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set 绕过框架拦截设值,再手动 dispatchEvent('input') + dispatchEvent('change') | 简单 Vue/React v-model 绑定、CDP 完全不可用时 | Slate/wangEditor 等非 input 编辑器(无 .value 属性)、依赖逐字验证的组件 |
⚠️ Slate/wangEditor 的致命坑:
Slate 系编辑器(wangEditor、Tiptap 等)与普通受控组件有本质区别,必须注意:
beforeinput,不监听 input — 普通 Vue/React 组件靠 input 事件同步状态,Slate 靠 beforeinput。所以级别 D 的 dispatchEvent('input') 对 Slate 完全无效execCommand 改的是 DOM,Slate 不知道 — document.execCommand('insertText') 直接修改 DOM,但 Slate 的内部数据模型不变,下次 Slate 重渲染会覆盖掉beforeinput 后 DOM 更新有延迟 — beforeinput 被 Slate 接管后,文本已写入 Slate 数据模型,但 DOM 渲染要等微任务队列跑完。验证时不能立即读 textContent,必须延迟 50ms+ 再验证beforeinput({inputType: 'deleteContentBackward'})如何判断当前字段是否为 Slate 编辑器:
[data-slate-editor]、[data-slate-node].w-e-text-container(wangEditor)每级尝试的完整流程:
对每个字段:
1. scrollIntoView 确保在视口内
2. 点击聚焦(对 input:点击输入框;对 contenteditable:点击编辑区域)
3. 清空已有内容(见下方清空方式)
4. 判断字段类型:
- 如果是 Slate/contenteditable 编辑器 → 从级别 S 开始
- 如果是普通 input/textarea → 从级别 A 开始
5. 按级别依次尝试:
a. 用当前方法写入文本
b. 验证写入是否生效(见下方验证规则)
c. 如果验证通过 → 本字段完成,进入下一个字段
d. 如果验证失败 → 清空内容,降级到下一级别重试
6. 如果所有级别都失败 → 记录该字段为"无法自动填写",最后告知用户手动填写
各级别操作细节:
级别 A — CDP Input.insertText(普通 input/textarea 首选):
1. 聚焦(点击元素)
2. 清空已有内容
3. CDP Input.insertText: "要填入的文本"
4. 验证
级别 S — JS dispatch beforeinput 事件(Slate/contenteditable 编辑器首选):
1. 聚焦(点击编辑器内容区域,确认 activeElement 是编辑器或其子节点)
2. 清空已有内容(用 beforeinput deleteContentBackward,见下方清空方式)
3. 对文本中每个字符 ch:
Runtime.evaluate:
const be = new InputEvent('beforeinput', {
inputType: 'insertText',
data: ch,
bubbles: true,
cancelable: true,
composed: true
});
editor.dispatchEvent(be);
短暂等待(10-20ms)
4. 延迟 50ms 后验证(Slate DOM 更新有延迟)
级别 B — 逐字 dispatchKeyEvent:
1. 聚焦(点击元素)
2. 清空已有内容
3. 对文本中每个字符 ch:
- Input.dispatchKeyEvent: type=keyDown, key=ch, code=KeyX
- Input.dispatchKeyEvent: type=char, text=ch
- Input.dispatchKeyEvent: type=keyUp, key=ch, code=KeyX
- 短暂等待(20-50ms,模拟人类打字节奏)
4. 验证
注意:中文等非 ASCII 字符,用 type=char 事件 + text 参数传递
级别 C — JS Clipboard 粘贴:
1. 聚焦(点击元素)
2. 清空已有内容
3. Runtime.evaluate: document.execCommand('insertText', false, '要填入的文本')
4. 延迟 50ms 后验证
注意:对 Slate 编辑器此方法大概率无效(execCommand 改 DOM 不改 Slate 模型),但仍可一试
级别 D — native setter + dispatchEvent(仅适用于 /,不适用于 contenteditable):
1. 聚焦(点击元素)
2. 清空已有内容
3. Runtime.evaluate:
const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set;
setter.call(el, '要填入的文本');
el.dispatchEvent(new Event('input', {bubbles: true}));
el.dispatchEvent(new Event('change', {bubbles: true}));
4. 验证
清空已有内容:
对 普通 input/textarea:
1. triple-click 全选(Input.dispatchMouseEvent clickCount=3)
2. Backspace 删除
3. 如果 triple-click 无效,改用 Ctrl+A → Delete
对 contenteditable / Slate 编辑器:
1. 聚焦编辑器
2. 全选:Ctrl+A(Input.dispatchKeyEvent keyDown key=a modifiers=2)
3. 删除:dispatch beforeinput 事件
Runtime.evaluate:
editor.dispatchEvent(new InputEvent('beforeinput', {
inputType: 'deleteContentBackward',
bubbles: true,
cancelable: true,
composed: true
}));
4. 如果 beforeinput 删除无效,回退到 Backspace 键事件
5. 延迟 50ms 后验证内容为空
禁止的方式:
innerHTML — Slate/Vue 不会感知,placeholder 不消失,暂存后内容丢失textContent — 同上el.value = "xxx" 不发事件 — 框架完全无感知dispatchEvent('input') — Slate 不监听 input 事件,只监听 beforeinputexecCommand — 改 DOM 不改 Slate 数据模型,下次渲染被覆盖逐级尝试:
| 级别 | 方法 |
|---|---|
| A | 点击 select 打开下拉面板 → 点击目标选项 |
| B | 点击 select → 键盘方向键选择 → Enter 确认 |
| C | native setter 设值 + dispatchEvent('change') |
自定义下拉(非原生 <select>,如 Element Plus el-select、Ant Design Select):
Input.dispatchMouseEvent 点击目标选项逐级尝试:
| 级别 | 方法 |
|---|---|
| A | 点击日期输入框 → 在弹出的日历面板中逐年/月/日点击 |
| B | 点击日期输入框聚焦 → 用级别 A/B 文本输入方法输入日期字符串 |
| C | native setter 设值 + dispatchEvent |
<input type="file"> 或上传按钮DOM.setFileInputFiles 设置文件路径| 控件类型 | 验证方式 | 通过条件 | 注意 |
|---|---|---|---|
| 普通 input | 检查 el.value 和 placeholder 可见性 | el.value 包含预期内容 且 placeholder 不可见 | 可立即验证 |
| contenteditable / Slate 编辑器 | 检查 editor.textContent.trim() 和 placeholder 元素 | textContent 包含预期内容 且 placeholder 元素 display: none | 必须延迟 50ms 再验证 — Slate 处理 beforeinput 后 DOM 更新有延迟 |
| 下拉 select | 检查选中项文本 | 选中项文本与预期一致 | 可立即验证 |
| radio/checkbox | 检查 el.checked | 选中状态符合预期 | 可立即验证 |
contenteditable 验证的具体实现:
// 等待 Slate DOM 更新后验证
await new Promise(r => setTimeout(r, 50));
const text = editor.textContent.trim();
const ph = editor.closest('.w-e-text-container')
?.querySelector('.w-e-text-placeholder');
const phHidden = !ph || getComputedStyle(ph).display === 'none';
const passed = text.length > 0 && phHidden;
验证失败的处理:
切换页面/栏位前,必须执行:
如果当前页没有暂存按钮:
所有页面填写完成后:
原因:直接操作 DOM 绕过了框架响应式系统(Vue/React/Slate),框架内部状态仍为空。
解决:按 3-B 节的逐级尝试策略填写。对普通 input 优先 CDP Input.insertText;对 Slate 编辑器优先 dispatch beforeinput 事件。每级尝试后验证,失败则降级。
原因:同上 — 框架内部状态为空,暂存提交的是空值。
验证方法:暂存后,导航到其他页面再回来,检查字段是否仍有值。
原因:Slate 处理 beforeinput 后,文本已写入数据模型但 DOM 还没渲染完。立即读 textContent 会读到空值。
解决:验证前延迟 50ms+,等 Slate 微任务队列跑完再读 textContent。
对策:
[contenteditable="true"]),而非工具栏document.activeElement 是编辑器或其子节点[data-slate-editor]),是则从级别 S(beforeinput)开始尝试对策:
对策:
browser-guide 的 CAPTCHA 处理流程操作1. Runtime.evaluate: el.scrollIntoView({block:'center'})
2. Runtime.evaluate: el.getBoundingClientRect() → {x, y, width, height}
3. Input.dispatchMouseEvent: mousePressed at (x+width/2, y+height/2)
4. Input.dispatchMouseEvent: mouseReleased
1. 聚焦元素(见上)
2. Input.dispatchMouseEvent: mousePressed clickCount=3 (triple-click 全选)
3. Input.dispatchMouseEvent: mouseReleased clickCount=3
4. Input.dispatchKeyEvent: keyDown Backspace
5. Input.dispatchKeyEvent: keyUp Backspace
6. 验证: el.value 为空
如果 triple-click 无效,改用 Ctrl+A:
2. Input.dispatchKeyEvent: keyDown key=a modifiers=2 (Ctrl)
3. Input.dispatchKeyEvent: keyUp key=a modifiers=2
1. 聚焦编辑器
2. Input.dispatchKeyEvent: keyDown key=a modifiers=2 (Ctrl+A 全选)
3. Input.dispatchKeyEvent: keyUp key=a modifiers=2
4. Runtime.evaluate:
editor.dispatchEvent(new InputEvent('beforeinput', {
inputType: 'deleteContentBackward',
bubbles: true, cancelable: true, composed: true
}));
5. 等待 50ms
6. 验证: editor.textContent.trim() 为空
如果 beforeinput 删除无效,回退到 Backspace 键事件。
1. 聚焦 + 清空
2. Input.insertText: "要填入的内容"
3. 验证: el.value 非空 且 placeholder 不可见
1. 聚焦 + 清空(用 beforeinput 方式清空)
2. 对每个字符 ch:
Runtime.evaluate:
editor.dispatchEvent(new InputEvent('beforeinput', {
inputType: 'insertText', data: ch,
bubbles: true, cancelable: true, composed: true
}));
等待 10-20ms
3. 等待 50ms(Slate DOM 更新延迟)
4. 验证: editor.textContent.trim() 非空 且 placeholder display=none
1. 聚焦 + 清空
2. 对每个字符 ch:
Input.dispatchKeyEvent: type=keyDown, key=ch
Input.dispatchKeyEvent: type=char, text=ch
Input.dispatchKeyEvent: type=keyUp, key=ch
等待 20-50ms
3. 验证
1. 聚焦 + 清空
2. Runtime.evaluate: document.execCommand('insertText', false, '要填入的内容')
3. 等待 50ms(contenteditable DOM 更新延迟)
4. 验证
1. 聚焦 + 清空
2. Runtime.evaluate:
const s = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set;
s.call(el, '要填入的内容');
el.dispatchEvent(new Event('input', {bubbles: true}));
el.dispatchEvent(new Event('change', {bubbles: true}));
3. 验证