- Author: Daniel Li
- Copyright © Daniel Li. All rights reserved.
⚡ Quick Workflow
触发词 → 填写内容 → 🔍 截图确认 → ⏳ 等待指令 → ✅ 发布/存草稿
Key: The screenshot confirmation step cannot be skipped!
| Step | Action | Blocking? |
|---|---|---|
| 1 | Fill in title, body, hashtags, upload cover | ❌ |
| 2 | Send screenshot to group | ⚠️ Required |
| 3 | Wait for user confirmation | ✅ Blocking |
| 4 | Upon receiving "publish" → click publish | ❌ |
| 4 | Upon receiving "save as draft" → click save draft | ❌ |
User instruction:
发布/确认发布→ execute publish存草稿/暂存→ save to drafts
Formatting Rules
| Item | Rule |
|---|---|
| Title | ≤20 characters, emoji + keywords + numbers, formula: audience + keywords + numbers + suspense + emoji |
| Body | 300-1000 characters, double line breaks between paragraphs, each paragraph starts with 1-2 emojis, put the core in the first 3 lines |
| Hashtags | #topic name (no spaces) 3-8, precise ones first, broad topics after |
| Cover | 3:4 vertical (1080×1440), large text + contrasting colors + person/product occupying 60%+ |
| Images | 1-18 images, JPG/PNG, ≤20MB each |
| Best time | Weekdays 18:00-22:00, weekends 20:00-23:00 |
Content Adaptation Template
When converting any content into Xiaohongshu format:
- Title: extract the core selling point, apply the formula
人群定位+关键词+数字+悬念词+emoji- Example:
打工人必看🔥5个效率工具让你准时下班
- Example:
- Body formatting:
emoji 开头金句/痛点(抓注意力) emoji 正文第一段(核心信息,≤3行) emoji 正文第二段(展开论述) emoji 正文第三段(补充/案例) emoji 总结/互动引导(提问/投票) #精准话题1 #精准话题2 #泛话题3 #泛话题4 - Emoji strategy: 1 at the start of each paragraph, moderately accent next to keywords in the body, no more than 5% of the body
- Hashtag strategy: 2 precise topics (high search volume) + 2-4 broad topics (high exposure) + 1-2 long-tail topics
Publishing Process (Browser tool operations)
General process
Step 0: Confirm the publishing content type
- Video → follow the video publishing process
- Image-text → follow the image-text publishing process
- Long-form → follow the long-form publishing process
📷 Image-Text Publishing Process (7 steps)
Step 1: Open the publish page
browser(action="navigate", profile="openclaw",
targetUrl="https://creator.xiaohongshu.com/publish/publish?source=official&target=image")
Step 2: Confirm the publishing type
- Click "Upload Image-Text"
Step 3: Upload cover image
- Click the upload button or
input[type='file']upload cover image - Wait for the image to finish loading
Step 4: Fill in the title
- ⚠️ Locate the title input box: placeholder="Fill in the title to get more likes"
- Use JavaScript to fill precisely (to prevent UI misalignment):
// 找到标题输入框并设置值
const titleInput = document.querySelector('input[placeholder*="标题"]');
if(titleInput) {
titleInput.value = '标题内容(≤20字)';
titleInput.dispatchEvent(new Event('input', {bubbles: true}));
}
Step 5: Fill in the body (⚠️ the most critical step)
- First click the body editor to focus it:
browser(action="act", kind="click", ref="<正文框ref>") - Wait 500ms let the Tiptap/ProseMirror editor initialize
- Use the Tiptap/ProseMirror API to fill in the body:
// 将 Markdown 转换为 HTML
const text = `要粘贴的正文内容`;
const paragraphs = text.split('\n').filter(p => p.trim());
const htmlParts = paragraphs.map(p => {
// 处理 **粗体**
p = p.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
return '<p>' + p + '</p>';
});
const html = htmlParts.join('');
// 写入编辑器
const editor = document.querySelector('.tiptap.ProseMirror');
if (editor) {
editor.focus();
editor.innerHTML = html;
editor.dispatchEvent(new Event('input', {bubbles: true}));
}
- Verify: The body text box word count should display
X/1000(X>0); if it still displays0/1000, try solutions B/C/D (see "UI issues and solutions") - ⚠️ If using browser evaluate, fn cannot contain await; wrap it with setTimeout:
new Promise(r=>setTimeout(r,500)).then(()=>{...})
Step 6: Check layout
- ⚠️ You must first verify that the body text has been written: the body text box word count should be > 0
- Take a screenshot to confirm the formatting
- Confirm the title box contains only the title (≤20 characters), word count
X/20 - Confirm the body text box contains the complete body text, word count
X/1000 - If body text is 0/1000 → body text was not written → go back to Step 5 and try other solutions
- If there are layout issues, adjust manually
Step 7: 🔍 Screenshot confirmation (required step)
- ⚠️ Before clicking publish/save draft, this step must be performed
- Use browser to take a screenshot of the current page content:
browser(action="screenshot", target="host") - Send the screenshot to the group to report:
message(action="send", channel="telegram", target="-1003890797239", message="📝 小红书内容已填写完毕,请确认后回复'发布'或'存草稿'", accountId="xiaocode") - You must wait for user confirmation before continuing(do not publish automatically)
Step 8: ⏳ Wait for user confirmation
- The user replies with one of the following commands:
发布/确认发布→ execute publish存草稿/暂存→ save to drafts修改/取消→ stop the process and wait for retriggering
- ⚠️ This step is blocking; do not perform any operation before receiving an explicit command
Step 9: ✅ Execute publish/save draft
Choose based on user confirmation:
Publish mode:
- Click the "Publish" button
- Wait for the publish success prompt
- Take a screenshot to confirm the publish result
- Send a completion notification to the group
Draft mode:
- Click the "Save and leave" button
- Confirm the draft was saved successfully
- Send a completion notification to the group
📝 Long-form publishing process (6 steps)
Compared with image-text posts, the step of uploading an image cover is omitted
Step 1: Open the publishing page
- URL:
https://creator.xiaohongshu.com/publish/publish?source=official
Step 2: Confirm publishing type
- Click "Write long-form"
Step 3: Fill in the title
- Same as image-text Step 4
Step 4: Fill in the body text
- Same as image-text Step 5
Step 5: 🔍 Screenshot confirmation (required step)
- Same as image-text Step 7: you must first take a screenshot and send it to the group to wait for confirmation
Step 6: ⏳ Wait for user confirmation
- Same as image-text Step 8: wait for the user to reply "publish" or "save draft"
Step 7: ✅ Execute publish/save draft
- Same as image-text Step 9
🎬 Video publishing process (3 steps)
Step 1: Upload video
- Click "Upload video"
- Select the video file to upload
Step 2: 🔍 Screenshot confirmation (required step)
- After filling in the title, description, and other content
- Execute
browser(action="screenshot", target="host")screenshot - Send to the group and wait for confirmation
- ⚠️ You must wait for user confirmation before publishing
Step 3: ⏳ Wait for user confirmation
- Same as image-text Step 8
Step 4: ✅ Execute publish
- After user confirmation, click "Publish"
Automation script usage
python scripts/xhs_publish.py \
--title "标题文字" \
--content "正文内容" \
--tags "话题1,话题2,话题3" \
--image "/path/to/cover.jpg" \
--mode draft \
--auto-login
Parameter description:
--auto-login: Automatically connect to openclaw browser to obtain login state--mode draft: Save to drafts (default)--mode publish: Publish directly
Browser Automation Notes
Logged-in State Acquisition
Use openclaw browser profile="openclaw", connect via CDP (http://127.0.0.1:18800) to automatically reuse the login state.
UI Issues and Solutions
❌ Issue 1: The body text box 0/1000 cannot be written into (confirmed 2026-03-20)
Symptom: The title is correct (8 characters), but the body text box shows 0/1000, and neither fill nor type of browser can write content.
Root cause: The Xiaohongshu creator platform has migrated from Quill.js to Tiptap/ProseMirror(class=tiptap.ProseMirror). Ordinary DOM operations (setting innerText/value) do not trigger React state updates, resulting in:
- Although the content appears visually, the internal state of Tiptap is empty
- The word count does not change (0/1000)
- The body text is lost after saving the draft
✅ Solution (recommended solution):
Option A: Direct write via Tiptap/ProseMirror API (recommended)
// 将 Markdown 转换为 HTML 段落
const text = '要粘贴的正文内容\n第二段内容';
const paragraphs = text.split('\n').filter(p => p.trim());
const htmlParts = paragraphs.map(p => {
// 处理 **粗体**
p = p.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
return '<p>' + p + '</p>';
});
const html = htmlParts.join('');
// 写入 Tiptap 编辑器
const editor = document.querySelector('.tiptap.ProseMirror');
if (editor) {
editor.focus();
editor.innerHTML = html;
editor.dispatchEvent(new Event('input', {bubbles: true}));
}
Option B: Simulate paste via Clipboard API (alternative)
// 先点击正文编辑器获取焦点
document.querySelector('.tiptap.ProseMirror')?.click();
// 等待 300ms 让编辑器初始化
await new Promise(r => setTimeout(r, 300));
// 使用 Clipboard API 粘贴
const text = '要粘贴的正文内容';
const clipboardData = new DataTransfer();
clipboardData.setData('text/plain', text);
const pasteEvent = new ClipboardEvent('paste', {
clipboardData: clipboardData,
bubbles: true,
cancelable: true
});
document.querySelector('.tiptap.ProseMirror')?.dispatchEvent(pasteEvent);
Option C: executeCommand (compatible solution)
document.querySelector('.tiptap.ProseMirror')?.focus();
document.execCommand('selectAll');
document.execCommand('insertText', false, '正文内容');
Verification criterion: After writing, the body text box word count should display X/1000(X>0)。
❌ Issue 2: Title and body content are mixed up
Symptom: The title input box displays a character count such as "510/20", and the content was pasted into the title position.
Root cause: Incorrect positioning of the contenteditable field of the editor UI.
Solution:
- Fill in the title first: Use
input[placeholder*="标题"]to locate - Then click the body text box: After clicking
.ql-editor, wait 500ms - Finally fill in the body text: Use Option A to paste
- Verify that the title box word count ≤ 20 and the body text box word count > 0
Manual Correction Process (final fallback)
- Open the drafts
- Click "Edit" to enter the article
- Manually copy the body text to the correct position and correct the title
- Click publish
Error Handling
| Error | Handling |
|---|---|
| Login expired | Prompt the user to scan the QR code again and save the new cookie |
| Slider verification | Pause and wait for manual completion, set --headless false |
| Title too long | Automatically truncate to 20 characters and warn |
| Image upload failed | Retry 3 times with a 5-second interval each time |
| Network timeout | Retry 3 times with exponential backoff (5s/15s/45s) |
| Publishing rate limit | Wait 60 seconds and retry |
| Content mixed into the title | Manually correct after saving the draft |
Pre-publish Checklist
- Title ≤ 20 characters, including emoji and keywords
- Body text ≥ 100 characters (required by new rules)
- 3-8 tags, # format
- Cover image in 3:4 vertical format
- No watermarks from other platforms
- Not AI-reposted content (originality requirement)
File Structure
xhs-smart-publisher/
├── SKILL.md
├── scripts/
│ └── xhs_publish.py # Playwright 自动发布脚本
├── references/
│ └── platform-rules.md # 完整平台规则(从调研文档提取)
└── templates/
└── content-template.md # 排版模板示例