文章生成质量很大程度取决于 brief。这篇在调用任何写作 API 前,校验 topic、主关键词、意图、受众与至少 3 个大纲要点,并粗检关键词堆砌。
先看清成品
- sample_ok → OK;sample_bad → INVALID。
- 3 项 pytest 通过。
合规提醒:Writesonic API 密钥在控制台激活后仅显示一次副本,勿提交公开仓库。SEO/AI 可见度类功能变化较快,以官网与文档为准;本文不编造价格。生成内容需避免抄袭与虚假宣传,并遵守目标市场广告法。
运行说明:无正式密钥。A/C 用 mock Chatsonic;对官方主机无效密钥实测 HTTP 401。B 为本地 SEO brief 校验。2026-10-23(Asia/Shanghai)pytest 通过。
做完之后你会得到什么?
| 成果 | 说明 |
|---|---|
seo_brief.py |
校验器 |
| 样例 JSON | ok/bad |
test_99b.py |
测试 |
需要准备哪些环境?
| 项目 | 说明 |
|---|---|
| Python | 3.10+ |
关键事实
| 事实 | 来源 |
|---|---|
| 搜索意图常用 informational/commercial/transactional/navigational | SEO 实践 |
| Writesonic 产品侧强调 SEO 与 AI 可见度,具体菜单以官网为准 | 定位说明 |
| 本校验不调用 Writesonic,可与任意生成器组合 | 实践 |
文件结构
99-writesonic-B-src/
├── seo_brief.py
├── sample_ok.json
├── sample_bad.json
└── test_99b.py
第 1 步:校验脚本
#!/usr/bin/env python3
"""Validate an SEO article brief before generating long-form content."""
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path
REQUIRED = ("topic", "primary_keyword", "search_intent", "audience", "outline")
def validate(brief: dict) -> list[str]:
errs = []
for k in REQUIRED:
if not brief.get(k):
errs.append(f"缺少字段: {k}")
kw = (brief.get("primary_keyword") or "").strip()
topic = (brief.get("topic") or "").strip()
if kw and len(kw) > 80:
errs.append("primary_keyword 过长")
if topic and kw and kw.lower() not in topic.lower() and kw.lower() not in " ".join(brief.get("outline") or []).lower():
errs.append("primary_keyword 未出现在 topic 或 outline 中")
intent = (brief.get("search_intent") or "").lower()
if intent and intent not in {"informational", "commercial", "transactional", "navigational", "信息", "商业", "交易", "导航"}:
errs.append(f"search_intent 不在常见集合: {intent}")
outline = brief.get("outline")
if outline is not None:
if not isinstance(outline, list) or len(outline) < 3:
errs.append("outline 至少 3 个 H2 级要点")
elif any(not str(x).strip() for x in outline):
errs.append("outline 含空项")
# discourage stuffing
if kw and outline:
joined = " ".join(str(x) for x in outline)
if len(re.findall(re.escape(kw), joined, flags=re.I)) > 5:
errs.append("outline 疑似关键词堆砌")
return errs
def main(argv=None) -> int:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("brief_json")
args = ap.parse_args(argv)
brief = json.loads(Path(args.brief_json).read_text(encoding="utf-8"))
errs = validate(brief)
if errs:
print("INVALID")
for e in errs:
print("-", e)
return 1
print("OK")
print(json.dumps({"topic": brief["topic"], "h2": len(brief["outline"])}, ensure_ascii=False))
return 0
if __name__ == "__main__":
raise SystemExit(main())
第 2 步:测试
from pathlib import Path
import json
import subprocess
import sys
from seo_brief import validate
HERE = Path(__file__).resolve().parent
def test_ok():
assert validate(json.loads((HERE / "sample_ok.json").read_text())) == []
def test_bad():
errs = validate(json.loads((HERE / "sample_bad.json").read_text()))
assert errs
def test_cli():
r = subprocess.run(
[sys.executable, str(HERE / "seo_brief.py"), str(HERE / "sample_ok.json")],
capture_output=True,
text=True,
)
assert r.returncode == 0 and "OK" in r.stdout
第 3 步:运行
OK
{"topic": "如何为 B2B SaaS 写产品更新邮件", "h2": 4}
INVALID
- 缺少字段: audience
- primary_keyword 未出现在 topic 或 outline 中
- search_intent 不在常见集合: viral
- outline 至少 3 个 H2 级要点
... [100%]
3 passed in 0.03s
怎么检查结果?
- 主关键词出现在 topic 或 outline。
- outline ≥ 3 条非空。
调试示例
关键词不在标题里。 有的 brief 把关键词只放在「备注」。本规则要求出现在 topic 或 outline,逼你把检索词写进结构。
常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| 中文意图词 | 已部分支持 | 可扩展集合 |
| 多个关键词 | 本篇单主词 | 次要词放 outline |
| 通过了仍写不好 | brief≠成品 | 仍需人工改稿与事实核对 |
模板规则
- 不自动发布未经审的稿。
- 不堆砌关键词。
- 不编造引用数据。
项目资源
| 用途 | 地址 |
|---|---|
| Writesonic 官网 | https://writesonic.com/ |
| Chatsonic 说明(官方) | https://docs.writesonic.com/docs/chatsonic-api |
| API key(官方) | https://docs.writesonic.com/reference/finding-your-api-key |
brief 合格,生成才有讨论意义。