原始内容
name: xhs-image-note-release version: 1.2.2 description: > 小红书图文笔记自动发布技能。通过 ego-browser 自动化完成图片上传、标题填写、正文编辑、 话题标签、发布等全流程。核心解决了小红书发布按钮封装在 closed Shadow DOM 中无法点击的问题。 当用户要求发小红书、发布图文笔记、上传到小红书、小红书发帖或涉及小红书内容发布时触发此技能。 前置依赖:ego-browser (ego-lite) 已安装且正在运行,小红书账号已登录。 bins: [ego-browser, node] metadata: openclaw: requires: env: - IMAGE_DIR - IMAGES - TITLE - BODY
- Version: 1.2.2
- License: MIT
- Author: Evan Song · github.com/Songhonglei
- Repository: https://github.com/Songhonglei/better-office-work-flow
xhs-image-note-release
通过 ego-browser 自动化发布小红书图文笔记。覆盖从打开创作平台到发布成功的完整流程,包含图片批量上传、标题/正文填写、话题标签、以及最关键的发布按钮处理。
Dependencies
本技能依赖以下外部工具/技能,必须在使用前安装并配置:
| 依赖 | 类型 | 用途 | 安装方式 | 验证 |
|---|---|---|---|---|
| ego-browser (ego-lite) | CLI 工具 + Skill | 浏览器自动化引擎,提供 CDP、snapshot、fillInput 等 API | 参考 ego-browser skill 安装 | ego-browser --version 能正常输出版本号 |
| 小红书账号 | 平台账号 | 需在 ego-lite 浏览器中已登录小红书 | 手动在 ego-lite 中登录 creator.xiaohongshu.com | 打开创作平台能看到发布按钮 |
| WorkBuddy 沙箱 | 环境配置 | 沙箱模式会 SIGKILL ego-browser 进程(exit 137) | WorkBuddy 设置 → 关闭沙箱 | 运行 ego-browser --version 不报 sandbox 错误 |
ego-browser API:本技能代码中使用的
useOrCreateTaskSpace、openOrReuseTab、snapshotText、click、fillInput、typeText、pressKey、cdp、js、pageInfo、completeTaskSpace、captureScreenshot、wait、waitForNetworkIdle、cliLog均为 ego-browser 提供的 API,需先Skill("ego-browser")加载后使用。
前置条件
- ego-lite 已安装且运行中 — ego-browser CLI 依赖 ego-lite app 提供的浏览器环境
- WorkBuddy 沙箱已关闭 — 沙箱模式下 ego-browser 进程会被 SIGKILL(exit 137),需在 WorkBuddy 设置中关闭沙箱
- 小红书已登录 — ego-browser 继承用户登录态,需在 ego-lite 中手动登录小红书一次
- 已加载 ego-browser 技能 — 先
Skill("ego-browser")加载浏览器操作技能
核心流程(9 步)
创建 task space → 打开创作平台 → 等待 SPA 渲染(15s) → 进入图文发布页(自动兼容 tab/下拉)
→ 上传图片(CDP 批量) → 填标题 → 填正文 → 关闭话题弹窗
→ 调用 _onPublish() 发布 → 等待跳转确认 → 清理 task space
1. 创建 task space 并打开创作平台
ego-browser nodejs <<'EOF'
const task = await useOrCreateTaskSpace('publish xhs note')
await openOrReuseTab('https://creator.xiaohongshu.com/publish/publish', { wait: true, timeout: 25 })
// Vue SPA,必须等待 15 秒渲染
await wait(15)
EOF
关键点:小红书创作平台是 Vue SPA,<div id="app"> 初始为空,waitForNetworkIdle 不够,必须 await wait(15)。
2. 进入图文发布页
创作平台支持两种入口进入图文编辑页,脚本会自动兼容两种 UI:
- 方式 A(顶部 tab):页面顶部有「上传视频 / 上传图文 / 写长文」tab 导航,点击「上传图文」
- 方式 B(下拉菜单):页面有「发布笔记」下拉按钮,点击展开后选「上传图文」
脚本逻辑:先试方式 A(按文本遍历点击),检查是否出现 input.upload-input;如果没有,回退到方式 B(snapshotText 匹配 ref 点击)。
// 方式 A:点击顶部「上传图文」tab
const tabClicked = await js(`(() => {
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false)
let node
while (node = walker.nextNode()) {
if (node.textContent.trim() === '上传图文') {
let element = node.parentElement
for (let i = 0; i < 4; i++) {
if (!element) break
element.click()
element.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }))
element = element.parentElement
}
return true
}
}
return false
})()`)
await wait(3)
// 检查是否已进入图文编辑页
if (!await js(`!!document.querySelector('input.upload-input')`)) {
// 方式 B:回退到「发布笔记」下拉菜单
const pageText = await snapshotText()
const matchPublish = pageText.match(/发布笔记.*?\[ref=(\d+)/)
if (matchPublish) {
await click('@' + matchPublish[1])
await wait(2)
const text2 = await snapshotText()
const matchUpload = text2.match(/上传图文.*?\[ref=(\d+)/)
if (matchUpload) {
await click('@' + matchUpload[1])
await waitForNetworkIdle(5)
await wait(3)
}
}
}
注意:两种 UI 可能同时存在或随版本切换,脚本以
input.upload-input是否出现为判断标准,自动选择可用入口。
3. 上传图片(CDP 批量)
不能用 uploadFile() 传逗号分隔多文件路径(不生效)。必须用 CDP:
const doc = await cdp('DOM.getDocument', {})
const inputNode = await cdp('DOM.querySelector', {
nodeId: doc.root.nodeId,
selector: 'input.upload-input'
})
await cdp('DOM.setFileInputFiles', {
files: ['/abs/path/img1.png', '/abs/path/img2.png', ...],
nodeId: inputNode.nodeId
})
await wait(8) // 等待图片处理
- 图片推荐尺寸:1080x1440(3:4 竖屏)
- 最多 18 张
4. 填写标题
await fillInput('css:input[placeholder="填写标题会有更多赞哦"]', '标题内容')
5. 填写正文
正文区域是 contenteditable="true" 的富文本编辑器:
// 先 focus 编辑器
await js(`document.querySelector('[contenteditable="true"]').focus()`)
// 再用 typeText 输入(支持换行和 #话题标签)
await typeText('正文内容...\n\n#话题1 #话题2')
await wait(1)
// 按 Esc 关闭话题建议弹窗
await pressKey('Escape')
6. 点击发布按钮(最关键的坑点)
问题
小红书的「发布」按钮封装在 <xhs-publish-btn> 自定义组件中,使用 closed Shadow DOM:
<xhs-publish-btn submit-text="发布" submit-disabled="false">
#shadow-root (closed) ← closed 意味着 host.shadowRoot 返回 null
<div class="publish-page-publish-btn">
<button class="ce-btn bg-red">发布</button>
</div>
</xhs-publish-btn>
无效的方法
| 方法 | 结果 |
|---|---|
querySelector('button.ce-btn.bg-red') |
找不到(在 shadow DOM 内) |
host.shadowRoot.querySelector(...) |
shadowRoot 为 null(closed) |
click('@N') / 坐标点击 |
snapshotText 不穿透 shadow DOM |
cdp('DOM.performSearch', {pierce: true}) |
找到节点但 nodeId=0 |
cdp('Input.dispatchMouseEvent', {x, y}) |
不触发 Vue 事件 |
成功的方法
直接调用组件暴露的内部方法 _onPublish():
const host = document.querySelector('xhs-publish-btn')
// 检查状态
const disabled = host.getAttribute('submit-disabled') // 'false' = 可点
const loading = host.getAttribute('submit-loading') // 'false' = 未加载
// 触发发布
host._onPublish()
原理:<xhs-publish-btn> 是 Web Component,原型链上暴露了 _onPublish 和 _onSave 方法。通过 host._onPublish() 直接调用,绕过 Shadow DOM 封装。
发现方法:Object.getOwnPropertyNames(Object.getPrototypeOf(host)) 列出原型方法找到 _onPublish。
7. 等待发布完成
调用 _onPublish() 后按钮进入 loading,约 5-10 秒后页面会重置并出现 published=true URL 参数。验证:
const info = await pageInfo()
if (info.url.includes('published=true') || info.url.includes('note-manage')) {
cliLog('SUCCESS: 发布成功!')
}
8. 清理
await completeTaskSpace(task.id, { keep: false })
快速复用
修改 scripts/publish_note.sh 中的 4 个参数后直接运行:
IMAGE_DIR="/path/to/images"
IMAGES="img1.png,img2.png,img3.png"
TITLE="标题"
BODY='正文内容\n\n#话题1 #话题2'
bash ~/.workbuddy/skills/xhs-image-note-release/scripts/publish_note.sh
注意事项
- 沙箱:如果报错
from the default agent sandbox,需在 WorkBuddy 设置中关闭沙箱后重试 - SPA 渲染:首次加载等 15 秒,不要用
waitForNetworkIdle代替 - 话题标签:正文中的
#话题自动被识别,输入后弹建议列表,按Escape关闭 - 发布频率:严禁短时间连续发多篇,可能触发风控;建议每次发布间隔至少 5 分钟
- 权限设置:发布前如需设置权限(公开/仅自己可见),在填正文后、点发布前操作
- 标题特殊字符:脚本中 TITLE 变量会插入 JS 单引号字符串,严禁包含单引号,否则会中断脚本
- 正文特殊字符:脚本中 BODY 变量会插入 JS 模板字符串,严禁包含反引号(
`)和${,否则会中断脚本
Resources
scripts/
scripts/publish_note.sh— 一键发布脚本,修改 4 个参数即可复用
references/
references/publish-method.md— 完整方法文档,含失败方案对比表和技术原理详解。按需加载:当需要了解发布按钮失败方案的完整对比、或需要排查 closed Shadow DOM 穿透问题时阅读此文件;正常发布流程无需提前加载