返回 Skill 列表
extension
分类: 其它无需 API Key

小红书长文发布

小红书长文笔记自动发布(经过5轮多字数边界测试验证)

person作者: user_f4e1eca6hubcommunity

小红书长文发布 · 完整技能 v2

基于 5 轮实战迭代(不同字数、不同主题)验证的最佳实践。 区别于旧版,本技能使用「描述区 #话题名 → 等2秒 → Enter(精确匹配)」的话题添加方式, 不按方向键,直接Enter避免选中候选项后缀,成功率 100%。


📋 前置要求

环境要求

| 项目 | 要求 | 说明 | |------|------|------| | Playwright MCP | ✅ 必须 | 必需,用于文件上传(fileChooser 事件) | | Playwright 浏览器模式 | 推荐 headed | 老大建议「有头模式」方便观察,--headless 可不传 | | 小红书登录态 | ✅ 必须 | 首次需扫码,后续自动持久化 | | 封面图 | ✅ 推荐准备 | 路径: ~/.openclaw/.playwright-mcp/xhs_cover.png |

浏览器选择(重要)

  • 使用 Playwright MCP 工具playwright__browser_*),不要用 OpenClaw 内置浏览器(browser.*
  • 原因:封面文件上传需要 Playwright 的 fileChooser 事件,内置浏览器不支持
  • Playwright 浏览器 --headless=false 时,老大可以在 Chrome 窗口实时观看操作过程

Playwright MCP 配置

{
  "mcp": {
    "servers": {
      "playwright": {
        "command": "npx",
        "args": ["-y", "@playwright/mcp"]
      }
    }
  }
}

注:默认 headed。如异常为 headless 模式,排查启动参数中是否被强制加入 --headless

封面策略

  • 默认保持AI生成封面,不替换
  • 老大要求换封面时再操作

📝 完整操作流程(10步·两个Block)

核心思路

Block A — 编辑页操作(内容+封面)
Block B — 发布页操作(标题+话题+声明)独立block,避免file chooser中断

Block A: 编辑页(1-5步)

Step 1: 导航到写长文页面

await page.goto('https://creator.xiaohongshu.com/publish/publish?from=menu&target=article')
// 等待 2-3 秒加载

Step 2: 点击"新的创作"

await page.locator('button:has-text("新的创作")').first().click({ force: true })
await page.waitForTimeout(2000)

Step 3: 填写内容

// 3a. 标题填一个空格(防止标题文字出现在 AI 自动生成的封面上)
await page.evaluate(() => {
  const inputs = document.querySelectorAll('input, textarea')
  for (const inp of inputs) {
    if (inp.placeholder === '输入标题') {
      inp.value = ' '
      inp.dispatchEvent(new Event('input', { bubbles: true }))
      break
    }
  }
})

// 3b. 填写正文(建议 ≥200 字以保证更好的排版效果)
const content = `你的文章内容...`
await page.evaluate((text) => {
  const editor = document.querySelector('.tiptap.ProseMirror')
  if (editor) {
    editor.innerHTML = ''
    const paragraphs = text.split('\n\n')
    let html = ''
    for (const p of paragraphs) {
      html += `<p>${p.replace(/\n/g, '<br>')}</p>`
    }
    editor.innerHTML = html
    editor.dispatchEvent(new Event('input', { bubbles: true }))
  }
}, content)

// 3c. 验证写入(可选但推荐)
const chars = await page.evaluate(() =>
  document.querySelector('.tiptap.ProseMirror')?.textContent.length || 0
)

Step 4: 一键排版 + AI封面生成

await page.locator('button:has-text("一键排版")').first().click({ force: true })
await page.waitForTimeout(6000)  // 等排版+封面生成

// 验证页数
const pages = await page.evaluate(() => {
  const counts = new Set()
  document.querySelectorAll('*').forEach(el => {
    const t = el.textContent?.trim() || ''
    if (/^\d+\/\d+$/.test(t) && el.children.length === 0) counts.add(t)
  })
  return [...counts]
})
// pages 示例: ['1/2', '2/2'] 或 ['1/1']

Step 5: 封面检查

// 检查封面页数(仅做可视化确认,不替换封面)
const pages = await page.evaluate(() => {
  const counts = new Set()
  document.querySelectorAll('*').forEach(el => {
    const t = el.textContent?.trim() || ''
    if (/^\d+\/\d+$/.test(t) && el.children.length === 0) counts.add(t)
  })
  return [...counts]
})
// pages 示例: ['1/2', '2/2'] 或 ['1/1']
// ⚠️ 封面默认保持AI生成,老大要求换时再操作

Block B: 发布页(6-10步)

⚠️ 以下步骤必须写在独立的 run_code_unsafe block 中, 因为 Block A 中的 fileChooser 事件会中断连续执行流。

Step 6: 进入发布页

await page.locator('button:has-text("下一步")').first().click({ force: true })
await page.waitForTimeout(3000)

Step 7: 填写标题(从内容提炼)

await page.evaluate(() => {
  const inputs = document.querySelectorAll('input, textarea')
  for (const inp of inputs) {
    if (inp.placeholder?.includes('填写标题')) {
      inp.value = '你的标题(从内容提炼,≤20字)'
      inp.dispatchEvent(new Event('input', { bubbles: true }))
      break
    }
  }
})

Step 8: 添加话题(关键核心)

不要使用"话题"按钮弹窗搜索,弹窗搜索框定位不稳定且 keyboard.type 容易打到正文区。

正确方式:在描述区输入 #话题名 后按 Enter 确认,自动变为蓝色链接。

const editor = page.locator('.tiptap.ProseMirror').first()
await editor.click()

// 话题必须从内容提炼,不能机械复用
// ⚠️ 关键:必须等建议弹窗出现后再按Enter,否则可能不变成蓝色
// ⚠️ 不要按 ArrowDown,直接 Enter 才能精确匹配,按方向键会选到候选项
const topics = ['#话题1', '#话题2', '#话题3']
for (const t of topics) {
  await page.keyboard.type(t)
  await page.waitForTimeout(2000)   // 等建议弹窗出现(重要!)
  await page.keyboard.press('Enter')  // 直接Enter = 精确匹配
  await page.waitForTimeout(1000)
}
await page.waitForTimeout(1000)

// 验证话题
const topicCheck = await page.evaluate(() => {
  const ed = document.querySelector('.tiptap.ProseMirror')
  if (!ed) return { confirmed: 0, pending: 0 }
  return {
    confirmed: ed.querySelectorAll('.tiptap-topic').length,
    pending: ed.querySelectorAll('.suggestion').length
  }
})
// ✅ confirmed=3, pending=0 才算成功
// pending>0 说明有话题没确认,需要补按 Enter

Step 9: 内容类型声明(新流程)

⚠️ 与旧版不同:不再有开关按钮。新版流程为:点击「添加内容类型声明」→ 弹窗 → 选「笔记含AI合成内容」

// 点击"添加内容类型声明"
await page.locator('text=添加内容类型声明').first().click({ force: true })
await page.waitForTimeout(1500)

// 在弹窗中选择"笔记含AI合成内容"
await page.evaluate(() => {
  const all = document.querySelectorAll('*')
  for (const el of all) {
    if (el.children.length === 0 && el.textContent?.trim() === '笔记含AI合成内容') {
      el.click()
      return
    }
  }
})
await page.waitForTimeout(1000)

Step 10: 验证全部状态

const final = await page.evaluate(() => {
  let title = ''
  const inputs = document.querySelectorAll('input, textarea')
  for (const inp of inputs) {
    if (inp.placeholder?.includes('填写标题')) { title = inp.value; break }
  }

  let decl = false
  const allDivs = document.querySelectorAll('div')
  for (const div of allDivs) {
    if (div.textContent.includes('原创声明')) {
      const parent = div.parentElement
      if (parent) {
        const cb = parent.querySelector('input[type="checkbox"]')
        if (cb) decl = cb.checked
      }
      break
    }
  }

  const ed = document.querySelector('.tiptap.ProseMirror')
  const topics = ed ? ed.querySelectorAll('.tiptap-topic').length : 0
  const pending = ed ? ed.querySelectorAll('.suggestion').length : 0

  return { title, topics, pending, declaration: decl }
})
// ✅ 期望: title="你的标题", topics=3, pending=0, declaration=true

🧪 边界情况处理

字数 vs 封面

| 字数(约) | chars(实测) | 封面情况 | 行为 | |----------|-------------|----------|------| | ≥230字 | ≥180 | ✅ 独立封面 | hover→上传 | | ~200字 | ~133 | ⚠️ 可能无封面(1/1) | 跳过封面 | | <150字 | <100 | ❌ 大概率无封面 | 跳过封面 |

结论:流程必须双分支。 不要假设一定有封面。

话题添加失败

症状: topics=0pending>0 原因: keyboard.type 打到了正文区(发布页的编辑器接受焦点) 修复: 确认先 .click().tiptap.ProseMirrortype;每个话题后用 Enter 确认

原创声明开关找不到

症状: 找不到原创声明 checkbox 原因: 搜索到了页顶导航区的"原创声明"文本,而非内容设置区的开关 修复: 增加 text.includes('内容设置') 条件过滤

文件上传中断执行流

症状: Block B 的步骤不执行(标题空格、话题0) 原因: fileChooser 事件等待异步处理,run_code_unsafe 内的代码不会等待它完成 修复: 上传封面后,用一个单独的 run_code_unsafe block 执行 Block B


⚠️ 使用说明

执行前检查清单

  • [ ] 小红书创作平台已登录(Playwright MCP 浏览器中)
  • [ ] 封面图已放在 ~/.openclaw/.playwright-mcp/xhs_cover.png
  • [ ] Playwright MCP 服务正常(playwright__browser_* 工具可用)
  • [ ] 内容正文已准备好(建议 ≥200 字)
  • [ ] 标题从内容提炼,不超过 20 字
  • [ ] 话题从内容提炼,3 个为佳

执行中关键检查点

  • ✅ Block A:一键排版后验证页数(1/N vs 1/1)
  • ✅ Block A:封面上传后验证(可选:检查 background-image 是否含 xhscdn URL)
  • ✅ Block B:话题验证 confirmed=3, pending=0
  • ✅ Block B:原创声明验证 checkbox.checked=true
  • ✅ Block B:标题验证是否正确写入

常见问题 FAQ

Q: 为什么话题是 pending>0 A: 输入 #话题 后没按 Enter 确认。它只是建议状态,需要确认才变蓝色链接。

Q: 「从本地上传」不出现怎么办? A: 两种情况:① hover 位置不对——确认坐标约(168,386)在左侧封面预览区;② 小红书本次没提供上传选项——跳过,用 AI 封面。

Q: 原创声明弹窗没出现? A: 可能是开关没点到正确位置。确认搜索条件 text.includes('原创声明') && text.includes('内容设置')

Q: 标题显示为空格? A: 后续操作(如话题输入)抢了焦点了。每个 Block 结束时重新验证标题值。


📦 技能文件结构

skills/xhs-publish-v2/
├── SKILL.md          ← 本文件:完整操作手册
└── references/
    ├── workflow.md   ← 完整分步执行脚本(可选)
    └── faq.md        ← 常见问题集(可选)

🔄 版本历史

| 版本 | 日期 | 变更 | |------|------|------| | v2.1.0 | 2026-05-13 | 修正话题确认方式、原创声明新流程、封面默认不替换 | | v2.0.0 | 2026-05-13 | 完整重写:10步双Block流程、描述区话题Enter确认、多边界测试验证 | | v1.0.0 | 2026-05-09 | 初版:旧版流程,使用话题弹窗+ref定位(已弃用) |


💡 经验总结(跨场景可迁移)

这套方法论不限于小红书发布,可适用于其他平台自动化:

  1. 操作分区 — 多页表单拆分为独立区块,避免跨页状态污染
  2. 标签确认机制 — 输入→确认→验证三步法,适用于所有自动补全场景
  3. 兜底分支 — 对"不确定是否出现"的UI元素,预写双路径
  4. 每步验证 — 不自欺:每个操作后断言状态,留截图现场证据
  5. Block隔离 — file chooser / dialog 等异步交互,拆分独立执行单元