用大模型批量生成 Python 差异化自媒体创意
做自媒体最怕的不是"不会写",而是写完一百篇之后,每一篇都长得像上一篇。大模型能帮你把"写一篇"变成"写一百篇",但前提是------你得先教会它什么叫"不一样"。否则你得到的就是一百篇换汤不换药的流水线废稿。
这篇文章讲一套可落地的方案:用 Python 驱动大模型,批量生成有结构差异化的自媒体创意,而不是批量生产垃圾。
一、核心问题:为什么直接调 API 生成的创意都"一个味"
你肯定试过直接让大模型"给我写 10 个科技类短视频选题",结果拿到的是:
- AI 如何改变未来
- 5 个你不知道的 Python 技巧
- 程序员必看的 3 本书
...
这玩意儿你自己也能想出来,而且你想出来的可能还更有趣。
**问题不在模型,在你的输入结构。** 大模型是"条件生成器",你给的条件越粗糙,它就越往训练数据里最常见的方向靠。要出差异化,得从三个维度下手:
| 维度 | 粗糙做法 | 差异化做法 |
|---|---|---|
| 角度 | "写个 Python 教程" | 限定叙事视角:新手踩坑 / 面试官视角 / 十年老码农吐槽 |
| 格式 | "写篇文章" | 限定输出结构:清单体 / 对话体 / 反转体 / 数据对比体 |
| 钩子 | 无约束 | 强制前 3 秒必须出现反常识断言或具体数字 |
下面这套方案,就是把这三个维度参数化,让 Python 替你做排列组合。
二、整体架构
创意种子池(CSV/JSON)
│
▼
差异化参数矩阵(角度 × 格式 × 钩子类型)
│
▼
Prompt 模板引擎(Jinja2)
│
▼
批量调用大模型 API(异步并发)
│
▼
后处理:去重 / 打分 / 人工筛选
每一步都是可控的,每一步都能调。
三、创意种子池:别让模型"凭空想"
第一步最容易踩的坑:让模型自己决定写什么。
"给我 20 个选题" → 模型从训练数据里抽最常见的 20 个 → 你得到一堆陈词滥调。
正确做法:你给它具体的种子 ,它负责加工和变形。
# seeds.py
SEEDS = [
{"topic": "Python 虚拟环境", "audience": "转行学编程的文科生", "constraint": "必须提到 conda 和 venv 的恩怨"},
{"topic": "FastAPI vs Flask", "audience": "有 2 年经验的创业公司后端", "constraint": "要带真实 QPS 对比数据"},
{"topic": "爬虫被反爬", "audience": "刚被公司要求写爬虫的初级开发", "constraint": "必须包含一段真实报错信息"},
{"topic": "SQLAlchemy 2.0", "audience": "还在用 Django ORM 的老项目维护者", "constraint": "要对比迁移成本"},
]
种子不需要多,20-50 条就够。关键是每条都有明确的受众 和具体的约束------这两个东西是差异化的锚点。
四、差异化参数矩阵
这是整套方案的核心。把"创意风格"拆成可枚举的维度:
# dimensions.py
ANGLES = {
"rookie_trap": "以新手第一次踩坑的视角,从错误认知写到恍然大悟",
"war_story": "以'我去年在一个项目里遇到这个问题'的叙事口吻",
"contrarian": "直接反驳一个广泛流传的观点,用数据或代码证明",
"interviewer": "模拟技术面试官追问的节奏,层层递进",
"tool_compare": "用表格/代码对比两个方案的真实差异,不站队",
}
FORMATS = {
"listicle": "清单体,3-7 条,每条有独立小标题",
"dialogue": "对话体,两个角色辩论或问答",
"tutorial": "步骤体,从环境准备到运行结果逐步展示",
"story": "叙事体,有时间线、有冲突、有结局",
"thread": "Twitter 风格的线程体,每条短句,逻辑递进",
}
HOOKS = {
"counterintuitive": "开头第一句必须是一个反直觉的事实断言",
"specific_number": "开头第一句必须包含一个精确数字(不是'很多',是具体值)",
"personal_failure": "开头第一句必须是'我搞砸了一次......'",
"myth_bust": "开头第一句必须否定一个常见说法",
}
现在你有 5 × 5 × 4 = 100 种组合。同一个种子,走不同组合,出来的东西完全不同。
五、Prompt 模板引擎
用 Jinja2 把参数注入模板,而不是在代码里拼字符串:
# prompt_builder.py
from jinja2 import Template
TEMPLATE = Template("""
你是一个有 10 年经验的科技自媒体创作者,擅长把技术话题写得让人停不下来。
## 任务
基于以下种子信息,生成一篇{{ platform }}平台的创意大纲。
## 种子信息
- 核心话题:{{ seed.topic }}
- 目标受众:{{ seed.audience }}
- 硬性约束:{{ seed.constraint }}
## 风格要求
- 叙事角度:{{ angle_desc }}
- 内容格式:{{ format_desc }}
- 开头钩子:{{ hook_desc }}
## 输出结构
请按以下结构输出(严格遵循):
1. **标题**:1 个主标题 + 2 个备选(每个标题必须包含具体技术名词,禁止泛泛而谈)
2. **开头钩子**:2-3 句话,必须命中指定的钩子类型
3. **核心内容大纲**:3-5 个要点,每个要点包含:
- 要点标题
- 一句话说明
- 是否需要代码示例(是/否)
4. **结尾互动**:一个能引发评论区讨论的问题
## 限制
- 不要写"大家好""今天我们来聊聊"这类废话开头
- 不要用"首先、其次、最后"这种结构词
- 代码示例必须是真实可运行的,不能是伪代码
""")
def build_prompt(seed, angle_key, format_key, hook_key, platform="小红书"):
return TEMPLATE.render(
seed=seed,
angle_desc=ANGLES[angle_key],
format_desc=FORMATS[format_key],
hook_desc=HOOKS[hook_key],
platform=platform,
)
模板里的限制条件很重要------它们是在对抗模型的"默认写作习惯"。没有这些约束,模型会退化成最安全的平庸输出。
六、批量异步调用
用 asyncio + aiohttp 并发调用,别一个一个串行等:
# batch_generate.py
import asyncio
import aiohttp
import json
from itertools import product
from seeds import SEEDS
from dimensions import ANGLES, FORMATS, HOOKS
from prompt_builder import build_prompt
API_URL = "https://api.openai.com/v1/chat/completions"
API_KEY = "sk-..."
async def call_llm(session, prompt, max_retries=3):
payload = {
"model": "gpt-4o",
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.85, # 创意任务需要一定随机性
"max_tokens": 1500,
}
for attempt in range(max_retries):
try:
async with session.post(API_URL, json=payload, headers={
"Authorization": f"Bearer {API_KEY}"
}) as resp:
result = await resp.json()
return result["choices"][0]["message"]["content"]
except Exception as e:
if attempt == max_retries - 1:
return f"ERROR: {e}"
await asyncio.sleep(2 ** attempt)
async def batch_generate():
# 生成参数组合(每个种子取 3 种不同组合,避免全量爆炸)
combos = []
keys = list(product(
list(ANGLES.keys()),
list(FORMATS.keys()),
list(HOOKS.keys())
))
for seed in SEEDS:
# 每个种子随机选 3 种组合
import random
selected = random.sample(keys, min(3, len(keys)))
for angle, fmt, hook in selected:
combos.append((seed, angle, fmt, hook))
print(f"共 {len(combos)} 个创意待生成")
async with aiohttp.ClientSession() as session:
tasks = []
for seed, angle, fmt, hook in combos:
prompt = build_prompt(seed, angle, fmt, hook)
tasks.append(call_llm(session, prompt))
results = await asyncio.gather(*tasks)
# 保存结果
with open("creative_output.jsonl", "w", encoding="utf-8") as f:
for (seed, angle, fmt, hook), content in zip(combos, results):
f.write(json.dumps({
"seed": seed["topic"],
"angle": angle,
"format": fmt,
"hook": hook,
"content": content
}, ensure_ascii=False) + "\n")
print("完成!结果已写入 creative_output.jsonl")
if __name__ == "__main__":
asyncio.run(batch_generate())
跑一次,几百个差异化创意就出来了。
七、后处理:去重与打分
批量生成最大的问题是有些组合出来的东西确实很烂。需要自动过滤一轮:
# postprocess.py
import json
from collections import Counter
def score_creative(item):
"""简单打分,过滤明显低质的"""
content = item["content"]
score = 0
# 有具体技术名词加分
tech_keywords = ["Python", "API", "SQL", "Docker", "FastAPI", "Django", "Flask"]
found = sum(1 for kw in tech_keywords if kw.lower() in content.lower())
score += min(found * 2, 10)
# 有代码块加分
if "```" in content:
score += 5
# 有具体数字加分
import re
numbers = re.findall(r'\d+', content)
if len(numbers) >= 3:
score += 3
# 标题包含"如何""为什么""vs"等加分
title_indicators = ["如何", "为什么", "vs", "VS", "对比", "避坑"]
if any(ind in content[:200] for ind in title_indicators):
score += 3
# 太短减分
if len(content) < 300:
score -= 5
return score
def deduplicate(results):
"""基于标题去重(简单版)"""
seen_titles = set()
unique = []
for item in results:
# 提取标题(假设第一行是标题)
first_line = item["content"].split("\n")[0][:30]
if first_line not in seen_titles:
seen_titles.add(first_line)
unique.append(item)
return unique
# 加载并打分
results = [json.loads(line) for line in open("creative_output.jsonl", encoding="utf-8")]
results = deduplicate(results)
scored = [(score_creative(r), r) for r in results]
scored.sort(key=lambda x: x[0], reverse=True)
# 输出 top 20
for score, item in scored[:20]:
print(f"[{score}分] {item['seed']} | {item['angle']} | {item['format']}")
print(f" {item['content'][:100]}...")
print()
八、一个真实输出示例
种子:{"topic": "Python 虚拟环境", "audience": "转行学编程的文科生", "constraint": "必须提到 conda 和 venv 的恩怨"}
参数组合:contrarian + story + personal_failure
模型输出(节选):
标题:我搞砸了 conda 和 venv 之后,才明白虚拟环境不是"可选技能"
开头钩子:我搞砸了一次部署,原因是我在 conda 环境里装了包,却用 venv 跑代码------这两个东西根本不该同时存在,但我花了 3 天才搞清楚。
核心内容大纲:
- conda 不是包管理器,是环境模拟器 ------ 一句话说明区别,配
conda listvspip list输出对比- venv 的干净与残酷 ------ 它只管 Python 包,不管系统依赖,所以轻量但"裸"
- 我犯的错误:混用 ------ 展示
which python在不同环境下的输出差异- 文科生的生存方案:只选一个 ------ 给出决策树:数据科学→conda,Web 开发→venv
结尾互动:你第一次遇到"包装了但 import 报错"是什么时候?
这个东西你直接拿去就能写。而且它和"5 个 Python 技巧"那种稿子完全不是一个物种。
九、进阶:让差异化更进一步
1. 平台适配层
同一个创意,小红书需要"情绪 + 干货 + 排版",B 站需要"节奏 + 反转 + 视觉提示",掘金需要"深度 + 代码 + 架构图"。加一层平台参数:
PLATFORMS = {
"xiaohongshu": "每段不超过 5 行,必须有用 emoji 分隔,语气像朋友安利",
"bilibili": "需要设计'悬念点'和'笑点'的位置标记,适合口播节奏",
"juejin": "需要完整的代码块和架构说明,语气专业克制",
}
2. 系列化生成
不要一个一个孤立选题,让模型一次性生成系列内容:
SERIES_TEMPLATE = """
基于话题"{{ topic }}",生成一个 5 篇的系列大纲,要求:
- 每篇之间有逻辑递进关系(不能简单并列)
- 第 1 篇是"钩子篇",用最低门槛吸引最广泛受众
- 第 5 篇是"深度篇",留住核心粉丝
- 每篇标注预估阅读/观看时长
"""
3. 竞品差异化注入
把你竞品的标题喂进去,让模型刻意避开:
AVOID_PATTERNS = [
"不要出现'终极指南'、'完整教程'、'一文搞懂'这类标题党词汇",
"不要和以下已有标题雷同:{competitor_titles}",
]
十、关键认知
这套方案的本质不是"让 AI 替你创作",而是把你脑子里的创意维度显式化、参数化,然后用工具批量探索组合空间。
- 模型负责生成和变形
- 你负责定义什么是"好"和什么是"不同"
- Python 负责把探索空间放大 100 倍
最终产出的 100 个创意里,可能只有 10 个能用。但问题是------你自己想 10 个差异化创意可能要花一周,而这套流程跑一次只要 20 分钟。
省下来的时间,你用来打磨那 10 个真正好的创意,才是正经事。