Back to skills
extension
Category: OtherNo API key required

抖音全类型自动发布

抖音全类型作品自动发布——文章、图文、视频三种类型,支持AI封面、话题添加、背景音乐、自主声明、定时发布。基于多轮真实测试验证通过。

personAuthor: user_f4e1eca6hubcommunity

抖音全类型自动发布技能 (v4.0)

⚠️ 本技能基于 多轮真实测试 验证通过,覆盖文章/图文/视频三种类型。 测试账号:小小程序猿🐒 测试时间:2026-05-14 ~ 2026-06-01


📖 目录


📦 支持的类型

| 类型 | 适用场景 | 发布方式 | 流程步数 | 测试轮次 | 状态 | |------|---------|---------|:--------:|:--------:|:----:| | 📝 文章 | 长文内容(≤8000字) | 脚本一键 | 8步 | 10轮 | ✅ 稳定 | | 🖼️ 图文 | 图片+文字(≤1000字) | 浏览器操作 | 10步 | 10轮 | ✅ 稳定 | | 🎬 视频 | 视频(≤16GB/≤60min) | 浏览器操作 | 8步 | 5轮 | ✅ 稳定 |


🔧 环境要求

必须环境

  • OpenClaw CDP Chrome 浏览器(所有类型都需要)
    • 启动命令:openclaw browser start
    • CDP 端口:18800(http://127.0.0.1:18800
    • 推荐 headed 模式(可见浏览器操作过程)
    • 登录态通过 cookie 持久化,首次需扫码

文章额外要求

  • Playwright 已安装(npm 全局安装)
  • douyin-publish.cjs 脚本(位于 workspace/douyin-publish.cjs
  • Node.js(运行脚本)

图文/视频额外要求

  • 无(直接用浏览器操作)

👤 账号要求

  • 拥有抖音账号
  • 已在 creator.douyin.com 登录(首次需扫码)
  • 登录态保持(cookie 持久化,重启浏览器后需重新扫码)
  • 账号没有被封禁发布权限

🎬 素材准备

| 发布类型 | 需要的素材 | 格式 | 存放位置 | |---------|-----------|------|---------| | 📝 文章 | 头图 + 封面 各1张 | jpg/png | 任意路径→拷贝到 /tmp/openclaw/uploads/ | | 🖼️ 图文 | 1-35张图片 | jpg/png/webp | 任意路径→拷贝到 /tmp/openclaw/uploads/image_XX.jpg | | 🎬 视频 | 1个视频文件 | mp4/webm | 任意路径 |

素材来源

  • Pexels(推荐):免费高质量图片/视频
    • 图片下载格式:
      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"
      
    • 视频搜索 API:https://api.pexels.com/videos/search?query=dog&per_page=5
    • 视频下载格式:https://videos.pexels.com/video-files/{id}/{filename}.mp4
  • 本地文件:直接拷贝到目标目录

内容准备

  • 由 AI 根据主题自动生成(推荐)
  • 或用户提供具体文案

🛠 工具列表

| 工具 | 用途 | 使用场景 | |------|------|---------| | 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();
}

填标题(React input)

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 脚本运行。

完整步骤

Step 1:写文章内容

  • 标题 ≤30字
  • 摘要 ≤30字
  • 正文 HTML排版,用 <p>+<strong>+<p></p> 分段
  • 话题 3-5个精准话题

Step 2:准备图片

下载头图+封面到 /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"

Step 3:修改脚本配置

编辑 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: [],
};

Step 4:运行发布

cd ~/.openclaw/workspace
NODE_PATH=~/.npm-global/lib/node_modules node douyin-publish.cjs --type=article --publish

