两年前我还在手动运营六个内容账号,睡得最晚的一晚,是因为一篇稿子要在三个平台各改一遍格式,凌晨一点半才把封面、标签、摘要全部处理完。那时候我就意识到:内容的“生产”已经被AI接管,但“分发”和“品牌植入”仍然在靠人工死磕。后来我花了两三周时间把整个流程代码化,做了一套开源的一站式AI运营源码系统,把AI内容生成、多平台发布、品牌信息嵌入放进同一条流水线。到今天它已经稳定运行了八个多月,每天自动输出十几篇图文,我需要做的只是每周看一次效果报表。
这套系统不是什么大厂平台,就是个人也能部署起来的轻量方案。做自媒体矩阵的、做企业内容营销的、想给产品批量做种草内容的,都可以参照这篇文章里的设计自己搭一套。下面我会从最底层的需求拆解讲到模块实现,再到部署和真实踩坑,代码片段可以直接拿回去改。
1. 内容运营流水线的改造起点:从“重复搬运”到“全自动分发”
1.1 手动发布一个平台需要多少分钟
先说一个最朴素的问题:一篇图文手动发到五个平台,到底要花多久?
很多人觉得复制粘贴很快,其实完全不是。我实测过,一篇2000字左右的文章,发到第一个平台需要处理标题字数限制、首图尺寸、摘要字数、正文排版、话题标签,大概10分钟。发到第二个平台,又因为编辑器不支持某些Markdown语法,需要重新调整代码块和列表,再加8分钟。如果是第三个、第四个平台,每家的规则都不一样,平均下来一个平台至少6到8分钟。五个平台就是四十分钟,而且纯粹是机械劳动,中间还不能走神,否则容易漏设原创声明、漏勾选“允许转载”之类的选项。
这种工作最可怕的地方在于它会天天重复。每天写三篇内容,分发就要两个多小时,占掉一天有效工作时间的三分之一。更别提分发完还要回去检查哪些平台发布失败了、哪篇被系统判了重复、哪个账号的Token过期了。我见过不少团队用协作表格管理这些事,但表格只能记录“有没有发”,不能解决“为什么要手动发”。
所以这套系统要做的第一件事,是把“写完内容到内容上架”之间的所有重复步骤接管过来。人工只需要确认选题、审核最终稿,剩下的标题微调、排版转换、标签组合、定时发布,全部交给代码。
1.2 品牌植入为什么是体力活
如果你的内容里需要带上自家品牌或者客户品牌,工作量还要再加一档。品牌植入不是简单地把品牌名塞进文章里,而是要控制出现位置、出现次数、上下文语境,还要避开违禁词。手动操作的时候,我经常为了把品牌词“自然”地放进某一段,反复调整前后句,改着改着就把原来的行文节奏破坏了。
更麻烦的是多平台发布时,每个平台对“营销内容”的容忍度不一样。有些平台品牌词出现三次就可能被限流,有些平台则需要你在文末明确标注广告信息。这些规则如果全由人来记,很容易出错。我当时的想法是:把品牌相关的约束做成结构化数据,交给AI在生成阶段就处理好,而不是生成完再人工去插。
1.3 为什么现成的“内容中台”工具解决不了
我也试用过几个号称“AI写稿+多平台分发”的商业工具,总结下来有几个痛点:一是价格不便宜,按账号数和生成次数收费,账号一多成本直线上升;二是“品牌植入”都做得很浅,基本就是生成完把品牌词替换进去,效果生硬而且容易被平台识别为营销;三是数据不透明,我看不到生成Prompt、发布日志和失败重试的具体细节,出了问题只能找客服。
开源项目里也有不少单点工具,比如只做AI文章生成的、只做定时发布到单一平台的。但它们之间没有统一的数据结构,任务状态、文章内容、发布结果各存各的,串不起来。我要的是一个能覆盖“选题—生成—植入—审核—发布—数据回传”全链路的自托管系统,这也是为什么最终决定自己写一套。
2. 系统整体架构:四个模块如何协作成一条完整的“产稿供应链”
2.1 模块划分:生成、植入、发布、调度
这套系统的源码不是一个大而全的单体应用,而是按职责拆成了四个模块,外加一个调度中枢:
- 内容生成模块(generator):负责根据主题、大纲、语气等参数调用大模型接口,产出标题、摘要、正文。
- 品牌植入模块(brand):维护品牌库,向生成模块提供结构化品牌约束,并在生成后校验植入效果。
- 多平台发布模块(publisher):封装各平台发布接口,处理登录态、排版转换、频率控制、失败重试。
- 任务调度中枢(task):把上面三个模块串起来,负责创建任务、推进状态、记录日志。
所有模块之间不直接互相调用,而是通过一个简单的状态机来协作。每个任务都有pending → generating → auditing → publishing → done/failed这几个状态,任何一个环节失败都会自动重试或者进入人工处理队列。
2.2 任务数据流与运行机制
具体跑一轮的流程是这样的:调度中心每天按配置生成任务,每个任务包含一个选题和一组目标平台。生成模块先从品牌库拉取约束信息,组装成Prompt,请求大模型得到文章草稿。草稿经过审核(长度检查、品牌词计数、敏感词过滤、相似度检测)之后,交给发布模块。发布模块根据目标平台的不同,分别做排版转换和封面处理,然后调用对应平台的发布接口,最后把返回的帖子ID、状态、失败原因写回任务日志。
这里我踩过一个设计上的坑:最初所有模块都是同步调用,生成完立刻发布,结果某个平台临时不可用会导致整条链路阻塞。后来改成了任务表驱动,每个阶段独立记录状态,失败的任务挂起,等下一轮调度再处理。这样单个平台出问题不会影响其他平台的发布进度。
2.3 技术选型:为什么是Python+FastAPI+SQLite
技术栈选得很轻:Python 3.10 + FastAPI做Web管理和API入口,SQLite做本地存储,APScheduler做定时调度。没有用PostgreSQL、Redis这些重型组件,因为这套系统本质上是一个“单人或者小团队可维护的自动化管道”,数据量一天也就几百条,SQLite完全扛得住,备份也简单,直接复制文件就行。
FastAPI的好处是自带API文档,我可以直接在浏览器里调试任务接口,不用单独搭一套管理后台。如果以后想把任务提交、文章审核做成多用户协作,FastAPI也可以方便地加认证和权限。整个系统跑起来内存占用不到300MB,放在一台2核2G的小机器上绰绰有余。
2.4 源码目录结构
开源出来的目录结构大致如下,没有过度工程化,基本一看就懂:
aiops/ ├── app/ │ ├── generator/ │ │ ├── client.py # 大模型接口封装 │ │ ├── prompt_builder.py # Prompt组装 │ │ └── post_cleaner.py # AI味清理 │ ├── brand/ │ │ ├── brand_lib.py # 品牌库读写 │ │ └── validator.py # 植入效果校验 │ ├── publisher/ │ │ ├── base.py # 发布基类 │ │ ├── platform_a.py # 某资讯平台实现 │ │ ├── platform_b.py # 某技术社区实现 │ │ └── platform_c.py # 某问答社区实现 │ ├── task/ │ │ ├── manager.py # 任务状态机 │ │ └── scheduler.py # 定时调度 │ └── config/ │ ├── settings.yaml # 全局配置 │ └── base.py # 配置加载 ├── storage/ │ ├── credentials/ # 各平台登录凭据 │ └── logs/ # 运行日志 ├── data/ │ └── app.db # SQLite数据库 ├── run.py # 启动入口 └── requirements.txt每个模块的入口都是一个CLI命令,也可以直接通过HTTP API调用。这样设计的好处是,我想单独跑一遍“只生成不发布”的流程时,不需要把整个系统拉起来。
3. 内容生成模块:把“AI写稿”从抽奖变成可控输出
3.1 模型接入与请求封装
内容生成模块是整个系统的“发动机”。我在接入层做了一个非常薄的封装,不绑定任何特定的大模型服务,而是通过标准的HTTP接口调用远端大模型API。这样既可以在云端服务之间切换,也可以换成自建的开源模型服务。
核心请求逻辑很简单,就是一个call_llm(prompt_messages, temperature, max_tokens)函数。但它支撑了系统里所有AI调用,所以我重点控制了三件事:超时时间、重试次数、输出长度上限。大模型接口偶尔会有网络抖动或者响应超时,如果不做重试,整个任务链就断了。我的策略是:连续失败3次就把任务标记为“需人工”,把错误信息写进日志,而不是无限重试。
3.2 Prompt模板设计:把写作要求“结构化”
AI写稿最容易出现的问题不是“写不出来”,而是“乱写”。让它自由发挥的话,十条里有七八条偏题,品牌词出现的位置也不可控。所以我在Prompt设计上花了最多功夫,核心思想是:把写作要求结构化成JSON,明确告诉模型“你要输出什么格式、包含什么信息、遵守什么规则”。
下面是一段简化的Prompt构建代码:
import json def build_prompt(topic: str, brand_info: dict, outline: list, tone: str = "专业"): sys_prompt = ( "你是一名资深内容运营编辑,擅长撰写结构清晰、有观点的原创内容。" "你会自然地在行文中提到指定品牌,但不要生硬重复品牌名。" "全文输出为JSON,包含title、summary、outline、content四个字段。" ) user_prompt = f""" 请围绕主题“{topic}”写一篇约1200字的图文内容。 要求: 1. 语气风格:{tone} 2. 章节结构至少覆盖以下大纲点:{json.dumps(outline, ensure_ascii=False)} 3. 品牌信息:{json.dumps(brand_info, ensure_ascii=False)} 4. 品牌出现规则:全文出现3到5次,第一次必须出现在前两段,最后一次放在结尾总结句中。 5. 禁止出现“最好的”“第一品牌”“国家级”等广告违禁词,用使用场景和用户体验描述替代。 请直接输出JSON,不要包含markdown代码块。 """ return [ {"role": "system", "content": sys_prompt}, {"role": "user", "content": user_prompt}, ]几个关键设计点:
- 大纲由任务配置传入,模型不需要自己定选题方向,避免跑偏。
- 品牌信息里面的“关键词”和“使用场景”会被拼进Prompt,让模型知道该从哪些角度写,而不是简单地把品牌词替换掉。
- 强制输出JSON格式,方便后续代码解析。模型偶尔会输出多余的文字说明,所以我在解析的时候做了容错:只截取第一个
{到最后一个}之间的内容。
3.3 输出解析与“AI味”清理
大模型生成的原稿直接拿去发,很快会被平台识别为低质内容。我在生成之后加了一道“后清理”流程,主要做三件事:
- 首段改写:很多模型喜欢用“在当今社会”“随着科技的发展”开头,这些句子一眼就能看出来是AI写的。我会用一个“首段重写”的二次调用,要求模型把开头改成具体的场景描写或者故事引入。这个技巧实测效果很好,能让文章存活率提升不少。
- 语气词替换:把“值得注意的是”“总而言之”这类模板化连接词批量替换成更自然的过渡句,比如“这里有个容易被忽略的点”“所以下次遇到类似情况时可以试试”。
- 长度校准:模型报的“1200字”经常不准。我会按字符数做一次软检查,少于800字就追加一段,超过2000字就截断到主要章节。
这一步听起来很繁琐,但对最终发布效果影响非常大。没有做清理之前,我的文章在某个平台被系统标记为“疑似AI生成”的比例高达30%;加了这个环节之后,这个比例降到了5%左右。
3.4 成本与速度的平衡策略
大模型API是按次调用的,每次都让最强的模型来写,成本会有点吓人。我的方案是“草稿用轻量模型,成稿用高质量模型”:先用轻量模型按大纲生成一个粗糙草稿,再让高质量模型在草稿基础上重写润色。这样做比直接让高质量模型从零开始写,能省下不少token,而且最终质量更稳定,因为草稿已经把结构和素材定死了,重写模型只需要专注表达。
实际跑下来,一篇1200字文章的总token成本大概在轻量模型的两倍多一点,但这是值得的。内容质量直接影响平台推荐量,质量差的内容发了也白发,账号权重还会被拉低。
4. 品牌信息嵌入:从硬广告到“观点式自然出现”的实现路径
4.1 品牌库的数据结构设计
品牌植入不是简单地把“某品牌”三个字塞进文章里。我在系统里设计了一个品牌库,每个品牌的信息以JSON形式存储:
{ "id": 1, "name": "某品牌", "aliases": ["某牌子", "某家"], "keywords": ["耐用", "省电", "便携"], "scenes": ["长途通勤", "户外露营", "办公桌面"], "forbidden_words": ["第一", "最好", "国家级"], "max_count": 5, "min_count": 3 }字段含义很直接:name是标准品牌名,aliases是备选叫法,用来让文章不那么重复;keywords是品牌卖点,AI在写作时需要围绕这些词展开;scenes是使用场景,用来生成真实的用户描述;forbidden_words用来规避广告法风险;max_count和min_count控制品牌词出现次数。
这个品牌库是整个植入功能的基石。它不只服务于Prompt构建,还服务于生成后的校验。
4.2 三种植入策略与适用场景
根据内容类型和投放目的,我实现了三种植入策略,可以在任务配置时按需选择:
- 显式嵌入:适合短平快的内容。在首段明确提一次品牌,中间的小标题里带一次,结尾再点一次。这个策略最简单可控,但只适合用户已经对品牌有认知的场景,比如公众号推文、新品发布公告。
- 语义嵌入:适合中长文。不直接说品牌名,而是先描述某个使用场景,比如“露营三天不充电”,再引出品牌产品。模型在这个策略下会把品牌卖点和使用体验绑定在一起。这种方式在问答社区和测评类内容里非常自然。
- 观点式嵌入:适合深度内容。把品牌产品作为“作者观点”的一部分,比如“经过对比测试,我认为在千元价位里它是最均衡的选择”。这种植入最软,但要求模型的判断和内容逻辑一致,否则会显得突兀。
三种策略在实现上差别不大,都是通过Prompt里的“品牌出现规则”字段来控制,但最终效果差异很明显。观点式嵌入的阅读体验最好,平台识别营销的几率也最低;显式嵌入胜在稳妥,适合赶时间批量出内容。
4.3 植入效果校验与密度控制
生成完成之后,不能直接信模型说“我植入好了”。我写了一个独立的校验函数,做三件事:
- 统计品牌名和别名在全文的出现次数,如果不在
min_count和max_count之间,就打回重写。 - 检查品牌词第一次出现的位置,要求必须在前两段以内,避免读者读了一半才知道文章在讲什么。
- 检查
forbidden_words列表里的词是否出现,一旦出现就标记为“需人工调整”。
密度控制是品牌植入里最容易翻车的地方。我一开始把max_count设成8,结果大批文章被平台判定为营销内容,账号被限流。后来调到3到5次,配合语义植入策略,被系统误伤的情况明显减少了。现在我的默认配置是:文章篇幅越长,品牌出现次数可以稍高,但单篇不超过5次。
4.4 品牌信息如何在生成阶段与大纲绑定
这里还要强调一个设计思路:品牌植入是在生成阶段完成的,不是生成之后再插入。因为文章一旦写完整,再去插入品牌词,前后语境很难圆回来,读起来会有明显的割裂感。
具体做法是在构建大纲的时候,把品牌卖点和某个章节绑定。比如一篇露营装备推荐文章,大纲里本来就有一个“电池续航实测”的部分,品牌库里的“省电”这个关键词就会被安排进这个章节的要求里,让模型在这一段自然提到品牌。这样一来,品牌词出现在哪个章节、出现在什么上下文里,都是可控的,而不是碰运气。
5. 多平台发布模块:接口抽象、登录态维护与排版适配
5.1 统一发布接口的设计
多平台发布是这个系统里“脏活”最多的模块。不同平台的接口风格完全不一样,有的接受Markdown,有的要纯文本,有的必须单独提交封面图和摘要。
为了不让上层业务逻辑被某个平台的API绑死,我抽象了一个发布基类:
class BasePublisher: def __init__(self, credentials: dict): self.credentials = credentials def login(self): """获取并缓存登录态""" raise NotImplementedError def publish_text(self, title: str, content: str, tags: list): """纯文本发布""" raise NotImplementedError def publish_rich(self, title: str, content: str, cover: str, digest: str): """图文发布,适用于需要封面图和摘要的平台""" raise NotImplementedError每个平台的实现类只需要关注两件事:如何登录、如何把文章数据翻译成该平台要求的参数。上层调度完全感知不到平台差异,都是调用publish_text或者publish_rich。
5.2 不同平台内容的差异化处理
这里说的“排版适配”不是简单地把HTML转成Markdown,而是每个平台的特殊处理规则。我整理了三个典型场景:
- 资讯类平台:要求缩略图尺寸为16:9,摘要控制在90字以内,正文里不能有某些HTML标签。我在发布前会把封面图统一裁切成平台所需尺寸,并重新生成摘要。
- 技术社区类:代码块转换是最麻烦的,平台编辑器对缩进和空行的处理不一致,必须把代码块转成平台自己的代码插件格式。我写了一个轻量转换函数,把GitHub风格的 ``` 代码块替换成平台需要的标签。
- 问答社区类:这类平台对“营销嫌疑”最敏感。我的做法是跳过显式植入策略的文章,优先用观点式嵌入的内容。发布时还要注意不能带外链。
平台差异如果全部硬编码进发布模块,会越写越乱。我的做法是写一个“平台能力表”,用配置声明每个平台支持哪些能力、需要哪些必填项,发布时根据这张表动态调整。
5.3 登录态与Token刷新机制
多平台发布绕不开登录态管理。有些平台用开放平台的Token,有效期几个小时要刷新;有些平台是类似浏览器的Cookie,过期时间短且需要维护会话。
我在storage/credentials/目录下按平台保存凭据文件,发布模块启动时会先检查凭据是否有效,如果过期就执行刷新逻辑。刷新失败时,系统不会直接崩溃,而是把任务标记为“等待人工登录”,并通过日志和监控提醒我。
这个模块比较费心,因为各平台刷新Token的流程差别很大。我的建议是:不要试图把所有平台都做到“免人工维护”,先把两三个主要平台的刷新流程跑通,其他平台预留手动更新凭据的入口。大多数个人运营者,一个平台能自动跑,就已经省下很多时间了。
5.4 发布调度与频率控制
批量发布不能在同一秒内一股脑把十几篇内容全发出去,容易被平台风控。我在调度里加了频率控制,默认每个平台间隔5到10分钟,每天最多发布固定数量。时间点也会做随机化处理,避免每天发布节奏完全一致,比如8点、10点、14点各一篇,而不是整点钉死。
如果某个平台返回“频率过高”或者“内容重复”的错误,调度中心会把该任务挂起,延后半小时再重试。重试两次仍然失败,就会进入人工处理队列,不会无限循环下去。
6. 部署上线流程:从拉取源码到第一篇文章自动发出
6.1 环境准备与依赖安装
这套系统对运行环境的要求非常低。我用的是Python 3.10版本,依赖都写在requirements.txt里,主要包括FastAPI、APScheduler、requests、PyYAML、Pillow(图片处理)。安装命令就是标准的:
pip install -r requirements.txt首次建议在本地跑通流程再上服务器。我一开始直接在服务器上调试,结果因为网络环境和本地不一样,平台接口的连通性排查了好久。先在本地把生成、发布流程跑一遍,能省掉很多冤枉时间。
6.2 配置文件详解
系统核心配置全部集中在app/config/settings.yaml里,我贴一个经过简化的示例:
llm: endpoint: "http://你的模型服务地址/v1/chat/completions" api_key: "替换成你的key" model: "default" temperature: 0.7 max_tokens: 3000 brands: - id: 1 name: "某品牌" aliases: ["某牌子"] keywords: ["耐用", "省电"] scenes: ["户外露营", "办公"] max_count: 5 min_count: 3 platforms: - name: "某资讯平台" type: "rich" credential_file: "credentials/platform_a.json" publish_interval_seconds: 300 - name: "某技术社区" type: "markdown" credential_file: "credentials/platform_b.json" publish_interval_seconds: 600 schedule: daily_count: 12 start_time: "08:00" end_time: "22:00" audit: min_content_length: 800 max_brand_count: 5 similarity_threshold: 0.75配置项里最容易忽略的是max_tokens。如果设得太小,文章生成到一半会被截断,导致品牌词没有出现,校验阶段就会把文章打回重写。我最初设的是1500,经常出现这种问题,后来根据实际输出字节数调整到了3000才稳定。
6.3 数据库设计与初始化
系统一共三张核心表,设计得非常简单:
CREATE TABLE tasks ( id INTEGER PRIMARY KEY AUTOINCREMENT, topic TEXT NOT NULL, brand_id INTEGER, target_platforms TEXT, status TEXT DEFAULT 'pending', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE articles ( id INTEGER PRIMARY KEY AUTOINCREMENT, task_id INTEGER, title TEXT, summary TEXT, content TEXT, status TEXT, generated_at DATETIME ); CREATE TABLE publish_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, article_id INTEGER, platform TEXT, platform_post_id TEXT, status TEXT, message TEXT, published_at DATETIME );数据库文件放在data/app.db,首次运行时会自动创建表结构,不需要手动执行SQL。日常使用中,我每三天备份一次这个文件,因为里面存了所有发布历史,换服务器或者出问题恢复都靠它。
6.4 任务创建与结果验证
部署完成后,可以手动创建一个测试任务来验证全流程:
python run.py create-task --topic "露营灯怎么选" --brand-id 1 python run.py process-tasks第一条命令创建任务,第二条命令处理所有待处理任务。如果只想生成不发布,可以用--dry-run参数,这是我最常用的调试方式。验证通过后,再把定时调度打开,系统就会按schedule配置自动创建任务并执行。
再提一个细节:正式用之前,先用一个临时测试账号跑几次发布,确认平台返回的帖子ID是正常的。千万别拿主力账号直接试,有些平台在凭据失效后可能触发安全验证,处理起来很麻烦。
7. 实际运行中的问题与调优记录
7.1 生成内容超长导致品牌词丢失
系统上线头两周,最频繁的问题不是发布失败,而是校验阶段打回率接近两成。查日志发现,大部分打回原因是“品牌词出现次数不达标”。但奇怪的是,查看Prompt,品牌约束明明写得很清楚。
后来定位到根因:文章超过模型输出上限,尾部内容被截断,被截掉的部分恰好包含品牌词的收尾出现。解决方式有两个,一是把max_tokens提高,二是把“品牌词最后一次出现”从文章末尾改到倒数第二部分。现在我用的是后者,因为即使长度还是超了,结尾被截断也不太影响品牌词出现次数。
7.2 平台检测到内容相似怎么办
发布一段时间后,某技术社区平台开始提示“内容疑似重复”,但我的文章确实是每篇不同主题。仔细排查发现,问题出在开头的“AI味清理”没有完全生效——清理之后,每篇文章的第一段都是同一个“故事引入”模板,只是具体名词换了一下。平台可能提取的是段落哈希,导致相似度被误判。
解决办法是在清理流程里加入“首段重写”和“句法变体”两步:首段重写确保引入方式不同,句法变体则把开头第一句的主语、语序做随机调整。加了这两步之后,平台重复提示基本消失了。
7.3 品牌植入过度的账号被限流
第三个坑是关于限流的。某资讯平台一开始给了不错的推荐量,但连续发了一周带品牌词密度较高的文章之后,账号的推荐权重明显下降,后台看到“低质内容”的提示。
我把植入密度参数从5次降到了3次,并且把文章重点转向“使用体验描述”而不是“产品参数堆砌”。一周后账号推荐恢复。经验是:品牌词密度宁可少一点,也不要让平台觉得你在发广告。好内容是给用户看的,不是给爬虫识别的。
7.4 运行数据与后续设想
到现在为止,这套系统每天的运行情况大致是:创建并生成12到15篇内容,其中大约有2篇会因为审核不通过或者平台错误进入人工处理。真正需要人工干预的比例不到15%,剩余85%的内容从生成到发布全自动完成。内容推荐数据比起纯人工时期不仅没有下降,反而整体提升了大概20%。
后续我想做三件事:一是给生成模块加入“历史效果反馈”机制,阅读量高的文章风格会反向影响Prompt权重;二是做一个多账号矩阵支持,目前只用了一个主账号,很多平台的流量红利吃不满;三是在发布模块里接入更多内容类型,比如短视频脚本和图文卡片。
如果你也在搭类似的东西,我的建议是先跑通最小闭环——一个选题、一个品牌、一个平台,稳定以后再横向扩展。把链路拉长之前,先把每一环的容错做扎实,比增加功能点重要得多。