
最近折腾了一个定时运行的短视频脚本生成流水线,踩了几个坑,这篇把问题说清楚。
市面上的"自动化无脸YouTube频道"教程套路都差不多:拉一个RSS订阅源,用for循环遍历条目,把每个条目POST给大模型,塞一句"写一个40秒的TikTok脚本",把返回结果写到文件。四十行代码,演示没问题。
然后你设好定时任务,那些没人写过的问题就开始冒出来了:
最后我把它做成了Apify actor(TikTok & YouTube Shorts脚本生成器),真正有意思的部分不是生成本身,而是上面这五个问题。以下是每个问题的解决方案,不管你用不用这个actor,自己写都能用上。
脚本不是一段文字。它是一个有时间轴的序列,每个段落有各自的用途。如果你的流水线把它当一坨文本处理,下游什么都干不了——没法匹配B-roll素材、没法做字幕时间轴、没法逐段重录。
所以输出格式是一个分段列表,每段都有标注:
{ "category": "tech", "topic": "A hidden phone setting", "duration": "40 seconds", "platform": "TikTok/YouTube Shorts", "wordCount": 96, "segments": [ { "label": "HOOK", "start": 0, "end": 3, "durationSec": 3, "speech": "Your phone has been throttling itself since the day you bought it.", "visual": "Close-up of hand pulling down settings panel, harsh overhead light" }, { "label": "BODY", "start": 3, "end": 33, "speech": "...", "visual": "..." }, { "label": "CTA", "start": 33, "end": 40, "speech": "...", "visual": "..." } ], "status": "ok", "generatedAt": "2026-08-27T14:02:11.884Z"
} 这个格式有两个关键点。
speech和visual是两个独立字段。不是在一个字段里内嵌舞台指示。你一旦要喂旁白给TTS(文本转语音)、匹配素材库的视频片段、或者生成字幕,就必须只有纯文字,不带任何括号里的噪音。如果你放任模型自己来,它会愉快地在旁白中间塞(cut to close-up),然后你的语音合成会把"cut to close-up"大声念出来。
start和end在音频生成之前就存在。这是计划。在你真正生成音频之后,会得到从文件中测量的actualStart和actualEnd——计划时长和实际时长的差距是整个流水线里最有用的调试数据。一个计划3秒、实测5.2秒的开场钩子,是绝对撑不过划走的。
这是大多数通用流水线产出垃圾的地方,而且不是通过在提示词里多问几句就能解决的。
科技类短视频和美妆类短视频,开场结构完全不同。科技类用反直觉的论点开场——你的手机一直在自我节流。美妆类用视觉结果开场,前后对比,不提论点。新闻类开场讲利害关系。游戏类在动作中开场,通常还截断一句话。儿童内容用观众能口头回答的问题开场。
一个提示词不可能同时做好这些,这就是为什么单提示词流水线会产出那种一眼就能认出来的垃圾风格——不管什么话题都套上"你知道吗……"这种通用开场。
所以分类是一等输入,不是随便一个话题字符串。tech、trends、beauty、fashion、sports、gaming、news、learning、kid friendly、music、general每个都有自己的角色设定、钩子策略和段落结构。
RSS模式,这是你要定时运行会用到的版本:
{ "mode": "rss", "categories": ["tech", "gaming"], "maxItems": 10, "duration": "40 seconds"
} 直接模式,当你有自己的主题来源——表格、爬虫、自己积累的选题库:
{ "mode": "direct", "items": [ { "category": "tech", "topic": "A hidden phone setting", "data": "Most phones ship with a battery saver that is off by default. Enabling it..." } ]
} 直接模式才是构建更大系统的关键。它让这个工具成为流水线的"一个环节"而不是整条流水线——任何能产出{category, topic, data}的东西都能喂给它。
如果你请求["tech", "gaming", "news"]且maxItems: 10,粗暴的实现会先走科技订阅源,生成10个科技脚本,然后停掉。你说要三个分类,结果只得到一个。
解决方案是把预算按分类分配,并且交错收集,而不是挨个抽干订阅源。这个bug你一旦见过就觉得显而易见,但在只有一个分类的测试场景下完全看不出来,很容易就上线了。
一个只有40字符摘要加上付费墙链接的RSS条目,里面没有任何信息。喂给模型说"根据这个写个脚本",模型会写——很自信,完全凭自己的先验知识编。那是一条打着你频道名号的捏造内容。
门槛很简单但有效:要么摘要至少500字符左右,要么提取的正文字数200以上,否则跳过这个条目。内容单薄的和带付费墙的页面直接丢弃,而不是给它添油加醋。
不管你用什么方案,在某个地方放一个内容长度门槛。这是总结型流水线和编造型流水线的区别。
小模型有时会返回无法解析的输出。不常见,但到了定时运行的规模,每周都会遇到几个。错误的做法是无限重试、让运行崩溃、或者悄悄丢弃条目。
正确的做法是分流:无法解析的生成结果进入独立的failed-generations数据集,标记"status": "unparseable",附上原因和返回的原始文本——而且不算钱,因为你没有拿到脚本。
最后这一点即使你自己写也值得设计。如果按token付费,垃圾输出和好输出的单价一样,你就没有任何信号来发现提示词在退化。把可计费的成功和不可计费的失败分开,提示词回归问题就变得可见了,而不只是贵。
failed-generations数据集也是最好的提示词调试语料。读二十个,你通常会发现某一类话题里某个特定措辞会可靠地把模型带偏。
这个actor默认运行内置的Llama 3.1 8B。不用OpenAI key,不用Anthropic key,不用另建账单关系——跑就生成。
这不是因为8B模型更强。确实不是。是因为短视频脚本接近小模型的理想任务:输出约100词、结构严格、按分类有强大的脚手架支撑。几乎所有质量都来自结构本身,模型原始能力贡献的很少。当脚手架干了大部分工作时,模型大小就不再是瓶颈了。
如果你不认同,有后路:把worker/目录里的worker部署到你自己的Cloudflare账号,然后作为一对传入workerUrl和workerSecret——光有URL没有secret会被拒绝,而且内置生成器自己的secret永远不会转发给自定义worker。
加上ElevenLabs key后,每个段落会得到一个audioUrl和实测时间:
{ "mode": "rss", "categories": ["tech"], "maxItems": 5, "elevenLabsApiKey": "<your key>", "voiceId": "21m00Tcm4TlvDq8ikWAM", "ttsModelId": "eleven_turbo_v2_5", "ttsConcurrency": 2
} 有两件事要注意。ElevenLabs直接跟你自己的账号结算,不走Apify——运行日志会提前估算字符数,让你提前看到账单影响。ttsConcurrency默认是2,因为免费版超了会限速;只有付费计划才往上调。
一个8B模型按严格模板输出100词,给你的是一个扎实的第一稿,不是成品脚本。实际上,哪些部分能用、哪些部分需要改,是有规律的:
基本不用动:分段结构和时长、视觉提示(虽然通用但那是真实的分镜列表,比空白页面强多了)、CTA。
通常需要过一遍:钩子。它是整个视频里影响力最大的七个词,也是小模型最弱的地方,因为好的钩子取决于了解你特定受众已经相信什么。大多数钩子都要重写,这是正常的——改一行比从头写五行强。
要注意:声明的语气比来源支持的更自信。内容长度门槛挡住了最糟糕的情况,但"研究人员建议"开头的摘要出来可能变成"研究人员证明"。录音之前,把speech字段和sourceUrl对照读一遍。
正确的心理模型是:这个工具替代了空白页和时长计算,不替代编辑判断。如果你生成10个留3个,它正在正常工作。
核心目的就是不手动跑这个。在Apify里,定时任务是actor上的一个cron表达式——0 7 * * *就是每天早上7点跑一批,输出积累在一个数据集里,你可以从任何地方拉取:
curl -s "https://api.apify.com/v2/datasets/$DATASET_ID/items?clean=true&format=json" \
| jq -r '.[] | select(.status == "ok") | "\(.topic)\n" + (.segments[] | " [\(.label)] \(.speech)")'
合理的每日流程是:早上生成10个 → 你扫一遍保留3个 → 那3个进分镜表 → 录制或合成 → 发布。流水线的职责是让"扫一遍"变得便宜。不是让你无人值守发布,而且我强烈反对把输出直接接到上传API上。价值在于降低第一稿的空白页成本,不是把人类从循环里拿掉。
maxItems每次运行上限50。共享推理配额,少量多次比一次搞大。generationConcurrency默认5(最高20),ttsConcurrency默认2(最高10)。runDeadlineSeconds,默认210)和单个条目超时。大批量开TTS会撞墙——拆开跑,别把两个都顶到上限。feedOverrides指向发布真实摘要的订阅源。垃圾进还是垃圾出,没有任何提示词能拯救一个只发标题的订阅源。这个actor是TikTok & YouTube Shorts脚本生成器——按事件计费,无法解析的生成不计费。
如果你打算自己写:值得抄的四件事,按重要性排序,是分段输出格式、按分类的钩子策略、内容来源的长度门槛、独立的失败数据集。模型调用是整件事里最无聊的部分。