脚本执行步骤(8步)

  1. 准备图片(下载Pexels或使用本地)
  2. 连接 OpenClaw CDP Chrome
  3. 打开文章编辑器:/content/post/article?type=new
  4. 填标题 + 摘要 + 正文(nativeInputValueSetter + innerHTML
  5. 上传头图 + 封面(fileChooser
  6. 添加话题(弹窗搜索→点选→确认)
  7. 添加配乐(点选择音乐→搜纯音乐→点使用
  8. 定时/立即发布

关键选择器

| 元素 | 选择器 | 操作 | |------|--------|------| | 标题输入 | 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 手动分步操作

完整步骤(10步)

Step 1:打开全新编辑器

await page.evaluate(() => { window.onbeforeunload = null; });
await page.goto('https://creator.douyin.com/creator-micro/content/post/image?type=new&_=' + Date.now());
// 加随机参数避免草稿残留

Step 2:上传图片

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张图片」文本

Step 3:填标题

nativeInputValueSetter 填 title input(≤20字)

Step 4:填描述

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

Step 5:添加话题

(见通用操作)

Step 6:设置封面

// 点「编辑封面」
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();
});

Step 7:自主声明

(见通用操作)

Step 8:配乐

// 点「选择音乐」右按钮
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),否则遮罩挡住发布按钮

Step 9:关配乐侧栏 + 定时发布

// 关侧栏
await page.evaluate(() => { const m = document.querySelector('.semi-sidesheet-mask'); if (m) m.click(); });
// 定时发布(见通用操作)

Step 10:发布

(见通用操作)

关键选择器

| 元素 | 选择器 | 操作 | |------|--------|------| | 手机预览区 | .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 手动分步操作

完整步骤(8步)

Step 1:上传视频

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));

Step 2:等AI封面生成(跳过,不选封面)

// 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));
}

Step 3:填标题

nativeInputValueSetter(≤30字)

Step 4:填描述

const ed = document.querySelector('[contenteditable]');
if (ed) { ed.focus(); document.execCommand('insertText', false, '描述内容...'); }

视频页的编辑器也是 Slate.js,用 execCommand('insertText') 即可

Step 5:添加话题

(见通用操作)

Step 6:跳过封面设置

// ❌ 不操作,默认不设封面

不去点击横/竖封面区域

Step 7:自主声明

(见通用操作)

Step 8:定时发布 + 发布

(见通用操作)

关键选择器

| 元素 | 选择器 | 操作 | |------|--------|------| | 视频上传 | 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秒 |


❗ 常见问题

通用问题

1. 浏览器连不上

ERROR: Protocol error (Browser.setDownloadManagement): Browser context management is not supported.

解决方法:重启浏览器 → openclaw browser stop && openclaw browser start

2. CDP端口不通

Command failed: curl -s http://127.0.0.1:18800/json/version

解决方法:确保 OpenClaw browser 正在运行

3. 登录态丢失

页面显示扫码登录。 解决方法:手动扫码登录一次,cookie 持久化

文章特有

4. 配乐侧栏打不开

.action-Q1y01k 找不到或点不开。 解决方法:先用 evaluate 滚动到配乐区域,再 retry 5次点击

5. 配乐"使用"按钮不可见

.apply-btn-LUPP0D 被隐藏。 解决方法:用 evaluate 修改 display/visibility/opacity 三属性

图文特有

6. filechooser 超时

点击上传区域后没有触发文件选择弹窗。 解决方法

  • 确保先 waitForEvent('filechooser') 再 click
  • page.mouse.click(x, y) 代替 dispatchEvent
  • 点击位置选在手机预览区(phone-screen-emLY2d)中心偏下

7. 图片上传后预览无变化

上传成功但页面不显示图片。 解决方法:等4-5秒让页面渲染,检查"已添加X张图片"文本

8. 封面编辑器不返回

点「确定」后页面没有跳回图文编辑页。 解决方法:等2-3秒自动回跳,或用 page.goBack()

视频特有

9. 视频处理超时

上传后长时间不跳转到编辑页。 解决方法:等待15秒以上,视频越长时间越长

10. 不需要设置封面

解决方法:默认不操作封面区域,跳过即可


🐛 踩坑记录

| # | 坑 | 症状 | 解决 | |:--:|-----|------|------| | 1 | 图文草稿残留 | 打开编辑页显示旧内容 | URL 加 &_=时间戳 | | 2 | Slate 编辑器 innerHTML | 内容消失 | 用 keyboard.typeexecCommand('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)