🤖 第五章 应用实战:知乎图文创作和发布助手
📄 一句话概括

出口这一步,知乎是四章里路最多的平台:三路形态(文章/回答/想法)各有一套操作,还多一条【创作声明】必须设。 所以本节的设计分两块——第一块是【路由】(这一篇走哪条路),第二块是【预检】,而预检要先分【发不出去】和【发出去出事】两大类。

📝 本节和前面几节的关系

5.4.1 取回了稿子、5.4.2 排好版,本节把稿子送进知乎编辑器、填好字段、设好声明,然后停下。 这一步的出入口形状与第二章(小红书)相同——都是点界面;但知乎多了三样东西:三路形态、创作声明、以及一套明确写进公告的处置梯度。

一、先把那条硬约束再说一遍(工程含义)

5.1.1 已经讲过结论,这里说它对这一节的工程含义:

事实 对脚本的含义
知乎开放平台只有只读能力(话题、热榜、知识库检索) 发布环节没有接口可调,只能走界面自动化
《开发者协议》禁止写入类操作 不要试图用非公开接口——那不只是技术问题
登录态有一定有效期(依赖多重票据) 脚本要能识别【已掉线】并停下来,不要对着登录页硬点
《用户协议》禁止用自动化工具访问或干扰网站 节奏必须像人——见下面第二条

第二条要单独说:节奏

这一条与 4.x 那章的【批量】不同——本章的批量小,但节奏问题依然在:

⚠️ 【像人一样操作】这句话,在这一章有两层意思

第一层是技术性的:间隔够长、不并发、不绕过验证码。 第二层是内容性的:这正是全章一直在讲的那件事——内容要有人味。 两层其实是同一件事:知乎这个平台的治理逻辑,是围绕【真人真内容】建的(5.1.1 现状三:判定看文本特征)。所以它既不容忍【机器批量发内容】,也不容忍【机器批量做操作】。 这不是两条规则,是同一条。

二、第一件事:路由——这一篇走哪条路

这是本节最核心的设计,也是知乎独有的。 三路形态互不通用,RPA 必须先读任务表的【形态】字段,再选操作序列。

文章 回答 想法
入口 创作中心 → 写文章(zhuanlan.zhihu.com/write) 打开一个具体问题 → 写回答 创作中心 → 发想法
必填 标题、正文 问题(问题链接)、正文 正文
封面 有 无 无
话题 有(≤5 个) 有(≤5 个) 有(≤5 个)
定时发布 支持 不支持 不支持
内容长度 长文 中等(可长可短) 短内容

这张表里有一条特别值得记:官方定时发布只支持【文章】这一种形态——回答和想法目前必须即时发出。

这个细节的意义在于:它把【排期】这件事限制在了一条路上。 如果你想做【晚上十点发】这种节奏控制,只有文章能用定时;回答和想法要么现在发,要么先存草稿、到时候再让脚本点发布——后者要额外设计一个【定时唤醒】的环节。

路由的具体做法(脚本层面):

读任务表的【形态】字段
   ├─ 文章 → 走【写文章】序列:填标题 → 正文 → 封面 → 话题 → 声明
   ├─ 回答 → 走【回答问题】序列:打开 qurl → 写回答 → 话题 → 声明
   └─ 想法 → 走【发想法】序列:正文 → 话题 → 声明

注意每条序列的最后都是【声明】——因为它是三路共同的必做项(下一节就是它)。

📝 为什么【路由】要写成显式的第一步,而不是在脚本里 if 判断

因为三路的不只是【操作位置】不同,【字段集合】也不同:文章有标题和封面,回答有问题链接,想法两样都没有。 如果不在最前面把路由定下来,后面每一个字段都要写一次 if——而且很容易漏(比如回答那一路忘了必填问题链接,脚本跑到最后才发现无处可填)。 更好的做法:路由决定之后,生成一张【这一篇要填哪些格子】的清单,后面的动作照着清单走。这与 5.2 那张任务表是同一个思路——先把要做的列清楚,再动手。

