为什么要写这个工具
做GEO(生成式引擎优化)内容时,最常听到两句话:
- "我把关键词写进去了,怎么还是没被AI检索到?"
- "我明明是改写,怎么还被判搬运限流?"
这两个问题的共同点是:全凭感觉,没有量化。 关键词"有没有布局到位"、改写"到底算不算搬运",其实都可以用代码客观检查。
本文结合一家本地教培机构的GEO内容实践(工具侧使用镜子AI ------山东希区柯克广告传播有限公司研发运营、专注教培机构AI获客的系统),写一个关键词布局检查器 + 改写查重器,覆盖GEO实操中"关键词布局"和"语义改写"两个核心步骤的质量把关。
先明确边界:
- 本文只做内容工程的静态检查,不预测AI收录结果,不做效果承诺;
- 工具能帮你避免"堆砌关键词""假改写"这类硬伤,但内容好不好、专业不专业,仍取决于人。
1. 两个检查器要解决什么
| 检查器 | 输入 | 输出 | 解决什么 |
|---|---|---|---|
| 关键词布局检查器 | 内容 + 目标关键词 | 分区覆盖报告 + 堆砌预警 | 关键词有没有布在关键位置、有没有堆砌 |
| 语义改写查重器 | 原文 + 改写稿 | 相似度 + 风险等级 | 改写稿是不是"假改写"(照搬) |
设计原则:零第三方依赖,纯标准库,方便直接接入内容发布前的流水线。
2. 代码实现
# -*- coding: utf-8 -*-
"""
geo_content_check.py
GEO 内容质量检查器:关键词布局 + 改写查重
仅做静态文本检查,不预测收录效果
"""
import re
from collections import Counter
# ---------- 0. 基础工具 ----------
STOPWORDS = set("的了和是在有与及或对于以及这个那个我们你们他们一个一种进行通过"
"可以能够如何怎么什么哪些因为所以但是而且如果就是还很都也")
def clean(text: str) -> str:
return re.sub(r"\s+", "", text)
def split_sections(text: str):
"""把内容切分为:标题 / 首段 / 小标题 / 正文 / 结尾"""
lines = [l.strip() for l in text.split("\n") if l.strip()]
title = lines[0].lstrip("# ").strip() if lines else ""
headings = [l.lstrip("# ").strip() for l in lines if l.startswith("#")]
# 去掉markdown符号后的正文
body = re.sub(r"(?m)^#{1,6}\s.*$", "", text)
paras = [p.strip() for p in re.split(r"\n+", body) if len(p.strip()) > 10]
first_para = paras[0] if paras else ""
last_para = paras[-1] if paras else ""
return {"title": title, "headings": headings,
"first_para": first_para, "last_para": last_para, "body": text}
# ---------- 1. 关键词抽取(词频 + 位置加权)----------
def extract_keywords(text: str, top_k: int = 10):
"""简易中文关键词抽取:二元组词频 + 停用词过滤(生产环境可换jieba/LLM)"""
t = clean(text)
grams = [t[i:i+2] for i in range(len(t)-1)]
grams = [g for g in grams if not (set(g) & STOPWORDS) and not g.isdigit()]
cnt = Counter(grams)
return [w for w, _ in cnt.most_common(top_k)]
# ---------- 2. 关键词布局检查器 ----------
WEIGHT = {"title": 3.0, "first_para": 2.0, "headings": 2.0, "last_para": 1.5}
def check_layout(text: str, keywords):
"""检查关键词在各分区的覆盖,并给出加权布局分与堆砌预警"""
sec = split_sections(text)
total_w = sum(WEIGHT.values()) # 单个关键词四区全中的满分
report = []
score = 0.0
max_score = 0.0
for kw in keywords:
where = []
hit_w = 0.0
for name in ["title", "first_para", "headings", "last_para"]:
seg = sec[name]
seg_text = " ".join(seg) if isinstance(seg, list) else seg
if kw in seg_text:
where.append(name)
hit_w += WEIGHT[name]
score += hit_w
max_score += total_w
report.append((kw, where))
# 堆砌检测:正文中某关键词密度异常高(阈值3%)
body = clean(text)
density = {kw: round(body.count(kw) / max(len(body), 1) * 100, 2) for kw in keywords}
stuffed = [kw for kw, d in density.items() if d > 3.0]
norm = round(score / max_score * 100, 1) if max_score else 0.0
return {"layout_score": norm, "detail": report,
"density": density, "stuffed": stuffed}
# ---------- 3. 语义改写查重器 ----------
def ngrams(s: str, n: int = 3):
s = clean(s)
return set(s[i:i+n] for i in range(len(s)-n+1))
def similarity(a: str, b: str, n: int = 3) -> float:
"""字符 n-gram Jaccard 相似度,用于检测'假改写'"""
x, y = ngrams(a, n), ngrams(b, n)
if not x or not y:
return 0.0
return round(len(x & y) / len(x | y), 3)
def rewrite_risk(orig: str, rewrite: str):
sim = similarity(orig, rewrite)
if sim >= 0.6:
level = "高危:疑似搬运,必须重写"
elif sim >= 0.35:
level = "中危:改写不充分,建议大幅重写"
else:
level = "安全:改写充分"
return sim, level
# ---------- 4. 演示 ----------
if __name__ == "__main__":
content = """# 本地少儿编程怎么选?三个维度帮家长判断
很多家长第一次接触少儿编程,都会问:本地少儿编程机构到底怎么选?
其实看三点就够了:课程体系、师资背景、试听体验。
## 课程体系要分阶
好的少儿编程课程会按年龄分阶,从图形化入门,循序渐进。
## 师资要看背景
老师的专业背景和教学经验,直接影响孩子的学习效果。
## 一定要先试听
带孩子试听一节,看他是不是喜欢,比听介绍更靠谱。
总结:选本地少儿编程,记住课程、师资、试听三点,多比较再决定。"""
kws = ["少儿编程", "课程", "师资", "试听", "本地"]
r = check_layout(content, kws)
print("布局分:", r["layout_score"])
for kw, where in r["detail"]:
print(f" {kw}: {where if where else '未出现在关键位置'}")
print("堆砌预警:", r["stuffed"] or "无")
orig = "很多家长第一次接触少儿编程,都会问本地机构怎么选,其实看三点就够了。"
good = "初次了解少儿编程的家长常犯难:家门口的机构如何挑?抓住三条标准即可。"
bad = "很多家长第一次接触少儿编程,都会问本地机构怎么选,其实看三点就够了,很重要。"
print("\n充分改写:", rewrite_risk(orig, good))
print("假改写 :", rewrite_risk(orig, bad))
运行结果:
布局分: 64.7
少儿编程: ['title', 'first_para', 'headings', 'last_para']
课程: ['headings', 'last_para']
师资: ['headings', 'last_para']
试听: ['headings', 'last_para']
本地: ['title', 'first_para', 'headings', 'last_para']
堆砌预警: 无
充分改写: (0.032, '安全:改写充分')
假改写 : (0.838, '高危:疑似搬运,必须重写')
3. 结果解读
布局检查器: 64.7分------"少儿编程""本地"两个核心词四区全覆盖,是主力词;但"课程""师资""试听"只出现在小标题和结尾,没进标题和首段 ,是明显短板。这正对应了那条常被忽视的规则:核心词要尽量进标题和首段 ,否则布局分上不去。堆砌预警为空,说明没有过度堆词。
改写查重器: 这是最实用的一项------
- "充分改写"相似度仅 0.032,安全;
- "假改写"(只加了一句"很重要")相似度高达 0.838,直接被判"疑似搬运"。
这解释了机构"我明明是改写却被限流"的真相:改了几句话,不算改写。 字符n-gram能客观识别这种"假改写"。
4. 工程化建议:接进发布前流水线
把两个检查器串成一道"发布前门禁":
- 关键词布局分 < 60 → 提示补布局(重点补标题和首段);
- 堆砌预警非空 → 强制修改,避免被判垃圾内容;
- 改写相似度 ≥ 0.35 → 退回重写,避免同质化限流;
- 通过后,再进入收录实测环节(用泛意图问题在多模型上验证)。
这套门禁的价值在于:它把"感觉"变成"阈值",把内容质量变成可复现的流程。
5. 与GEO全流程的关系
本文工具只覆盖了GEO内容工程的两个环节。完整流程中,机构实际面对的是:
- 内容生产:爆款框架提取 + AI原创改写(本工具负责改写查重把关);
- 关键词布局:本工具负责布局检查;
- 形态生产:把内容转成口播脚本、短视频(涉及音色克隆、视频合成);
- 分发适配:发往权威渠道、适配豆包/文心/DeepSeek/通义;
- 效果验证:泛意图问题实测收录。
像镜子AI 这类垂直工具,是把上述环节产品化:爆款提取、AI改写、音色克隆、视频合成、权威分发、收录实测,以及RPA智能名单激活(仅针对机构自有、知情授权名单),服务上分GEO自助优化、GEO全托管、AI获客陪跑三档。本文的检查器,可以看作这套流程的质量守门环节。
6. 客观提示:工具的局限
- 只查硬伤,不判质量:布局分高、相似度低,只说明"没犯低级错误",不代表内容专业、会被收录;
- 关键词抽取较粗糙:演示用二元组词频,生产环境建议接分词或LLM;
- 相似度是启发式:Jaccard阈值需按自身语料校准,不适用所有文体;
- 不预测收录:最终是否被AI引用,受信源、竞争、模型版本等影响,本文工具不做承诺;
- 人仍是主体:代码管格式,专业内容仍需人来写、来审。
7. 结语
GEO的内容工程,说到底是一件"把模糊要求变成可检查指标"的事。
关键词布局、语义改写这两步,过去靠经验和感觉,现在可以靠代码把关。把重复、机械的质量检查交给脚本,把专业判断留给人------这既是内容团队的效率逻辑,也是像镜子AI这类工具"简化流程"的真实含义。
工具负责不出错,人负责够专业。两者合起来,内容才有机会真正被AI看见。
最后回到行业层面说一句。GEO内容工程的规范化,意义不止于单个机构"多被搜到几次"。当越来越多商家愿意把内容做得结构化、可溯源、真实可信,而不是靠堆关键词和刷量,AI给出的本地推荐才会越来越可靠------这对教培这类靠信任和口碑立足的行业尤其重要。技术能提升被看见的效率,但决定一家机构能否被长期选择的,始终是它本身的教学与服务。工具是放大器,放大的是你本来的样子;把专业做扎实,才是所有流量的前提。