> ⚠️ 本技能基于 多轮真实测试 验证通过,覆盖文章/图文/视频三种类型。
> 测试账号:小小程序猿🐒
> 测试时间:2026-05-14 ~ 2026-06-01
| 类型 | 适用场景 | 发布方式 | 流程步数 | 测试轮次 | 状态 |
|---|---|---|---|---|---|
| ------ | --------- | --------- | :--------: | :--------: | :----: |
| 📝 文章 | 长文内容(≤8000字) | 脚本一键 | 8步 | 10轮 | ✅ 稳定 |
| 🖼️ 图文 | 图片+文字(≤1000字) | 浏览器操作 | 10步 | 10轮 | ✅ 稳定 |
| 🎬 视频 | 视频(≤16GB/≤60min) | 浏览器操作 | 8步 | 5轮 | ✅ 稳定 |
openclaw browser starthttp://127.0.0.1:18800)workspace/douyin-publish.cjs)creator.douyin.com 登录(首次需扫码)| 发布类型 | 需要的素材 | 格式 | 存放位置 |
|---|---|---|---|
| --------- | ----------- | ------ | --------- |
| 📝 文章 | 头图 + 封面 各1张 | jpg/png | 任意路径→拷贝到 /tmp/openclaw/uploads/ |
| 🖼️ 图文 | 1-35张图片 | jpg/png/webp | 任意路径→拷贝到 /tmp/openclaw/uploads/image_XX.jpg |
| 🎬 视频 | 1个视频文件 | mp4/webm | 任意路径 |
```
curl -sL --max-time 8 -H "Referer: https://www.pexels.com/" \
-o /path/to/output.jpg \
"https://images.pexels.com/photos/{id}/pexels-photo-{id}.jpeg?cs=srgb&fm=jpg"
```
https://api.pexels.com/videos/search?query=dog&per_page=5https://videos.pexels.com/video-files/{id}/{filename}.mp4| 工具 | 用途 | 使用场景 |
|---|---|---|
| ------ | ------ | --------- |
Playwright (chromium) | 浏览器自动化操作 | 全部类型 |
page.setInputFiles() | 上传视频/图片 | 全部类型 |
page.waitForEvent('filechooser') | 监听文件选择弹窗 | 文章/图文上传 |
page.mouse.click() | 模拟鼠标点击 | 点击上传区域 |
page.keyboard.type() | 逐字输入文字 | 图文/视频的描述区 |
page.keyboard.press() | 按键操作(Meta+A, Backspace等) | 全选删除 |
page.evaluate() | 直接执行JS(最稳定) | 所有交互操作 |
nativeInputValueSetter | React 输入框设值 | 填标题(所有类型) |
execCommand('insertText') | Slate.js 编辑器插入文本 | 视频描述 |
element.click() | 原生点击(触发React) | 话题选择 |
document.querySelector() | 查找元素 | 全部类型 |
document.querySelectorAll() | 查找多个元素 | 全部类型 |
Object.getOwnPropertyDescriptor() | 获取 input value setter | React输入 |
window.getComputedStyle() | 获取元素样式 | 检查可见性 |
以下操作在所有发布类型中通用:
const { chromium } = require('playwright');
const { execSync } = require('child_process');
const cdpHttp = 'http://127.0.0.1:18800';
const ver = JSON.parse(execSync(`curl -s ${cdpHttp}/json/version`).toString());
const browser = await chromium.connectOverCDP(ver.webSocketDebuggerUrl);
const context = browser.contexts()[0];
// 关闭旧抖音页面
for (const p of context.pages()) {
if (p.url().includes('creator.douyin.com')) await p.close();
}
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
const ti = document.querySelector('input[placeholder*="标题"]');
setter.call(ti, '标题内容');
ti.dispatchEvent(new Event('input', { bubbles: true }));
for (const topic of ['话题1', '话题2', '话题3']) {
// 点 #添加话题 按钮
await page.evaluate(() => {
for (const btn of document.querySelectorAll('.toolbar-button-spPS4r'))
if (btn.textContent.includes('话题')) btn.click();
});
await new Promise(r => setTimeout(r, 300));
await page.keyboard.type(topic, { delay: 10 });
await new Promise(r => setTimeout(r, 1500));
// 点第一个建议项
await page.evaluate(() => {
const el = document.querySelector('.tag-dVUDkJ.tag-hash-o0tpyE');
if (el) el.click();
});
await new Promise(r => setTimeout(r, 500));
}
await page.evaluate(() => window.scrollTo(0, 600));
await new Promise(r => setTimeout(r, 300));
await page.evaluate(() => {
const sb = document.querySelector('.selectBox-buZRzi');
if (sb) sb.click();
});
await new Promise(r => setTimeout(r, 1500));
await page.evaluate(() => {
const modals = document.querySelectorAll('.semi-modal');
for (let i = 0; i < modals.length; i++) {
if (modals[i].textContent.includes('内容由AI生成')) {
const labels = modals[i].querySelectorAll('label, .semi-radio');
for (let j = 0; j < labels.length; j++)
if (labels[j].textContent.includes('内容由AI生成')) { labels[j].click(); break; }
const btns = modals[i].querySelectorAll('button');
for (let j = 0; j < btns.length; j++)
if (btns[j].textContent.includes('确定')) btns[j].click();
}
}
});
await page.evaluate(() => window.scrollTo(0, 1200));
await new Promise(r => setTimeout(r, 200));
// 点「定时发布」
const all = document.querySelectorAll('span, div, label');
for (const el of all) if (el.textContent.trim() === '定时发布') { el.click(); break; }
await new Promise(r => setTimeout(r, 1500));
// 设时间(只改 placeholder="日期和时间" 的 input)
const inputs = document.querySelectorAll('input');
for (const inp of inputs) {
if (inp.placeholder === '日期和时间') {
const s = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set;
s.call(inp, '2026-06-04 19:00');
inp.dispatchEvent(new Event('input', { bubbles: true }));
inp.dispatchEvent(new Event('change', { bubbles: true }));
break;
}
}
const btns = document.querySelectorAll('button');
for (const btn of btns) if (btn.textContent.trim() === '发布') { btn.click(); break; }
长文内容,≤8000字,含头图+封面+话题+配乐。
脚本一键发布,通过 workspace/douyin-publish.cjs 脚本运行。
++ 分段下载头图+封面到 /tmp/openclash/uploads/:
mkdir -p /tmp/openclaw/uploads
# 头图
curl -sL --max-time 8 -H "Referer: https://www.pexels.com/" \
-o /tmp/openclaw/uploads/article_header.jpg \
"https://images.pexels.com/photos/{id}/pexels-photo-{id}.jpeg?cs=srgb&fm=jpg"
# 封面
curl -sL --max-time 8 -H "Referer: https://www.pexels.com/" \
-o /tmp/openclaw/uploads/article_cover.jpg \
"https://images.pexels.com/photos/{id}/pexels-photo-{id}.jpeg?cs=srgb&fm=jpg"
编辑 workspace/douyin-publish.cjs 的 CONFIG 区:
const CONFIG = {
PUBLISH: true,
TYPE: 'article', // article | image | video
TITLE: '标题(≤30字)',
SUMMARY: '摘要(≤30字)',
BODY: '<p>正文HTML</p>',
TOPICS: ['话题1', '话题2', '话题3'],
MUSIC_KEYWORD: '纯音乐',
SCHEDULED_TIME: '2026-06-04 19:00',
PEXELS_PHOTOS: [],
};
cd ~/.openclaw/workspace
NODE_PATH=~/.npm-global/lib/node_modules node douyin-publish.cjs --type=article --publish
/content/post/article?type=newnativeInputValueSetter + innerHTML)fileChooser)选择音乐→搜纯音乐→点使用)| 元素 | 选择器 | 操作 |
|---|---|---|
| ------ | -------- | ------ |
| 标题输入 | input[placeholder*="标题"] | nativeInputValueSetter |
| 摘要输入 | input[placeholder*="摘要"] | nativeInputValueSetter |
| 正编辑区 | [contenteditable] | innerHTML 注入 |
| 上传头图 | 文本"点击上传图片" | 先 waitForEvent('filechooser') 再 click |
| 上传封面 | 文本"点击上传封面图" | 同上 |
| 话题弹窗搜索 | input[placeholder*="搜索"] | 填关键词 |
| 话题选项 | .topicName-Nd8EPd | 点击选择 |
| 配乐按钮 | .action-Q1y01k | 点击打开侧栏 |
| 配乐搜索 | input[placeholder="搜索音乐"] | nativeInputValueSetter |
| 配乐使用 | .apply-btn-LUPP0D | 三设 display/visibility/opacity 后点击 |
| 配乐侧栏遮罩 | .semi-sidesheet-mask | 点击关闭 |
图片+文字,≤1000字,1-35张图,含封面+话题+配乐。
浏览器 Playwright MCP 手动分步操作。
await page.evaluate(() => { window.onbeforeunload = null; });
await page.goto('https://creator.douyin.com/creator-micro/content/post/image?type=new&_=' + Date.now());
// 加随机参数避免草稿残留
const fc = page.waitForEvent('filechooser', { timeout: 20000 });
// 点击手机预览区中心偏下
const screen = await page.evaluate(() => {
const el = document.querySelector('.phone-screen-emLY2d');
const r = el.getBoundingClientRect();
return { x: r.x, y: r.y };
});
await page.mouse.click(screen.x + 121, screen.y + 287);
await (await fc).setFiles(imageFiles);
> 验证:页面出现「已添加X张图片」文本
nativeInputValueSetter 填 title input(≤20字)
const editable = page.locator('[contenteditable]').first();
await editable.click();
await page.keyboard.press('Meta+a'); // 全选
await page.keyboard.press('Backspace'); // 删除
await page.keyboard.type('描述内容...', { delay: 3 });
> 关键:Slate.js 编辑器,必须 keyboard.type 逐字输入,不能用 innerHTML
(见通用操作)
// 点「编辑封面」
await page.evaluate(() => {
const ec = document.querySelector('[class*=mycard-info-text]');
if (ec && ec.textContent.includes('编辑封面')) ec.click();
});
// 页面跳转到 poster 页,选第一张缩略图
await page.evaluate(() => {
const imgs = document.querySelectorAll('img[src*="tos-cn-i"]');
if (imgs.length > 0) imgs[0].click();
});
// 点「确定」自动回跳
await page.evaluate(() => {
for (const b of document.querySelectorAll('button'))
if (b.textContent.includes('确定')) b.click();
});
(见通用操作)
// 点「选择音乐」右按钮
await page.evaluate(() => { const b = document.querySelector('.action-Q1y01k'); if (b) b.click(); });
await new Promise(r => setTimeout(r, 1500));
// 搜索纯音乐
await page.evaluate(() => {
for (const inp of document.querySelectorAll('input'))
if (inp.placeholder === '搜索音乐') {
const s = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set;
s.call(inp, '纯音乐');
inp.dispatchEvent(new Event('input', { bubbles: true })); break;
}
});
await new Promise(r => setTimeout(r, 1500));
// 点「使用」
await page.evaluate(() => {
const sheet = document.querySelector('.semi-sidesheet');
for (const btn of sheet.querySelectorAll('.apply-btn-LUPP0D')) {
btn.style.display = 'block';
btn.style.visibility = 'visible';
btn.style.opacity = '1';
btn.click(); return;
}
});
> 关键:配乐后必须关侧栏(点遮罩或Escape),否则遮罩挡住发布按钮
// 关侧栏
await page.evaluate(() => { const m = document.querySelector('.semi-sidesheet-mask'); if (m) m.click(); });
// 定时发布(见通用操作)
(见通用操作)
| 元素 | 选择器 | 操作 |
|---|---|---|
| ------ | -------- | ------ |
| 手机预览区 | .phone-screen-emLY2d | 点击上传 |
| 标题输入 | input[placeholder*="标题"] | nativeInputValueSetter |
| 描述编辑区 | [contenteditable] | keyboard.type |
| 话题按钮 | .toolbar-button-spPS4r text="添加话题" | element.click() |
| 话题建议项 | .tag-dVUDkJ.tag-hash-o0tpyE | element.click() |
| 封面编辑 | 文本"编辑封面" | 点击→跳转 poster 页 |
| 声明下拉 | .selectBox-buZRzi | 点击打开弹窗 |
| 配乐按钮 | .action-Q1y01k | 点击打开侧栏 |
| 配乐使用 | .apply-btn-LUPP0D | 三设后点击 |
| 侧栏遮罩 | .semi-sidesheet-mask | 点击关闭 |
| 定时发布 | 文本"定时发布" | 点击 |
| 时间输入 | input[placeholder="日期和时间"] | nativeInputValueSetter |
视频(≤16GB/≤60min),含标题+话题+声明,跳过封面设置,不配乐。
浏览器 Playwright MCP 手动分步操作。
await page.goto('https://creator.douyin.com/creator-micro/content/upload');
await page.waitForTimeout(3000);
// 直接设 file input
await page.setInputFiles('input[type="file"]', '/path/to/video.mp4');
// 等视频处理(15秒以上)
await new Promise(r => setTimeout(r, 15000));
// AI封面会自动生成,但不操作
for (let i = 0; i < 20; i++) {
const n = await page.evaluate(() => document.querySelectorAll('img[src*="blob"]').length);
if (n >= 2) break;
await new Promise(r => setTimeout(r, 1000));
}
nativeInputValueSetter(≤30字)
const ed = document.querySelector('[contenteditable]');
if (ed) { ed.focus(); document.execCommand('insertText', false, '描述内容...'); }
> 视频页的编辑器也是 Slate.js,用 execCommand('insertText') 即可
(见通用操作)
// ❌ 不操作,默认不设封面
> 不去点击横/竖封面区域
(见通用操作)
(见通用操作)
| 元素 | 选择器 | 操作 |
|---|---|---|
| ------ | -------- | ------ |
| 视频上传 | input[type="file"] | setInputFiles |
| 标题输入 | input[placeholder*="标题"] | nativeInputValueSetter |
| 描述编辑区 | [contenteditable] | execCommand('insertText') |
| 话题按钮 | .toolbar-button-spPS4r | element.click() |
| 话题建议项 | .tag-dVUDkJ.tag-hash-o0tpyE | element.click() |
| 声明下拉 | .selectBox-buZRzi | 点击打开弹窗 |
| 定时发布 | 文本"定时发布" | 点击 |
| 时间输入 | input[placeholder="日期和时间"] | nativeInputValueSetter |
| 横封面区域 | .cover-Jg3T4p | ❌ 不点击(跳过) |
| 项目 | 📝 文章 | 🖼️ 图文 | 🎬 视频 |
|---|---|---|---|
| ------ | -------- | -------- | -------- |
| 编辑页URL | /content/post/article | /content/post/image | /content/upload→跳转 |
| 发布方式 | 脚本一键 | 浏览器分步 | 浏览器分步 |
| 标题字数 | ≤30字 | ≤20字 | ≤30字 |
| 正文字数 | ≤8000字 | ≤1000字 | 不限 |
| 图片上传 | 头图+封面分开 | 批量上传1-35张 | 不需要 |
| 视频上传 | 不需要 | 不需要 | 1个文件 |
| 封面设置 | 上传或AI | 从图中选 | ❌ 跳过(默认AI) |
| 话题添加 | 弹窗搜索选择 | 弹窗搜索选择 | 弹窗搜索选择 |
| 声明方式 | 内容类型声明 | 自主声明 | 自主声明 |
| 配乐 | ✅ 搜索纯音乐 | ✅ 搜索纯音乐 | ❌ 跳过 |
| AI检查 | 无 | 发文助手 | 发文助手 |
| 随机参数避草稿 | 不需要 | ✅ 需要 &_=时间戳 | 不需要 |
| beforeunload | 不处理 | ✅ navigate前null | ✅ navigate前null |
| 预估用时 | 25-30秒 | 30-40秒 | 25-30秒 |
ERROR: Protocol error (Browser.setDownloadManagement): Browser context management is not supported.
解决方法:重启浏览器 → openclaw browser stop && openclaw browser start
Command failed: curl -s http://127.0.0.1:18800/json/version
解决方法:确保 OpenClaw browser 正在运行
页面显示扫码登录。
解决方法:手动扫码登录一次,cookie 持久化
.action-Q1y01k 找不到或点不开。
解决方法:先用 evaluate 滚动到配乐区域,再 retry 5次点击
.apply-btn-LUPP0D 被隐藏。
解决方法:用 evaluate 修改 display/visibility/opacity 三属性
点击上传区域后没有触发文件选择弹窗。
解决方法:
waitForEvent('filechooser') 再 clickpage.mouse.click(x, y) 代替 dispatchEvent上传成功但页面不显示图片。
解决方法:等4-5秒让页面渲染,检查"已添加X张图片"文本
点「确定」后页面没有跳回图文编辑页。
解决方法:等2-3秒自动回跳,或用 page.goBack()
上传后长时间不跳转到编辑页。
解决方法:等待15秒以上,视频越长时间越长
解决方法:默认不操作封面区域,跳过即可
| # | 坑 | 症状 | 解决 |
|---|---|---|---|
| :--: | ----- | ------ | ------ |
| 1 | 图文草稿残留 | 打开编辑页显示旧内容 | URL 加 &_=时间戳 |
| 2 | Slate 编辑器 innerHTML | 内容消失 | 用 keyboard.type 或 execCommand('insertText') |
| 3 | 话题点不上 | 点击无反应 | 用 element.click() 原生方法点 .tag-dVUDkJ |
| 4 | 话题上限5个 | 超出后点不上 | 删草稿重新发布 |
| 5 | 配乐侧栏挡发布按钮 | 发布按钮点不动 | 配乐后关 mask 或 Escape |
| 6 | 定时时间写进标题 | 标题变成时间字符串 | 只改 placeholder="日期和时间",不碰标题 input |
| 7 | for...of 变量冲突 | evaluate 报错 m is not defined | 用传统 for 循环 |
| 8 | 封面确认弹窗未处理 | 后续声明弹窗错乱 | 选封面后必须点「是否确认应用此封面?」的确定 |
| 9 | Pexels 图混入猫/兔子 | 图片内容不对 | 下载后用 image 工具确认 |
| 10 | 浏览器关闭后页面丢失 | 草稿没保存 | 保持浏览器打开,或 Douyin 会自动存草稿 |# 抖音全类型自动发布技能 (v4.0)
共 4 个版本