三、第二件事:填字段(三路的清单)

步 文章 回答 想法
1 打开编辑器 打开问题页 → 写回答 打开想法编辑器
2 填标题(≤80 字符) —— ——
3 粘正文(5.4.2 的富文本) 粘正文 粘正文
4 上传封面(690×260 或 800×450) —— ——
5 上传正文图(走知乎图床) 同 同
6 选话题(1~5 个,别多) 同 同
7 设创作声明 同 同
8 停在【发布】前 同 同

这张清单里,第 5 步和第 7 步是【容易出事的两个点】:一个关系图片能不能显示,一个关系合规。下面各说一句。

四、第三件事:创作声明——知乎独有的一步

它是本节最不能漏的一步,而且它有自己的【位置】和【形态】。

项 说明
位置 编辑器底部的【发布设置】区域里,有一个【创作声明】下拉框
默认值 无声明
六档可选 无声明 / 包含剧透 / 包含医疗建议 / 虚构创作 / 包含理财内容 / 包含 AI 辅助创作(作者对内容负责)
时机 必须在发布前设——如果发布时漏了,要回头编辑已发布的内容补设(多一步麻烦,也多一次风控记录)

RPA 的动作是固定三步:点开下拉框 → 选中那一档 → 确认框里的文字真的变了(这一步就是【回读校验】,与 5.4.2 的第 ⑥ 步同一个原则)。

⚠️ 那一档的完整名字,值得念一遍

它不叫【含 AI 生成】,它叫——【包含 AI 辅助创作,作者对内容负责】。 后半句是重点。 它不是免责声明,它是责任声明:你标了它,等于你承诺【我对这篇内容的准确性负责】。 所以声明不是【把责任推给平台】的动作,是【把责任接过来】的动作——这也解释了为什么它必须由人设,不能由脚本默认。

五、第四件事:图床

这一条与 5.4.2 的结论一字不差,这里只说 RPA 的动作:

① 正文图先存到本机
        ↓
② 在知乎编辑器里用【本机文件路径】上传(让它自己转存到知乎图床)
        ↓
③ 上传完成后回读一遍:图真的在编辑器里显示出来了吗
        ↓
④ 如果用的是墨滴图床的地址 → 换成知乎图床的地址再发

第 ③ 步不能省——图片是【异步上传】的,点了上传不等于传完了。回读是唯一可靠的确认方式。

六、动手 Demo:发布前预检(知乎版)

它是本节真正属于你自己的那部分代码,也是全章最后一道关。

它的结构比 4.4.4 更清楚一层——先分两大类:

组 含义 处理方式
A 组 会让这一篇【发不出去】的(字段/格式不对) 技术故障——改完就能发,顺序无所谓
B 组 能发出去、但会【被处置】的 风险——平台按最重的那个处置,所以必须从重往轻修
账号级 单篇看不出、连着发才出事的 单独列,因为它防的是【这一串】不是【这一篇】

B 组的排序依据,就是知乎官方公告里那五档:

删除  >  折叠  >  减少分发  >  建议修改  >  标识
(重)                                        (轻)
# -*- coding: utf-8 -*-
"""5.4.3 Demo:发布前预检(知乎版)—— 先分【发不出去】和【发出去出事】,再按处置档位排序(纯标准库)"""
import re
import statistics

# 每篇:形态 / 标题 / 问题链接 / 正文 / ai参与 / 创作声明 / 有亲历素材 / 是否本机图
DOCS = [
    {"id": "P1", "form": "回答", "title": "", "qurl": "https://www.zhihu.com/question/123456",
     "ai": True, "declare": "包含 AI 辅助创作", "src": True, "localimg": True},
    {"id": "P2", "form": "回答", "title": "", "qurl": "https://www.zhihu.com/question/123456",
     "ai": True, "declare": "无声明", "src": False, "localimg": True},
    {"id": "P3", "form": "想法", "title": "", "qurl": "",
     "ai": True, "declare": "包含 AI 辅助创作", "src": True, "localimg": True},
    {"id": "P4", "form": "回答", "title": "", "qurl": "",
     "ai": True, "declare": "包含 AI 辅助创作", "src": True, "localimg": True},
]

