抖音评论自动回复机器人部署实践:无头 Chrome + DeepSeek + NapCat(2C2G 云主机)

目录

一、为什么不用官方评论接口

二、整体架构

三、服务器与基础环境

四、评论采集与去重

五、风控与回复策略

六、楼中楼的正确回法

[七、QQ 通知](#七、QQ 通知)

[八、常驻运行与 2G 内存错峰](#八、常驻运行与 2G 内存错峰)

九、实际运行效果

十、踩坑记录

十一、安全与维护

十二、小结


本文面向需要在自有云主机上运行抖音评论自动回复的开发者,解决个人开发者拿不到官方 item.comment 权限、又不想维护小程序审核链路的问题。方案基于无头 Chrome 读取自己作品下的评论,用页面上下文接口回评,DeepSeek 生成回复,NapCat 把异常和结果推到 QQ。

一、为什么不用官方评论接口

抖音开放平台的 item.comment 权限要求应用具备企业或站点资质,个人开发者通常无法创建网站应用。小程序方案虽然能拿到 ma.item.comment,但需要额外的审核、包体和账号体系,维护成本远高于一个自用机器人。

在"只处理自己作品、回复量不大、可接受人工处理滑块"的前提下,浏览器自动化是更现实的路径:

  • 用无头 Chrome 登录网页版,读取作品评论和楼中楼;
  • 在页面上下文里调用评论接口,复用页面已有的签名;
  • 用本地 SQLite 做去重、配额和审计;
  • 遇到滑块验证立即停止写操作,转人工处理。

代价是依赖网页结构、需要维护登录态,并且平台风控会不定期触发验证。这一点会在后文的踩坑部分展开。

二、整体架构

整条链路分为六块:

  1. douyin-runner:systemd 常驻,默认 180 秒轮询一次,负责调度采集、筛选、生成和发送。
  2. dsh-douyin-watch:浏览器自动化插件,提供 work-comments.mjs、comment-api.mjs、comment-delete.mjs 等命令行工具,Chrome 通过 CDP 9334 暴露。
  3. SQLite + triage:保存作品、评论、回复记录,按 comment_id 去重,并过滤自己发的、已回过的、观众互聊的评论。
  4. DeepSeek:只根据评论上下文生成 10--40 字回复,失败时走固定模板。
  5. NapCat:本地 OneBot HTTP 服务,负责把通知发到指定的 QQ。
  6. dsh-web + Remotion:预留给视频生成和渲染,评论任务和渲染任务错峰使用同一台 2C2G 机器。

数据流是:网页评论 → work-comments → SQLite → triage → DeepSeek → comment-api → 回评;异常、高风险评论和发送结果另走 NapCat → QQ。

三、服务器与基础环境

测试环境为阿里云 2 vCPU / 2 GB 内存 / 40 GB 磁盘,Ubuntu 24.04。2 GB 内存跑 Chrome 比较紧,先补 2 GB swap。

bash 复制代码
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab

安装 Node.js 24、Chrome、Docker、中文字体和 ffmpeg:

bash 复制代码
curl -fsSL https://deb.nodesource.com/setup_24.x | bash -
apt-get install -y nodejs google-chrome-stable docker.io ffmpeg fonts-noto-cjk

NapCat 使用 Docker 运行,只监听本机端口:

bash 复制代码
docker run -d --name napcat-douyin \
  -e ACCOUNT=<QQ号> \
  -p 127.0.0.1:5701:5700 \
  -p 127.0.0.1:6100:6099 \
  -v /opt/napcat-douyin/config:/app/napcat/config \
  -v /opt/napcat-douyin/qq:/app/.config/QQ \
  --restart unless-stopped \
  mlikiowa/napcat-docker:latest

QQ 登录需要扫码,登录态保存在挂载目录里。容器重启后通常可以自动恢复,但会话失效时仍要重新扫码。

四、评论采集与去重

评论由 work-comments.mjs 采集。它会在已登录的页面里读取作品列表、根评论和每条根评论下的回复,并返回结构化的 JSON。核心字段包括 cid、uid、nick、text、create_time,以及"我是否已经回过"的判断结果。

本地用 SQLite 保存三类数据:

sql 复制代码
CREATE TABLE comments (
    cid TEXT PRIMARY KEY,
    aweme_id TEXT,
    root_cid TEXT,
    parent_cid TEXT,
    depth INTEGER DEFAULT 0,
    uid TEXT,
    nick TEXT,
    text TEXT,
    create_time INTEGER DEFAULT 0,
    answered_by_me INTEGER DEFAULT 0,
    risk TEXT DEFAULT '',
    seen_at TEXT
);

CREATE TABLE replies (
    target_cid TEXT PRIMARY KEY,
    aweme_id TEXT,
    root_cid TEXT,
    target_uid TEXT,
    target_nick TEXT,
    text TEXT,
    our_cid TEXT,
    created_at TEXT,
    status TEXT DEFAULT 'sent'
);

cid 是主键,天然去重。即使某次采集重复返回同一条评论,也只会更新状态,不会重复回复。

候选评论的筛选条件如下:

sql 复制代码
SELECT c.* FROM comments c
WHERE c.answered_by_me = 0
  AND c.risk = ''
  AND c.uid != :own_uid
  AND NOT EXISTS (SELECT 1 FROM replies r WHERE r.target_cid = c.cid)
ORDER BY c.create_time ASC;

这里有三层保护:不回复自己、不重复回复、不碰已标记风险的评论。楼中楼还会额外判断父评论或根评论是否属于自己,避免在观众之间的对话里插话。

五、风控与回复策略

回复策略以"短、具体、不引流"为原则。发送前会做一次清洗:

  • 去掉 URL;
  • 去掉 @,避免误触发提醒;
  • 压缩空白字符;
  • 超过 60 字直接截断;
  • 生成失败时使用固定模板兜底。

限额只按用户计算:同一用户 24 小时内最多 100 条,不设全局上限和小时上限。查询逻辑如下:

python 复制代码
def per_user_count(conn, uid, hours=24):
    cutoff = (datetime.now(timezone.utc) - timedelta(hours=hours)).isoformat(timespec="seconds")
    row = conn.execute(
        "SELECT COUNT(*) AS n FROM replies WHERE target_uid=? AND created_at>=?",
        (str(uid), cutoff),
    ).fetchone()
    return int(row["n"] if row else 0)

发送间隔随机 3--8 秒,避免在短时间内形成明显的固定节奏。评论内容命中广告、引战、敏感词等规则时只通知、不自动回复。

六、楼中楼的正确回法

楼中楼最容易踩的坑是评论 ID 用错。接口需要两个 ID:

  • reply_id:根评论的 cid;
  • reply_to_reply_id:真正要回复的那条子评论的 cid。

如果两个参数都填子评论 cid,接口可能返回成功,但服务端不会把回复挂到目标位置。桥接层按下面的方式组装参数:

python 复制代码
args = ["--aweme", aweme_id, "--file", text_file]
if root_cid:
    args.extend(["--reply", root_cid])
if target_cid and target_cid != root_cid:
    args.extend(["--reply-to-reply", target_cid])

发送时在页面上下文里执行 fetch,复用页面已有的签名和 Cookie,不直接调用外部 HTTP 接口。下面的截图来自创作者中心评论管理页,展示了根评论、一级回复和楼中楼三层结构(昵称和头像已打码):

七、QQ 通知

通知走新服务器本地的 NapCat,不和 B 站机器人共用 QQ。Python 侧只需要向 127.0.0.1:5701 发一个 send_private_msg:

python 复制代码
body = json.dumps({
    "user_id": int(target_qq),
    "message": [{"type": "text", "data": {"text": text[:1500]}}],
}, ensure_ascii=False).encode("utf-8")

req = request.Request(
    "http://127.0.0.1:5701/send_private_msg",
    data=body,
    headers={"Authorization": f"Bearer {onebot_token}", "Content-Type": "application/json"},
    method="POST",
)

OneBot 端口和 WebUI 端口都只绑定 127.0.0.1,通过 SSH 隧道访问,不直接暴露公网。Token 放在 .env 里,权限设为 600,日志里不打印 Token 和 Cookie。

八、常驻运行与 2G 内存错峰

douyin-runner 用 systemd 管理,限制内存并降低调度优先级:

ini 复制代码
[Service]
Type=simple
User=douyin
WorkingDirectory=/opt/douyin-operator
EnvironmentFile=/opt/douyin-operator/.env
ExecStart=/usr/bin/python3 /opt/douyin-operator/scripts/runner.py --interval 180 --notify-risk
Restart=on-failure
RestartPreventExitStatus=2
MemoryHigh=800M
MemoryMax=1000M
Nice=10

RestartPreventExitStatus=2 用于滑块验证:脚本以状态码 2 退出后,systemd 不会无限重启。

2 GB 内存同时跑 Chrome、NapCat、dsh-web 和渲染任务会非常紧张,因此评论和渲染必须错峰:

渲染前停止评论和网页相关服务,只保留必要的系统进程:

bash 复制代码
systemctl stop douyin-runner dsh-web
pkill -f work-comments.mjs
pkill -f '/opt/google/chrome/chrome'

渲染时固定单并发:

bash 复制代码
npx remotion render <入口文件> <输出文件> --concurrency 1

渲染完成后再依次启动 dsh-web、Chrome 和 douyin-runner。评论不会丢,暂停期间的评论会在恢复后按轮询补回,只是通知会延迟。

九、实际运行效果

创作者中心内容管理页可以看到已发布作品和播放、评论、收藏等数据。下面的截图来自真实环境,账号信息已打码:

评论管理页可以按作品查看评论、回复和删除。根评论、一级回复、楼中楼都会在同一页面展示,方便核对机器人是否回复到了正确的位置。

十、踩坑记录

  1. 无头 Chrome 的 UA 带 HeadlessChrome :评论写接口会返回 200 和空响应,看起来成功、实际没发出去。发送前需要把 UA 覆盖为普通 Chrome。
  2. comments: null 被当成失败 :没有评论时接口返回 null,早期脚本会报错退出。现在把 total = 0 视为正常空列表。
  3. Node 进程不退出 :部分工具执行完后仍保持 CDP 连接,导致轮询挂起。工具结束时应显式 process.exit。
  4. 楼中楼 ID 用错 :两个参数都填子评论 cid 时,接口返回成功但服务端不落点。必须分别传根评论和子评论 ID。
  5. 2 GB 内存下同时加载多个重页面:Creator Center 和视频页同时打开会让内存和 CPU 迅速打满,SSH 也可能短暂无响应。做法是一次只加载一个页面,先停掉 dsh-web,再重启 Chrome。
  6. 滑块验证 :work-comments.mjs 会返回 verify_required。此时不要继续自动回复,切换到有头浏览器人工完成验证,再恢复轮询。
  7. Cookie 过期 :网页登录态失效后所有采集都会失败。Cookie 文件按密码级凭据管理,权限 600,不要提交到 Git。

十一、安全与维护

  • 评论 Cookie、OneBot Token、DeepSeek Key 全部放在 .env,文件权限 600;
  • 所有管理端口只监听 127.0.0.1,通过 SSH 隧道访问;
  • 发送前做内容清洗,不输出 URL、不 @ 任何人;
  • 同一用户 24 小时最多 100 条,不设全局上限;
  • 高风险评论只通知、不自动回复;
  • 定期备份 douyin.db,并检查 operator.log 中是否有连续失败;
  • 平台风控策略会变化,浏览器自动化方案需要持续维护。

十二、小结

这套方案的核心不是"绕过接口权限",而是把官方接口不可用的部分换成可控的浏览器自动化,再用 SQLite、triage 和限额把风险收住。2C2G 的机器可以跑通评论链路,但必须把渲染和评论错峰,并接受滑块验证需要人工处理一次的现实。

标签:抖音 Chrome CDP DeepSeek NapCat SQLite 自动化

背景说明:本文基于自建抖音账号的评论回复实践整理,截图中的昵称、头像和账号信息已做打码处理;公网 IP、QQ 号和 Token 已隐去,请替换为自己的环境。

相关推荐
Java爱好狂.1 小时前
阿里1658页Java面试题总结(含答案)
java·后端·高并发·架构师·java面试·java面试题·java八股文
Thanks_ks1 小时前
从 0.1+0.2 不等于 0.3 说起:聊聊浮点数精度问题
java·python·数值计算·精度丢失·bigdecimal·浮点数·ieee754
(●—●)橘子……1 小时前
力扣 第525场周赛 练习理解
数据结构·python·学习·算法·leetcode
海鸥-w1 小时前
区域名称同步存储实现笔记
java·笔记
镜子AI1 小时前
镜子AI工程实践:用 Python 自动生成 GEO 问答语料与意图覆盖矩阵(零依赖)
人工智能·python·矩阵
lily5227831 小时前
工具池与分发
前端·chrome
长沙三为智能科技1 小时前
多平台内容矩阵怎么配合GEO:4层分工与3条去重规则
python·aigc·geo优化
ycydynq1 小时前
django 添加信息时,对外键设置条件
数据库·python·django
Rain的Java大神之路1 小时前
LeetCode 21 合并两个有序链表:一道 Easy 题,凭什么刷掉一半候选人?
java·后端·算法