Install
openclaw skills install @songhonglei/xhs-image-note-release小红书图文笔记自动发布技能。通过 ego-browser 自动化完成图片上传、标题填写、正文编辑、 话题标签、发布等全流程。附带 28 种多样式风格卡片生成器(含 3 种照片背景氛围主题), 卡片主题、布局、背景图、遮罩强度、模糊、颗粒等参数均可自由配置。 当用户要求发小红书、发布图文笔记、上传到小红书、小红书发帖或涉及小红书内容发布时触发此技能。 前置依赖:ego-browser (ego-lite) 已安装且正在运行,小红书账号已登录。
openclaw skills install @songhonglei/xhs-image-note-release通过 ego-browser 自动化发布小红书图文笔记。覆盖从打开创作平台到发布成功的完整流程,包含图片批量上传、标题/正文填写、话题标签、一键发布等。附带 28 种多样式风格卡片生成器(含 3 种照片背景氛围主题),卡片参数可自由配置。
本技能依赖以下外部工具/技能,必须在使用前安装并配置:
| 依赖 | 类型 | 用途 | 安装方式 | 验证 |
|---|---|---|---|---|
| ego-browser (ego-lite) | CLI 工具 + Skill | 浏览器自动化引擎,提供 CDP、snapshot、fillInput 等 API | 参考 ego-browser skill 安装 | ego-browser --version 能正常输出版本号 |
| 小红书账号 | 平台账号 | 需在 ego-lite 浏览器中已登录小红书 | 手动在 ego-lite 中登录 creator.xiaohongshu.com | 打开创作平台能看到发布按钮 |
| Google Chrome / Chromium | 系统浏览器 | 卡片生成器 (card_generator.py) 使用 Chrome headless 渲染 PNG | macOS: 从 google.com/chrome 安装;Linux: apt install chromium-browser | 终端运行 "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --version 或 chromium --version |
| Python 3.8+ | 运行时 | 运行 card_generator.py,仅用标准库,无需 pip 安装任何包 | macOS 自带;Linux: apt install python3 | python3 --version 输出 ≥ 3.8 |
特别提示(仅 WorkBuddy 环境):较新版本的 WorkBuddy 需先关闭沙箱功能(设置 → 关闭沙箱),否则运行 ego-browser 会被中断。这不是本技能的依赖——在终端或其他 Agent 中直接运行本技能无需任何沙箱相关操作。
ego-browser API:本技能代码中使用的
useOrCreateTaskSpace、openOrReuseTab、snapshotText、click、fillInput、typeText、pressKey、cdp、js、pageInfo、completeTaskSpace、captureScreenshot、wait、waitForNetworkIdle、cliLog均为 ego-browser 提供的 API,需先Skill("ego-browser")加载后使用。
Skill("ego-browser") 加载浏览器操作技能特别提示(仅 WorkBuddy 环境):较新版本的 WorkBuddy 需先关闭沙箱功能(设置 → 关闭沙箱),否则运行 ego-browser 会被中断。这不是本技能的依赖,在终端或其他 Agent 中直接运行无需此操作。
创建 task space → 打开创作平台 → 等待 SPA 渲染(15s) → 进入图文发布页(自动兼容 tab/下拉)
→ 上传图片(CDP 批量) → 填标题 → 填正文 → 逐个话题从下拉列表选择
→ 调用 _onPublish() 发布 → 等待跳转确认 → 清理 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)。
创作平台支持两种入口进入图文编辑页,脚本会自动兼容两种 UI:
脚本逻辑:先试方式 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是否出现为判断标准,自动选择可用入口。
不能用 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) // 等待图片处理
await fillInput('css:input[placeholder="填写标题会有更多赞哦"]', '标题内容')
正文区域是 contenteditable="true" 的富文本编辑器:
// 先 focus 编辑器并输入正文(不带 #话题)
await js(`document.querySelector('[contenteditable="true"]').focus()`)
await typeText('正文内容...')
await wait(1)
// 逐个话题从下拉列表选择,确保生成真正可点击的话题标签
const topics = ['话题1', '话题2']
for (const topic of topics) {
await typeText('#' + topic, { label: 'type topic' })
await wait(4) // 等待下拉列表返回
await js(`((topic) => {
const container = document.getElementById('creator-editor-topic-container')
if (!container) return
const items = [...container.querySelectorAll('.item')]
let item = items.find(el => {
const nameEl = el.querySelector('.name')
return nameEl && (nameEl.innerText === '#' + topic || nameEl.innerText.includes(topic))
})
if (!item && items.length) item = items[0]
item && item.click()
})('${topic}')`)
await wait(1.5)
await pressKey('Escape')
await typeText(' ')
}
注意:直接粘贴
#话题1 #话题2只会生成纯文本,不会真正挂载到话题词条。必须从#触发下拉列表后点击选中。
小红书的「发布」按钮封装为自定义 Web Component(<xhs-publish-btn>),常规选择器或坐标点击无法命中,需直接调用其暴露的发布方法:
const host = document.querySelector('xhs-publish-btn')
// 检查状态
const disabled = host.getAttribute('submit-disabled') // 'false' = 可点
const loading = host.getAttribute('submit-loading') // 'false' = 未加载
// 触发发布
host._onPublish()
若需了解完整的失败方案对比与排查细节,参见
references/publish-method.md。
调用 _onPublish() 后按钮进入 loading,约 5-10 秒后页面会重置并出现 published=true URL 参数。验证:
const info = await pageInfo()
if (info.url.includes('published=true') || info.url.includes('note-manage')) {
cliLog('SUCCESS: 发布成功!')
}
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='正文内容'
TOPICS="话题1,话题2,话题3"
bash ~/.workbuddy/skills/xhs-image-note-release/scripts/publish_note.sh
本技能附带统一风格卡片生成器,位于 references/card-generator/card_generator.py。
python3 ~/.workbuddy/skills/xhs-image-note-release/references/card-generator/card_generator.py \
--theme warm \
--tag "人生随笔" \
--lines "周五了\\n你还活着吗\\n去外面走走" \
--subtitle "关于活着" \
--page-number 1 \
--total-pages 6 \
--account "@雨夜心灯" \
-o ./card.png
# 图文卡片:顶部简笔画 + 窄列居中文字 + 底部署名
python3 ~/.workbuddy/skills/xhs-image-note-release/references/card-generator/card_generator.py \
--layout image-text \
--illustration ./door.svg \
--theme warm_illust \
--lines "周五了。\\n把工牌摘下来," \
--account "@雨夜心灯" \
-o ./illust-card.png
28 种内置主题(纯文字布局全部共用同一套布局;图文布局额外支持 warm_illust):
| 分组 | 主题 key |
|---|---|
照片背景氛围(需配合 --background) | cinematic 暗色电影感、film 胶片颗粒、journal 书页氛围 |
| 年轻人风格 | warm 温暖哲思、warm_illust 温暖简笔哲思、y2k Y2K 千禧潮酷、doodle 手绘涂鸦、pop 渐变波普、minimal 极简日系 |
| 科技 / 商务 | cyberpunk 赛博科技、apple Apple 质感、cowork 轻科技、bloomberg Bloomberg 终端、carbon 暗色极简 |
| 文艺 / 复古 | elegant 极简优雅、newspaper 报纸杂志、ink 水墨卷轴、steampunk 蒸汽朋克、palace 故宫金红 |
| 生活 / 治愈 | xhs 小红书·简洁、xhs_rich 小红书·丰富、morandi 莫兰迪灰、glass 玻璃拟态、fresh 清新绿、earthy 大地原木、dreamy 紫梦幻、macaron 马卡龙、vivid 活力渐变 |
小红书风有两个模式:
xhs= 简洁正式(白底 + 45° 极淡斜纹,呼吸感强,适合知识笔记);xhs_rich= 丰富活泼(淡粉渐变底 + 手账圆点纹 + 毛玻璃光晕 + 三段波浪线,适合生活攻略)。
用 --list-themes 可随时打印完整清单。
自定义参数:
| 参数 | 说明 | 示例 |
|---|---|---|
--theme | 主题风格 | --theme y2k |
--layout | 布局:text 纯文字(默认)、image-text 图文 | --layout image-text |
--illustration | image-text 布局的插画文件(.svg / .png / .jpg) | --illustration ./door.svg |
--image-text-width | image-text 文字区宽度占比(0.5–0.8,默认 0.65) | --image-text-width 0.7 |
--tag | 顶部标签(仅 text 布局默认显示) | --tag 人生随笔 |
--lines | 主文案,用 \\n 分行 | --lines "第一行\\n第二行" |
--subtitle | 副标题(短横线下方,仅 text 布局) | --subtitle 关于自省 |
--page-number / --total-pages | 页码(仅 text 布局) | --page-number 2 --total-pages 6 |
--account | 右下角账号(text)/ 底部署名(image-text) | --account @雨夜心灯 |
--hide-page-number | 隐藏页码 | 默认显示 |
--hide-account | 隐藏账号 | 默认显示 |
--hide-tag | 隐藏顶部标签 | 默认显示 |
--hide-subtitle | 隐藏副标题 | 默认显示 |
--width / --height | 输出尺寸 | 默认 1080x1440 |
--background | 满幅背景照片路径(.png/.jpg/.webp) | 无 |
--scrim | 暗色遮罩强度 0–0.92(默认取主题预设) | 主题预设 |
--blur | 背景高斯模糊半径(建议 2–6) | 0 |
--desaturate | 背景降饱和 0–1;不带值时取 0.65 | 主题预设 |
--grain | 胶片颗粒强度 0–0.4 | 主题预设 |
图文布局(--layout image-text)说明:
warm_illust(米白底、黑线稿、琥珀色点缀,与截图示例一致)。<g> 元素并居中放置。生成后配合 publish_note.sh 发布即可。
适用场景:用户要求「书籍实拍氛围」「暗色文字底图」「照片背景卡片」等需要满幅照片作为底图、文字压在图片上的效果。
照片背景功能在现有 HTML 渲染管线(SVG → HTML → Chrome headless → PNG)基础上,新增一层照片图层栈:
SVG 底层
└─ <rect> 纯色兜底(防止边缘透出)
└─ <defs>
├─ #photoFx — 可选:模糊 + 降饱和滤镜
├─ #scrimGrad — 暗色渐变遮罩(上下两端更重,保护标签/页脚可读)
├─ #txtShadow — 文字投影(保证任意画面上文字清晰)
└─ #grainFx — 可选:胶片颗粒噪点
└─ <image> — 背景照片(base64 内嵌,center-crop 铺满)
└─ <rect scrim> — 暗色遮罩层
└─ <rect grain> — 颗粒层(可选)
└─ <g filter="url(#txtShadow)">
└─ 标签 / 文案 / 页脚(全部带投影)
关键设计决策:
--background 时,文字/辅助色自动反转为亮色,无需手动切换主题file:// CSP 限制(与插画加载逻辑一致)| 主题 | key | 遮罩强度 | 遮罩色 | 颗粒 | 降饱和 | 适用情绪 |
|---|---|---|---|---|---|---|
| 暗色电影感 | cinematic | 0.55 | #000000 | 无 | 原色 | 深沉、哲思、连接 |
| 胶片颗粒 | film | 0.50 | #100B06 | 9% | 72% | 怀旧、记忆、追寻 |
| 书页氛围 | journal | 0.52 | #1A120B | 4% | 90% | 阅读、顿悟、内省 |
⚠️ 以下命令统一使用生成器的完整路径。若已
cd到references/card-generator/,可简写为python3 card_generator.py。 不要在续行符\后面加行内注释——\会转义空格而非换行,#之后整条命令被截断,下一行参数会被当作命令执行。
CG=~/.workbuddy/skills/xhs-image-note-release/references/card-generator/card_generator.py
# 基础用法:指定背景图 + 照片主题
python3 "$CG" \
--theme cinematic \
--background ./bg_photo.png \
--lines "说得着,一句顶一万句;\n说不着,万句皆是多余。" \
--account "@雨夜心灯" \
-o card_photo.png
# 高级用法:自定义遮罩、模糊、降饱和
# --scrim 0.6 更重的暗色遮罩(默认取主题预设)
# --blur 3 背景高斯模糊半径(默认 0 = 不模糊)
# --desaturate 降饱和到 70%(不带值时取 0.65)
# --grain 0.08 手动覆盖颗粒强度
python3 "$CG" \
--theme cinematic \
--background ./bg_photo.png \
--scrim 0.6 \
--blur 3 \
--desaturate 0.7 \
--grain 0.08 \
--lines "文案内容" \
--account "@雨夜心灯" \
-o card_custom.png
# 自动反转:用非照片主题(如浅色的 warm)+ --background,文字自动变亮色
python3 "$CG" \
--theme warm \
--background ./bg.png \
--lines "文案" \
-o card_auto.png
# 图文布局 + 照片背景:插画浮于照片之上,仅文字带投影
python3 "$CG" \
--layout image-text \
--illustration ./door.svg \
--theme warm_illust \
--background ./bg_photo.png \
--lines "文案" \
--account "@雨夜心灯" \
-o card_illust_photo.png
| 参数 | 类型 | 说明 | 默认值 |
|---|---|---|---|
--background | 路径 | 满幅背景照片(.png/.jpg/.webp),自动 center-crop | 无 |
--scrim | float | 暗色遮罩强度 0–0.92 | 取主题预设(通常 0.50–0.55) |
--blur | float | 背景高斯模糊半径 | 0(建议 2–6) |
--desaturate | float | 背景降饱和 0–1,1=原色;不带值时取 0.65 | 取主题预设(通常 0.72–1.0) |
--grain | float | 胶片颗粒强度 0–0.4 | 取主题预设(通常 0–0.09) |
当用户要求「书籍实拍氛围金句卡片」时,Agent 执行以下步骤:
CG=~/.workbuddy/skills/xhs-image-note-release/references/card-generator/card_generator.py
# Step 1: ImageGen 生成背景图(5 张,每张对应一条文案的情绪)
# ImageGen 是 agent 侧工具,不在本脚本内调用;产物存为 ./bg_1.png … ./bg_5.png
# Step 2: 用 card_generator 合成文字 + 背景
QUOTES=(
"说得着,一句顶一万句;\n说不着,万句皆是多余。"
"我常感到内心深处\n难以名状的孤独。"
"此刻才读懂,\n《一句顶一万句》\n到底有多动人。"
"世上人潮汹涌,\n说得着的人千里难寻。"
"我们所有的奔赴与寻找,\n本质都是对抗\n无法言说的孤独。"
)
for i in 1 2 3 4 5; do
python3 "$CG" \
--theme cinematic \
--background "./bg_${i}.png" \
--lines "${QUOTES[$((i-1))]}" \
--hide-tag --hide-subtitle \
--account "@雨夜心灯" \
--page-number "$i" --total-pages 5 \
-o "card_${i}.png"
done
# Step 3: 用 ego-browser 发布(见上文「核心流程」章节)
from the default agent sandbox,请在设置中关闭沙箱后重试(见上文「特别提示」)waitForNetworkIdle 代替TOPICS 中的每个话题都会先输入 # 触发建议下拉列表,再点击匹配项完成挂载;不要手动在 BODY 里写 #话题`)和 ${,否则会中断脚本scripts/publish_note.sh — 一键发布脚本,修改 4 个参数即可复用references/publish-method.md — 完整方法文档,含失败方案对比表和技术原理详解。按需加载:当需要了解发布按钮各失败方案的完整对比、或需要排查发布相关问题时阅读此文件;正常发布流程无需提前加载