BODIES = {
    "P1": "先说预算,我给三档,都是我实际花过的数。第一档二百元以内:窄边换鞋凳一百二,壁挂钥匙盘三十,"
          "折叠收纳箱五十,加起来两百,我家玄关就这一档。第二档五百元左右:把换鞋凳换成一米二宽的鞋柜,"
          "约四百,再配一个可视收纳箱八十。第三档一千五左右:做嵌入式玄关柜,柜体一千二,加装感应灯带三百。"
          "说实话我觉得没必要上第三档。我家门后只有二十厘米,一体式根本塞不进去。"
          "真要花钱,我建议先花二十块买个卷尺,量准三个尺寸:门后到墙、玄关进深、开门半径。",
    "P2": "据某机构统计,87% 的家庭都存在收纳困扰,这说明收纳是一个普遍需求。"
          "我朋友在杭州买的那套房子我亲眼见过,玄关做得非常漂亮,所以我总结了三档预算方案。"
          "首先,我们需要明确基本原则。合理的分类能够让物品各归其位。"
          "其次,我们要选择适合的收纳工具。收纳盒是常见的选择。"
          "第三档一千五左右:做嵌入式玄关柜,柜体一千二,加装感应灯带三百。"
          "综上,预算规划是收纳改造的第一步,建议先明确需求再选择工具。",
    "P3": "今天把玄关清空了,只留换鞋凳和钥匙盘。",
    "P4": "第一档二百元以内:换鞋凳一百二,钥匙盘三十,收纳箱五十。第二档五百元:窄边鞋柜四百,"
          "收纳箱八十。第三档一千五:嵌入式玄关柜一千二,加感应灯带三百。我建议先量尺寸再下单。",
}

CONNECT = ["首先", "其次", "再次", "最后", "综上所述", "总之", "综上"]

# 处置档位(从重到轻;知乎官方公告里的五档)
ORDER = ["删除", "折叠", "减少分发", "建议修改", "标识"]

def analyze(d):
    """返回 (A 组: 发不出去的问题, B 组: [(档位, 问题)], 账号级)"""
    A, B, ACCT = [], [], []
    body = BODIES[d["id"]]
    n = len(re.sub(r"\s+", "", body))

    # ---- A 组:字段/格式不对,这一篇根本发不出去 ----
    if d["form"] == "回答" and not d["qurl"]:
        A.append("形态是【回答】却没有问题链接 —— 知乎回答必须挂在一个具体问题下面")
    if d["form"] == "文章" and not d["title"]:
        A.append("形态是【文章】却没有标题")
    if d["form"] == "文章" and len(d["title"]) > 80:
        A.append("标题 %d 字,超过知乎上限 80" % len(d["title"]))

    # ---- B 组:能发出去,但会落到某一档处置 ----
    # 声明与事实不符
    if d["ai"] and d["declare"] != "包含 AI 辅助创作":
        B.append(("标识", "标了【AI 参与】但创作声明是【%s】—— 声明与事实不符" % d["declare"]))
    if not d["declare"]:
        B.append(("标识", "创作声明为空 —— 发布前必须显式设置"))
    # 疑似编造亲历(正文里有亲历口吻,但没有素材来源)
    if re.search(r"我亲眼|我亲历|我朋友|我同事|我邻居", body) and not d["src"]:
        B.append(("删除", "正文出现亲历口吻(【%s】)但没有素材来源 —— 虚构包装经历是明确红线"
                  % re.search(r"我亲眼|我亲历|我朋友|我同事|我邻居", body).group(0)))
    # 无出处数据
    m = re.search(r"据[^,。;]{0,12}(?:统计|调查|研究|数据)|\d{2,}\s*%", body)
    if m and not re.search(r"据(?:[^,。;]{0,12})(?:发布|公告|报告|白皮书)", body):
        B.append(("建议修改", "出现【%s】这类数字,但找不到机构名与日期 —— 缺乏事实依据" % m.group(0)[:14]))
    # 结构模板化
    cn = sum(body.count(w) for w in CONNECT) * 100.0 / n
    if cn > 1.5:
        B.append(("减少分发", "路标词每百字 %.2f 次,结构偏模板化 —— 判 AI 的抓手之一" % cn))

    # ---- 账号级:单篇看不出来,连着发才出事 ----
    if n < 100:
        ACCT.append("正文 %d 字,低于 100 字 —— 连续 3 天这样会让创作行为分归零重计" % n)

    return A, B, ACCT

