批量的流程不是【把单篇流程复制 N 遍】,而是在一张任务表上同时管两条线:纵线是每一篇自己的生命周期(从待处理到已发布),横线是一整批的调度(谁先谁后、隔多久、失败怎么办)。把这两条线画清楚,批量的复杂度就从【N 倍】降回【一倍】。
4.1 讲清楚了【为什么要批量】和【批量的边界在哪】。本节是把那个边界落成一张表、一张状态图和一套节奏规则——它是本章后面六节的共同底座:4.3 的 Agent 往这张表里写【出稿】,4.4 的 RPA 负责把状态一格一格往前推。
一、先把批量的形状画出来
单篇的流程是一条直线:选题 → 出稿 → 排版 → 上传 → 发布。
批量的流程是一张网的纵截面——同一时刻,不同篇处在不同状态:
批次 B01(20 个主题)
├─ 已成稿 ████████████ 12 篇 → 进入排版队列
├─ 进行中 ███ 3 篇 → Agent 正在写
├─ 待处理 ████ 4 篇 → 等前一批腾出 Agent 额度
└─ 异常 █ 1 篇 → 需要人看一眼
看清这张图,批量的四个难点就全暴露了:
| 难点 | 在这张图里长什么样 | 本节的对策 |
|---|---|---|
| 状态记不住 | 你不知道哪篇在哪一格 | 一张任务表(第三节) |
| 谁先谁后 | 20 篇同时往外发? | 一张状态机 + 一套节奏规则(第四、六节) |
| 一篇坏了怎么办 | 整批卡住 | 失败三原则:跳过、重试、留痕(第五节) |
| 人和机器怎么交接 | 人不知道什么时候该看 | 两个固定的人工闸门(第七节) |
二、一张表:批量的底座
批量的最小可信单位不是【一篇】,而是一张表。 这张表就是整条产线的【事实来源】——机器只往里写,人只看它。
| 字段 | 说明 | 谁写 |
|---|---|---|
batch_id |
批次号,例如 B01 |
人 |
seq |
批内序号,从 1 开始 | 人 |
topic |
这一篇的主题 | 人(或 Agent 扩写后由人筛) |
status |
状态机的当前态 | 机器 |
md_path |
Agent 出的 Markdown 稿 | 机器 |
html_path |
排版后的富文本/HTML | 机器 |
cover_path |
封面图本地路径 | 机器 |
draft_url |
存进草稿箱后回读到的地址 | 机器 |
ai_flag |
是否标注【含 AI 生成内容】 | 默认真,人可复核 |
error |
失败原因(失败态必填) | 机器 |
schedule |
排定的发布时间 | 机器算、人确认 |
因为发布是有先后节奏的(第六节),而顺序必须可复现。如果只靠文件名的字母顺序,一次改名就会打乱排期。batch_id + seq 是这一篇的身份证,后面所有日志、排期、去重都靠它定位。
三、状态机:八个状态、几条路
状态机的价值不在于状态多,而在于它规定了什么是不合法的——不合法的状态转换,就是产线上出了事故。
┌──────────────► 已跳过(人不想要这篇)
│
待处理 ─► 出稿中 ─► 已出稿 ─► 已排版 ─► 已入草稿箱 ─► 已发布
│ │ │ │ │
└────────┴─────────┴────────┴──────────┴──────► 失败(可重试回上一态)
| 状态 | 含义 | 到了这里必须已经有 |
|---|---|---|
| 待处理 | 主题定了,还没动手 | (什么都没有) |
| 出稿中 | Agent 正在写 | (什么都没有) |
| 已出稿 | Markdown 稿拿到手 | md_path |
| 已排版 | 富文本准备好 | md_path、html_path |
| 已入草稿箱 | 字段全部填完,停在发布前 | 上两项 + cover_path、draft_url |
| 已发布 | 人点了发布 | 上四项 |
| 已跳过 | 人决定不发(撞车、质量不够、时效过了) | error 里写原因 |
| 失败 | 某一步报错 | error 里写原因 |
- 状态到了某一步,必备字段就必须齐。例如【已入草稿箱】却没有
cover_path——这不是小疏漏,这说明有人(或某个脚本)骗了这张表;一旦骗了表,后面的重试和续跑全部不可信; - 状态还没到,就不该有产物。例如状态还是【待处理】却已经有
html_path——说明有一步是跳着走的,很可能是上一个批次的残留文件被误认。 这两条反向规则,就是下面 Demo 里最能抓到东西的两张网。
四、批量的节奏:为什么必须【慢】
这是本章和第二章最不一样的地方。第二章只有一篇,不存在节奏问题;批量之后,节奏本身就是风控信号。
| 机器会做的事 | 平台看到的样子 | 结论 |
|---|---|---|
| 20 篇稿子 3 分钟内全部存进草稿箱 | 一个账号在 3 分钟里产出 20 篇草稿 | 异常 |
| 每篇间隔整齐的 30 秒 | 像定时程序 | 异常 |
| 20 篇标题句式高度一致 | 模板化内容 | 命中 2026-09-15 公告第一类 |
所以本章给批量定三条节奏规则:
- 单账号单日上限:不超过 6 篇(本节的示例值,真实值应由你自己在账号健康度上试出来,从更少开始);
- 两篇之间最小间隔:不少于 45 分钟——不要用整齐的固定间隔(那本身就是机器特征),在 45 到 90 分钟之间取随机;
- 发布落在活跃时段:早 7 到 9 点、午 12 到 13 点、晚 20 到 22 点——这是内容有效性的要求,也顺带让节奏看起来更像真人。
批量的目标不是【尽快发完】,而是【在账号还能承受的范围内,稳定地发下去】。 一个被限流的账号,批量能力等于零。所以批量的第一性原理是【可用性】,不是【效率】。
五、失败三原则:跳过、重试、留痕
批量一定会遇到失败:Agent 超时、接口限流、图片上传失败、页面元素没找到……批量的成败,全看你处理失败的方式。
| 原则 | 做法 | 反例 |
|---|---|---|
| 跳过 | 一篇失败 只改它自己的状态,其他篇继续往下走 | 一篇失败就 raise 整个循环——20 篇全部作废 |
| 重试 | 可重试的(网络抖动、超时)自动重试 2 到 3 次,中间退避等待 | 一失败就判死刑,白丢一篇稿 |
| 留痕 | 失败的写进 error 字段,批次结束时汇总打印 |
静默 except: pass——你不知道哪几篇没发 |
分界线很清楚:
- 可重试的失败(技术性):超时、限流、连接中断、元素加载慢;
- 必须人来处理的失败(判断性):内容本身不合格、主题撞车、账号权限不足。
技术性失败自动重试,判断性失败立刻转【失败】态并留给人。 这条界线,就是第一章反复讲的那句【机器管可确定的,人管要判断的】。
六、动手 Demo:给一张任务表做体检,并排出发布节奏
下面这段代码只用 Python 标准库。它做三件事:体检任务表(用第三节那些反向规则)、统计状态分布、排出可发布篇目的时间表。
# -*- coding: utf-8 -*-
"""4.2 Demo:批量任务表体检 + 排期器(纯标准库,可直接跑)"""
import re
from datetime import datetime, timedelta
# ---- 状态机:只允许这几态;箭头表示允许的推进方向 ----
STATES = ["待处理", "出稿中", "已出稿", "已排版", "已入草稿箱", "已发布", "已跳过", "失败"]
ORDER = {s: i for i, s in enumerate(STATES)}
# 到达这些状态,必须已经存在哪些文件/字段
NEED = {
"已出稿": ["md_path"],
"已排版": ["md_path", "html_path"],
"已入草稿箱": ["md_path", "html_path", "cover_path", "draft_url"],
"已发布": ["md_path", "html_path", "cover_path", "draft_url"],
}
# 一天里适合发布的时段(头条用户的活跃高峰)
SLOTS = [(7, 9), (12, 13), (20, 22)]
MIN_GAP_MIN = 45 # 同一账号两篇之间至少隔 45 分钟
MAX_PER_DAY = 6 # 单账号一天最多发 6 篇
# ---- 待体检的任务表(batch_id, seq, topic, status, md, html, cover, draft_url, ai_flag)----
ROWS = [
("B01", 1, "玄关收纳三件套", "已发布", "a.md", "a.html", "a.jpg", "https://mp/1", True),
("B01", 2, "厨房台面收纳", "已入草稿箱", "b.md", "b.html", "b.jpg", "https://mp/2", True),
("B01", 3, "玄关收纳 三件套", "已出稿", "c.md", "", "", "", True),
("B01", 4, "阳台改造小成本", "出稿中", "", "", "", "", False),
("B01", 5, "衣柜换季整理", "已排版", "e.md", "e.html", "e.jpg", "", True),
("B01", 6, "浴室镜柜收纳", "已入草稿箱", "f.md", "f.html", "", "https://mp/6", True),
("B01", 7, "书桌线缆整理", "已发布", "g.md", "g.html", "g.jpg", "https://mp/7", True),
("B01", 8, "厨房台面收纳", "待处理", "", "", "", "", True),
("B01", 9, "玄关收纳三件套", "待处理", "", "", "", "", True),
("B01", 10, "旧物改造花瓶", "已跳过", "", "", "", "", True),
("B02", 1, "端午粽子做法", "已出稿", "k.md", "", "", "", True),
("B02", 2, "山楂陈皮茶", "失败", "", "", "", "", True),
("B02", 3, "夏季凉拌菜", "已排版", "m.md", "m.html", "m.jpg", "", True),
("B02", 4, "冰镇酸梅汤", "已入草稿箱", "n.md", "n.html", "n.jpg", "https://mp/14", True),
]
problems = []
def norm(t):
"""主题归一化:去空格、统一全半角,用来查重"""
return re.sub(r"\s+", "", t).replace(" ", "")
# --- 检查 1:状态合法性 + 必备字段 ---
for r in ROWS:
bid, seq, topic, st, md, html, cover, url, ai = r
tag = "%s-%02d" % (bid, seq)
if st not in ORDER:
problems.append((tag, "状态【%s】不在状态机里" % st))
continue
have = {"md_path": md, "html_path": html, "cover_path": cover, "draft_url": url}
for need in NEED.get(st, []):
if not have.get(need):
problems.append((tag, "状态已是【%s】,但缺 %s" % (st, need)))
# 反向检查:还没到【已出稿】的,不该有文件
if ORDER[st] < ORDER["已出稿"] and (md or html or cover or url):
problems.append((tag, "状态还是【%s】,却已经有产物文件" % st))
# 检查 2:AI 声明标记(本章所有内容都是 AI 参与的,必须为真)
if st not in ("已跳过",) and not ai:
problems.append((tag, "ai_flag 为假——本章内容必须标注【含 AI 生成内容】"))
# --- 检查 3:主题重复(含同一批次内与跨批次)---
seen = {}
for r in ROWS:
bid, seq, topic = r[0], r[1], r[2]
k = norm(topic)
tag = "%s-%02d" % (bid, seq)
if k in seen:
problems.append((tag, "主题与 %s 重复:【%s】" % (seen[k], topic)))
else:
seen[k] = tag
# --- 检查 4:相似主题检测(首尾两字相同视为高危近邻)---
keys = list(seen.keys())
for i in range(len(keys)):
for j in range(i + 1, len(keys)):
a, b = keys[i], keys[j]
if a != b and len(a) >= 3 and len(b) >= 3 and a[:2] == b[:2] and a[-1] == b[-1]:
problems.append((seen[a] + " / " + seen[b], "主题过于接近:【%s】vs【%s】" % (a, b)))
# --- 检查 5:一天能发多少篇 ---
ready = [r for r in ROWS if r[3] in ("已入草稿箱",)]
if len(ready) > MAX_PER_DAY:
problems.append(("B01", "待发布 %d 篇 > 单账号单日上限 %d 篇" % (len(ready), MAX_PER_DAY)))
# --- 排期:把【已入草稿箱】的按活跃时段 + 最小间隔排开 ---
def slots_today(day=datetime(2026, 10, 9)):
out = []
for h1, h2 in SLOTS:
t = day.replace(hour=h1, minute=0)
end = day.replace(hour=h2, minute=0)
while t < end:
out.append(t)
t += timedelta(minutes=MIN_GAP_MIN)
return out
free = slots_today()
plan, cursor = [], 0
for r in ready:
if cursor < len(free):
plan.append((r[0] + "-%02d" % r[1], r[2], free[cursor].strftime("%H:%M")))
cursor += 1
else:
plan.append((r[0] + "-%02d" % r[1], r[2], "今日无空位,顺延明日"))
print("=" * 64)
print("一、任务表体检(共 %d 行)" % len(ROWS))
print("=" * 64)
if problems:
for tag, msg in problems:
print(" [!] %-12s %s" % (tag, msg))
else:
print(" [OK] 全部通过")
print(" 问题数 = %d" % len(problems))
print()
print("=" * 64)
print("二、状态分布")
print("=" * 64)
for s in STATES:
n = len([r for r in ROWS if r[3] == s])
if n:
print(" %-10s %d 篇" % (s, n))
print()
print("=" * 64)
print("三、可发布篇目排期(间隔 >= %d 分钟,落在活跃时段)" % MIN_GAP_MIN)
print("=" * 64)
for tag, topic, when in plan:
print(" %-8s %-6s %s" % (tag, when, topic))
print(" 已排 %d 篇 / 今日空位 %d 个" % (len(plan), len(free)))
实际运行输出(真跑,不是示意):
================================================================
一、任务表体检(共 14 行)
================================================================
[!] B01-04 ai_flag 为假——本章内容必须标注【含 AI 生成内容】
[!] B01-06 状态已是【已入草稿箱】,但缺 cover_path
[!] B01-03 主题与 B01-01 重复:【玄关收纳 三件套】
[!] B01-08 主题与 B01-02 重复:【厨房台面收纳】
[!] B01-09 主题与 B01-01 重复:【玄关收纳三件套】
问题数 = 5
================================================================
二、状态分布
================================================================
待处理 2 篇
出稿中 1 篇
已出稿 2 篇
已排版 2 篇
已入草稿箱 3 篇
已发布 2 篇
已跳过 1 篇
失败 1 篇
================================================================
三、可发布篇目排期(间隔 >= 45 分钟,落在活跃时段)
================================================================
B01-02 07:00 厨房台面收纳
B01-06 07:45 浴室镜柜收纳
B02-04 08:30 冰镇酸梅汤
已排 3 篇 / 今日空位 8 个
读结果,四个值得注意的地方:
B01-03是本节最值钱的一条:它的主题是【玄关收纳 三件套】,和B01-01的【玄关收纳三件套】只差一个空格。这说明查重必须先做归一化——去掉空格、统一全半角,然后再比。批量生产里最常见的偷懒,就是【改动一个空格、一个标点,然后声称这是新主题】;而对平台来说,这就是同质内容;B01-06是【反向规则】抓到的那条:状态已经是【已入草稿箱】却没有封面——按第 3 章的经验,没有封面的稿子根本走不到这一步,所以这条记录说明表被写脏了。脏表比空表更危险,因为它会让后续的重试逻辑基于错误前提运行;B01-04是【合规前置】的价值:它的ai_flag是假。注意这条是在【出稿中】这一态就被抓出来的——合规检查要尽量往前放,等到上传那一步再发现,前面几步的机器工时全部白花;- 排期只排出了 3 篇,因为只有 3 篇真的到了【已入草稿箱】。这就是状态机的好处:排期器不需要判断【哪些能发】,它只需要读状态。状态是唯一的事实来源。
体检器没有【自动修好】任何一条问题,它只报告。因为这些问题里有判断性的(主题撞车——要不要合并?还是换一个角度?),机器改了就是替人做决定。报告给机器,决定给人——从第二章到这一章,这条界线一直没变。
七、两个人工闸门:人只需要看两个地方
批量的流程里,如果人需要盯全程,那就不是自动化。所以本节只留两个闸门:
| 闸门 | 位置 | 人要看什么 | 机器不能替代的原因 |
|---|---|---|---|
| 闸门一:出稿抽检 | 【已出稿】之后、【已排版】之前 | 抽 2 到 3 篇通读:有没有事实错误、有没有 AI 味、角度重复不重复 | 内容质量是判断,不是判定 |
| 闸门二:发布确认 | 【已入草稿箱】之后 | 逐篇看一眼预览就点发布(或按排期点) | 合规责任 + 信用分不可逆(4.1 第六节红线三) |
两个闸门之外的每一步,都不需要人。 这一条是判断一个批量方案好不好的唯一标准:如果你每天要在流程中间看十次,那这个方案只是在给你换一种班。
八、⚠️ 2026 现状:本节设计的四处依据来自官方公告
- 节奏规则来自风控现实:头条 2026 年上半年的水军治理公告里,被处置的账号特征是【批量化发布】【节奏模板化】【行为高度集中】。所以本节的【最小间隔 + 单日上限 + 随机抖动】不是洁癖,是照着风控特征反着写;
- 查重规则来自原创规则:头条规定同一篇文章在同矩阵/同 MCN/同作者的多个账号上只允许勾选原创一次——所以任务表的去重必须在【出稿之前】做,等到勾原创那一步才发现撞车,这篇就废了;
- AI 声明字段是法规要求:《人工智能生成合成内容标识办法》(2025-09-01 施行)+ 头条的落地要求(图文在标题下方或正文开头标注)。所以
ai_flag不是可选项,它是任务表里的一个必填字段; - 字段清单可能随时变:头条后台的字段名与分类名会改版。所以任务表的字段要按【语义】命名(
cover_path),不要按【界面上的文字】命名(封面图2)——界面变了,表不用改。
30 秒实验
把本节 Demo 里的 MIN_GAP_MIN 从 45 改成 1,再跑一次。
会发生什么:
- 程序不会报任何错——因为节流规则不是【校验】,是【生成规则】。体检器根本不检查它的值;
- 排期结果会从 3 篇变成……还是 3 篇(篇数不变,只是时间全部挤到 07:00、07:01、07:02)。
结论:排期器保证了【不撞在同一分钟】,但没有保证【像人】。 间隔值定多少,是个需要人判断的业务选择,不是一个能被程序自动判定的正确性。
这又是一条【机器管可判定的、人管要判断的】的实例——它在本门课里已经出现第三次了:第二章的字段、第三章的样式、本章的节奏。
常见误区
- 【批量就是把单篇流程复制 N 遍】——错。 复制的只是每一步的动作;批量真正新增的是一张任务表、一张状态机、一套节奏规则、一套失败处理。这四样东西没有,批量到第 5 篇就会乱。
- 【主题去重就是比字符串是否相等】——错。 本节 Demo 里的
B01-03就是【加了一个空格】的同一个主题。查重必须先归一化(去空格、统一全半角),否则漏报率极高。 - 【有了状态表,就可以让它自动流转到发布】——错。 状态机的价值正是在【已入草稿箱】和【已发布】之间留下一个人工闸门。自动跨过这一步,等于把信用分风险交给脚本承担。
- 【失败就整批停,最安全】——错。 整批停等于一次网络抖动废掉一批稿。正确做法是跳过这一篇、记录原因、继续下一篇,批次结束时汇总。可重试的自动重试,判断性的留给人。
- 【节奏慢一点只是效率损失】——错。 在头条这类平台,节奏本身就是风控信号。整齐、密集、快速的批量操作,正好落在平台治理【批量化发布】【模板化】的描述里。节奏不是效率问题,是可用性问题。
自测三问(点开对答案)
1. **为什么说批量的流程是【两条线】而不是【N 条线】?** 因为有两条正交的线:**纵线**是**每一篇自己的生命周期**(待处理 → 出稿中 → 已出稿 → 已排版 → 已入草稿箱 → 已发布),由**状态机**管;**横线**是**整批的调度**(谁先谁后、隔多久、单日发几篇),由**节奏规则**管。**这两条线各管一件事,互不干扰**——所以无论 N 是 10 还是 100,复杂度都是【一张表 + 一张状态图 + 一套节奏】,不会随 N 增长。 2. **状态机的【反向规则】为什么比正向规则更有用?** 正向规则是【到了这一步必须有这些字段】——它抓的是**漏做**;**反向规则是【还没到这一步就不该有这些东西】——它抓的是【表被写脏】**。例如状态还是【待处理】却已经有排版产物,很可能是**上一个批次的残留文件被误认**,或者某个脚本跳着走了一步。**脏表比空表危险得多**:后面的重试、续跑、排期全部建立在【状态可信】这个前提上,**前提一破,所有自动化都在错上加错。** 3. **本节的批量方案里,人一共只需要在几个地方出现?为什么?** **两个**:① **出稿抽检**(【已出稿】之后,抽 2 到 3 篇通读);② **发布确认**(【已入草稿箱】之后,逐篇点发布)。**理由是这两步都是【判断】而不是【判定】**——抽检要判断内容质量和角度是否重复(机器判不了),发布要承担合规责任与信用分风险(机器担不了)。**判据很简单:如果需要人在流程中间出现十次,那这个方案没有实现自动化,只是换了一种班。**- ☐ 下一节(4.3.1 创建文心智能体的方式)
相关笔记
- 小红书运营增效助手工作流程设计 —— 单篇版的任务表与人工闸门,本章是它的批量版
- 公众号创作智能助手整体流程设计 —— 上一章的流程总图,读的时候对照【批】这个维度
- 工作流编排 —— 【需要确定的地方用代码】的出处,本节的状态机就是它的落地
- 今日头条批量发布助手场景概述 —— 节奏规则与红线三的出处
- 🤖 Agent+RPA实战 · 索引 —— 全课路线图