print("=" * 76)
print("一、A 组:会让这一篇【发不出去】的(先拦这一批)")
print("=" * 76)
a_total = 0
for d in DOCS:
    A, B, ACCT = analyze(d)
    a_total += len(A)
    print()
    print("  [%s] %s" % (d["id"], "OK" if not A else "%d 条" % len(A)))
    for x in A:
        print("        ✗ %s" % x)

print()
print("=" * 76)
print("二、B 组:能发出去、但会【被处置】的(按档位从重到轻排)")
print("=" * 76)
b_all = []
for d in DOCS:
    A, B, ACCT = analyze(d)
    for lvl, msg in B:
        b_all.append((d["id"], lvl, msg))
b_all.sort(key=lambda t: ORDER.index(t[1]))
if not b_all:
    print("  (无)")
for tid, lvl, msg in b_all:
    print("  [%s]%-6s %s" % (tid, lvl, msg))

print()
print("=" * 76)
print("三、账号级:单篇看不出、连着发才出事的")
print("=" * 76)
found = False
for d in DOCS:
    A, B, ACCT = analyze(d)
    for x in ACCT:
        found = True
        print("  [%s] %s" % (d["id"], x))
if not found:
    print("  (无)")

print()
print("=" * 76)
print("四、汇总与放行")
print("=" * 76)
print("  A 组 %d 条(这部分不改就发不出去,处理顺序无所谓);" % a_total)
print("  B 组 %d 条(按档位排序:%s)。" % (len(b_all), " > ".join(ORDER)))
print()
ok = [d["id"] for d in DOCS if not any(analyze(d))]
print("  三项全清的:%s" % ("、".join(ok) if ok else "(无)"))
print("  注意:B 组里 P2 一篇就踩了四个不同档位 —— 平台按【最重的那个】处置,")
print("        所以要【从重往轻修】,不是按发现的先后修。")

实际运行输出(真跑,不是示意):

============================================================================
一、A 组:会让这一篇【发不出去】的(先拦这一批)
============================================================================

  [P1] OK

  [P2] OK

  [P3] OK

  [P4] 1 条
        ✗ 形态是【回答】却没有问题链接 —— 知乎回答必须挂在一个具体问题下面

============================================================================
二、B 组:能发出去、但会【被处置】的(按档位从重到轻排)
============================================================================
  [P2]删除     正文出现亲历口吻(【我朋友】)但没有素材来源 —— 虚构包装经历是明确红线
  [P2]减少分发   路标词每百字 1.57 次,结构偏模板化 —— 判 AI 的抓手之一
  [P2]建议修改   出现【据某机构统计】这类数字,但找不到机构名与日期 —— 缺乏事实依据
  [P2]标识     标了【AI 参与】但创作声明是【无声明】—— 声明与事实不符

============================================================================
三、账号级:单篇看不出、连着发才出事的
============================================================================
  [P3] 正文 19 字,低于 100 字 —— 连续 3 天这样会让创作行为分归零重计
  [P4] 正文 84 字,低于 100 字 —— 连续 3 天这样会让创作行为分归零重计

============================================================================
四、汇总与放行
============================================================================
  A 组 1 条(这部分不改就发不出去,处理顺序无所谓);
  B 组 4 条(按档位排序:删除 > 折叠 > 减少分发 > 建议修改 > 标识)。

  三项全清的:P1
  注意:B 组里 P2 一篇就踩了四个不同档位 —— 平台按【最重的那个】处置,
        所以要【从重往轻修】,不是按发现的先后修。

读结果,五个值得注意的地方:

  1. 把预检分成 A、B 两组,是本节最重要的一个设计决定。 为什么不能合成一个清单?因为它们的性质和处理方式完全不同:
  2. A 组是【技术故障】——发不出去,改完就能发,顺序无所谓;
  3. B 组是【风险】——能发出去,但会落到某一档处置,而且平台按最重的那个处置,所以必须从重往轻修。 如果混在一起排,人就会从第一条开始一条条改——而可能第一条是【标识】那一档(最轻),最后一条才是【删除】那一档。改完最轻的才发现最重的那条根本不该发,前面的力气白花。

  4. P2 一篇就踩了四个不同档位——而且两头的两条特别典型:最重的那条(编造亲历)是模型最容易犯的(它会为了"写得更像真人"而编细节),最轻的那条(声明)是人最容易忘的。这两个方向的错误同时出现在一篇稿子里——说明【机器出的错】和【人出的错】会叠加,而预检是唯一能一次看到全部的地方。

  5. P2 里那句【我朋友在杭州买的那套房子我亲眼见过】,是本节最典型的一条检查。 注意脚本没有去判断这句话是真是假(它判不了)——它判的是【有没有素材支撑】:正文里出现了亲历口吻,但 src 这一格是 False。 这也正好接回 5.2 任务表的【补过人味】那一格:如果当初真的补了亲历素材,这一条根本不会触发。 所以检查项之间不是独立的,它们在同一条链上。

  6. 账号级那一条被单独列出来,是因为它的性质不一样:它不是【这一篇会怎样】,是【这一串会怎样】。 单篇看,19 字和 84 字都没任何问题;但连着几天这样发,创作行为分会归零重计(5.1.1 那条规则)。 这也是【为什么要把账号级规则写进脚本】的原因——有些风险单看一篇是永远看不出来的,只有把它和【频次】放在一起才成立。

  7. "三项全清"的只有 P1,而 P1 恰好就是 5.4.1 里放行的那一份。 这不是巧合——它说明从 5.4.1 的取稿验收、到 5.4.2 的排版体检、到本节的发布前预检,三关的判据是【一致】的。 如果三条链路各自为政(比如取稿时说"人味 5/6 可以过",发布前却要求"必须 6/6"),那么合格的标准就会前后矛盾,人就会陷入【到底听谁的】。 一致性的价值,在于让人知道【改到什么程度为止】。

📝 顺手看一眼本节与 4.4.4 的关系

两节都是【发布前预检】,但排序依据完全不同: | | 4.4.4(头条) | 5.4.3(知乎) | |---|---|---| | 排序依据 | 信用分扣几分(70/20/10/5) | 落到哪一档处置(删除/折叠/减少分发/建议修改/标识) | | 共同点 | 都按【后果有多重】排,而不是按【发现顺序】排 | 同 |

这就是本课反复出现的那条原则的又一次应用:检查项的顺序不是【你想起来几件就列几件】,而是【后果从重到轻】。 而且两个平台的【后果】量纲不同——头条是分数(可累积、可计算),知乎是档位(离散、不可逆)——这正是 5.1.1 说的"风险形状不同"。

30 秒实验

把 Demo 里 P2 的 src 从 False 改成 True,再跑一次。

会发生什么:

结论:修掉最重的那条,不等于这篇就能发。 一篇文章常常是【一身的毛病】,而预检的价值是让你看清【哪一条最该先修】——先修它,防止【修了半天最轻的那条,最后还是要删掉重写】。

顺着这个思路想一层:为什么【编造亲历】会被放在最重的一档(删除)?因为它触碰的不是格式,是真实——平台可以容忍一篇写得普通的回答,但不能容忍一篇【假装真实】的回答。 这与第一章那句关于【虚构包装纪实】的公告是同一条线:在这个平台,真实性的权重高于一切。

常见误区

  1. 【一份脚本通吃三种形态】——错,这是本节最基础的一条。 文章、回答、想法三路互不通用:字段不同(文章有标题和封面、回答有问题链接、想法两样都没有)、入口不同、连定时发布都只有文章支持。必须先读任务表的【形态】字段做路由,再选操作序列。
  2. 【发布前预检就是列一张问题清单,一条条改】——错,要先分组。 A 组(发不出去)和 B 组(发出去出事)的处理方式完全不同:A 组是技术故障、顺序无所谓;B 组要按处置档位从重到轻修(删除 > 折叠 > 减少分发 > 建议修改 > 标识)。混在一起排,你可能会先改最轻的那条。
  3. 【创作声明可以发布之后补】——技术上可以,但代价是双重的。 漏了要回头编辑已发布的内容补设——多一步操作,多一次风控记录。而且那一档的完整名字是【包含 AI 辅助创作,作者对内容负责】——它是责任声明,不是免责声明。 责任由人签,所以这一步不能交给脚本默认。
  4. 【连续发多篇没关系,反正内容都合格】——错,节奏本身是被治理的对象。 短时间内连续发布会出现验证码或【操作过于频繁】提示,一个可靠的间隔是 60 秒以上。而且【像人一样操作】这件事在这一章有两层意思——技术上的节奏,和内容上的人味——它们是同一条规则的两面。
  5. 【只要三项全清就一定能发布成功】——不保证,但它把风险挡在了最便宜的位置。 平台的判据没有公开(5.1.1 反复强调过),预检做的是【把已知的规则全部过一遍】。它不能保证你过审,但它能保证【不因为你早就知道的原因而出事】——而在这一章,知道的原因已经很多了。
自测三问(点开对答案) 1. **为什么发布前预检要分成 A、B 两组,而不是列一张清单?** 因为**两类问题的性质和处理方式完全不同**: - **A 组是【发不出去】**(字段/格式不对)——**技术故障**,改完就能发,**顺序无所谓**; - **B 组是【能发出去、但会被处置】**——**风险**,而且**平台按最重的那个处置**,所以**必须按档位从重到轻修**(删除 > 折叠 > 减少分发 > 建议修改 > 标识)。 **混在一起排的后果**:你可能会从第一条开始改,而第一条恰好是最轻的【标识】档,最后一条才是【删除】档——**等改完最轻的,才发现最重的那条根本不该发,前面的力气白花。** 2. **知乎的三路形态,哪一条路支持【定时发布】?这个细节对流程有什么影响?** **只有【文章】支持**定时发布——**回答和想法目前必须即时发出**。 影响是:**【排期】这件事被限制在一条路上。** 如果你想做【晚上十点发】这种节奏控制,**只有文章能用官方定时**;**回答和想法要么当场发,要么先存草稿、到时候再让脚本点发布**——**后者需要额外设计一个【定时唤醒】的环节。** 3. **创作声明那一档为什么不叫【含 AI 生成】,而叫【包含 AI 辅助创作,作者对内容负责】?这个措辞对流程设计有什么含义?** 因为它**不是免责声明,是责任声明**——**标了它等于承诺【我对这篇内容的准确性负责】。** 对流程设计的含义:**声明必须由人来设,不能让脚本默认一个值。** 因为**责任不能由机器签**——这也正是 5.2 把【设声明】和【点发布】放在同一个闸门里的原因:**这两件事都属于【把责任接过来】的动作,必须同一个人、同一个时刻完成。**

相关笔记