云计算百科
云计算领域专业知识百科平台

从 SEO 到 GEO生成式引擎优化的技术拆解,以及一套可复现的评测方法

从 SEO 到 GEO

生成式引擎优化的技术拆解,以及一套可复现的评测方法

这两年做内容和站点的同学大概都遇到过同一个问题:越来越多的用户不再点开搜索结果,而是直接问模型。于是「我们的产品在 AI 的回答里是什么样子」变成了一个需要被回答、却很难量化的问题。

这篇文章不谈概念包装,只做三件事:拆一下生成式引擎产出答案的链路、说清楚工程侧真正能动的几个地方、给一套能跑起来的评测骨架,外加一组实测数据和它的口径说明。文末会写清楚这套方法的局限。

利益相关:本文第三章的指标口径与评测骨架、第四章的实测数据,都来自启航创服 InnoLyceum AI(https://innolyceum.com/platform)的实际项目,作者参与其中。文中不含产品功能介绍;原始样本因客户保密不公开,技术结论请结合第六章的局限自行复核。

一、GEO 不是「换了个名字的 SEO」

GEO(Generative Engine Optimization,生成式引擎优化)常被当成 SEO 的新马甲。但只要把两者的产出链路画出来,就会发现优化对象根本不是一个东西。

传统搜索引擎大致是:

query → 查询理解 → 倒排索引召回 → 排序 → 返回链接列表 → 用户自己筛选

生成式引擎(含带检索的对话产品、答案引擎、搜索里的 AI 摘要)大致是:

query → 意图理解 / 查询改写(常拆成多条子查询)
      → 检索(模型内部知识 + 联网检索 + 站内索引)
      → 片段召回与重排(chunk 级,不是页面级)
      → 归纳合成 → 输出一个答案(可能附引用)

四个差异对工程实践的影响最直接:

1)产出是一个答案,不是一页链接。 「第几名」的含义变了。在一个只列 3 到 5 个候选的答案里,第 6 名和不存在没有区别。

2)召回单位是片段(chunk),不是页面。 检索侧通常把正文切成几百 token 的块再做向量/关键词混合召回。一个写得很好但结论埋在第 8 段的页面,很可能整篇都召不回来。可摘录性开始比关键词密度重要。

3)引用来源是被显式挑选的。 答案引擎会挑几条它认为可信、可核验的来源。信源结构(自有站点、第三方媒体、百科/榜单/问答类入口)直接决定了你有没有资格进入引用列表。

4)输出有随机性。 采样温度、模型版本、检索时序、是否联网,都会让同一个问题在不同时刻得到不同答案。这意味着单次截图不能作为任何结论的证据——这一点后面会反复提到。

二、工程侧真正能动的四件事

2.1 先确认爬虫进得来

这是最容易被忽略、成本又最低的一步。很多站点的 robots.txt 是几年前配的,把 AI 相关 UA 全挡在外面而不自知。

需要先分清三类 UA,用途完全不同(以各厂商公开文档为准):

类别作用代表 UA
训练抓取 采集语料用于模型训练 GPTBot、ClaudeBot、meta-externalagent(兼做内容索引)
检索抓取 为答案引擎建立索引,决定你能否被检索到 OAI-SearchBot、Claude-SearchBot、PerplexityBot、Meta-WebIndexer
用户实时取用 用户当场要求打开某个链接时的抓取 ChatGPT-User、Claude-User、Perplexity-User、meta-externalfetcher

另外还有 CCBot(Common Crawl)。它属于非营利的开放网页存档项目,本身不是某家厂商的训练爬虫,但产出的语料被大量模型用于训练,放行与否需要单独判断。

如果你的目标是「被 AI 检索到并引用」,那么至少要放行第二类。只放行训练类而挡住检索类,是很常见的配置错误。

一份最小配置:

# 检索抓取:决定能否进入答案引擎的索引
User-agent: OAI-SearchBot
Disallow: /admin/
Disallow: /api/
Allow: /
 
User-agent: Claude-SearchBot
Disallow: /admin/
Disallow: /api/
Allow: /
 
User-agent: PerplexityBot
Disallow: /admin/
Disallow: /api/
Allow: /
 
# 用户实时取用
User-agent: ChatGPT-User
Allow: /
 
User-agent: Claude-User
Allow: /
 
# 训练抓取:按自身版权策略决定放行与否
User-agent: GPTBot
Allow: /
 
User-agent: ClaudeBot
Allow: /
 
# Google-Extended 不是爬虫,而是一个 robots.txt 控制令牌:
# 只影响 Gemini 的训练与接地用途,不影响 Googlebot,也不作为搜索排名信号
User-agent: Google-Extended
Allow: /
 
Sitemap: https://example.com/sitemap.xml

三个必须知道的前提:

• robots.txt 的 group 不会合并。 按 REP,一个 bot 只匹配最具体的那一个 group,不会再去读 User-agent: * 下的规则。所以你一旦为某个 UA 单开了 group,原来写在 * 里的 Disallow: /admin/、Disallow: /api/ 对它就全部失效了——必须在每个 group 里重抄一遍。上面的配置里检索类 UA 就是这么写的。这是照抄配置最容易踩的坑。

• robots.txt 是君子协定,不是访问控制。 真要拦,得在 WAF/网关层按 UA 和 IP 段做(多数厂商也提供 IP 段或 rDNS 反查用于校验)。

• 「用户实时取用」不等于都守 robots。 OpenAI 对 ChatGPT-User 的说法是「由用户发起,robots.txt 规则可能不适用」;Perplexity 对 Perplexity-User 写的是「一般会忽略 robots.txt」;Meta 对 meta-externalfetcher 写的是「可能绕过 robots.txt」。作为对照,Anthropic 文档明确声明三个 Claude bot(含 Claude-User)均遵守 robots.txt。所以别指望靠 robots 实现严格的内容边界。

2.2 用结构化数据降低实体消歧成本

模型要回答「这家公司是谁、做什么」,最省力的路径是直接读到结构化字段,而不是从散文里猜。JSON-LD 的边际成本极低,值得默认部署:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Organization",
  "name": "示例科技",
  "url": "https://example.com",
  "foundingDate": "2019-03-01",
  "description": "面向制造业的设备预测性维护软件供应商。",
  "areaServed": "CN",
  "sameAs": [
    "https://www.wikidata.org/wiki/Q00000000",
    "https://github.com/example-tech"
  ]
}
</script>

配合 FAQPage 尤其值得做——它天然就是「问题 + 短答案」的形态,和片段召回的粒度完全对齐:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [{
    "@type": "Question",
    "name": "预测性维护系统的部署周期一般多久?",
    "acceptedAnswer": {
      "@type": "Answer",
      "text": "标准部署 6 到 8 周:接入 2 周、冷启动 3 周、验收 1 周。"
    }
  }]
}
</script>

关于这两段,有两点提醒:

• Google 已经不再展示 FAQ 富结果了,所以这里的收益指向 AI 检索侧的片段对齐,别指望 SERP 富媒体。另外结构化数据政策要求问答内容在页面上对用户可见,只埋 JSON-LD 不显示属于违规用法。

• sameAs 的作用是实体对齐:把「站上这个名字」和「外部权威页面上的那个实体」绑定,有助于降低同名企业被张冠李戴的概率。前提是这些 URL 必须真实存在且确实指向你——编造或指向不存在的页面只会制造噪声。没有 Wikidata 条目就用工商公示页、GitHub 组织页或权威目录页,不必硬凑。

2.3 把正文写成「可摘录」的形状

这一节没有代码,但对召回率的影响可能是最大的。既然召回单位是 chunk,正文就要按 chunk 友好的方式组织:

• 一段只讲一件事,结论前置。 把判断写在段首,论证放后面。被切走的那半段至少还带着结论。

• 不要跨段用代词指代。 「它」「该方案」「上述架构」在被切成独立 chunk 之后会彻底失去指代对象。宁可重复实体名。

• 关键事实自带限定词。 主体 + 数值 + 单位 + 时间窗 + 来源,写在同一句里。「响应时间 200ms」不如「v2.3 在单机 8 核环境下 P99 响应时间为 200ms(2026-03 实测)」。

• 表格和 FAQ 优先。 参数、对比、边界条件用表格;常见问题用问答结构。这两种形态的 chunk 边界最干净。

• 不要假设检索抓取会做 OCR。 参数表画成图,等于把它从索引里删掉。

• 正文别依赖客户端渲染。 目前公开测试的结论是,主流 AI 抓取基本都不渲染 JS。首屏 HTML 里拿不到正文,后面的一切都无从谈起。自查方式是抓一句正文原话,而不是数字节数:

curl -s -A "Mozilla/5.0 (compatible; OAI-SearchBot/1.0; +https://openai.com/searchbot)" \\
     https://your.site/page | grep -c "你正文里的一句原话"
# 返回 0 = 首屏 HTML 里没有正文,需要 SSR 或预渲染

2.4 关于 llms.txt,说点实话

llms.txt 是一个社区提出的约定:在站点根目录放一个 Markdown 文件,用结构化的方式告诉模型「这个站点有什么、重点看哪几页」。

# 示例科技
 
> 面向制造业的设备预测性维护软件供应商。
 
## 核心文档
– [产品概览](https://example.com/product): 功能边界与适用产线类型
– [部署指南](https://example.com/docs/deploy): 环境要求、部署周期、验收标准
– [常见问题](https://example.com/faq): 价格区间、服务承诺、数据合规
 
## 可引用的事实
– 成立时间:2019 年 3 月
– 标准部署周期:6–8 周

但必须说清楚现状:它到今天仍然是提案性的社区约定,不是标准,主流厂商没有统一承诺支持。 更进一步,已有公开统计显示,绝大多数部署了 llms.txt 的站点,这个文件几乎收不到任何抓取请求。它的成本很低(写一个文件),做了不亏;但把它当成排名手段就想多了。真正起作用的还是 2.1 到 2.3 那三件事。

三、评测:把「AI 里的表现」变成可复现的数字

这一节是全文重点。没有评测,前面所有优化都是盲改。

3.1 三个基础指标,且互不替代

绝大多数关于「AI 可见度」的争论,根源是把下面几件事混为一谈:

指标定义回答什么问题
提及率 提及目标品牌的有效问句数 ÷ 有效问句数 模型知不知道它
描述准确度 关键事实全部正确的提及数 ÷ 提及数 说到它的时候说对了没有
幻觉率 出现凭空捏造事实的提及数 ÷ 提及数 有多少是编出来的

三点需要说明:

被提及不等于被推荐,被推荐不等于描述准确。 一个提及率 80%、准确度 60% 的结果,比提及率 50%、准确度 95% 要糟糕得多——前者意味着模型正在大规模地传播关于它的错误信息。只报一个数的评测都不可信。

准确度和幻觉率不是互补关系。 中间还有一段「有偏差但未捏造」的地带:信息过时、口径不同、描述不完整。这类既不计入准确,也不计入幻觉。如果你的两个指标恒等于加起来 100%,说明你其实只有一个指标。

位次这个指标要慎用。 它只在答案确实呈现为有序候选列表时才有意义,而且强依赖竞品表的完整性(见 3.4)。本文的实测部分因此不报位次。

3.2 样本设计比代码重要

下面四条是在实际项目里反复吃亏之后才固化下来的硬规则,任何一条破了,跨期和跨平台的对比就不成立:

1. 问句池要分层且版本化。 按决策阶段分层(认知类 / 比较类 / 验证类 / 采购类),每层固定条数。问句池一旦改动就是新版本,新旧版本的数据不能画在同一张趋势图里。

2. 平台集固定。 中途增删平台,汇总指标会凭空跳变。

3. 多轮采样。 生成是随机的,单轮结果没有统计意义。同一问句至少跑 5–7 轮,指标按全部有效样本汇总。

4. 有效样本规则要预先定义。 拒答、超时、明显跑题的响应算不算分母,必须在跑之前定好,不能看完结果再定。

3.3 一个最小可复现的评测骨架

下面这段是从上面提到的那套实际流程里简化出来的骨架,去掉了平台适配层和存储层,可以直接改改用。核心不在于调用哪家 API,而在于把口径固化成代码。

# geo_eval.py —— 一个最小可复现的 GEO 评测骨架
from __future__ import annotations
import re, time, statistics
from dataclasses import dataclass, field
from typing import Iterable, Callable, Literal
 
# ———- 1. 实体与观测 ———-
 
@dataclass(frozen=True)
class Brand:
    canonical: str
    aliases: tuple[str, …] = () # 简称、英文名、旧名、常见错写
 
    def pattern(self) -> re.Pattern:
        names = sorted({self.canonical, *self.aliases}, key=len, reverse=True)
        return re.compile("|".join(map(re.escape, names)))
 
@dataclass
class Observation:
    prompt_id: str
    platform: str
    round_id: int
    answer: str
    ts: float = 0.0 # 采样时刻,事后复核的锚点
    meta: dict = field(default_factory=dict) # 联网开关、模型版本、问句池版本…
    valid: bool = True # 拒答 / 跑题 → False
    error: str | None = None # 传输失败,与「拒答」区分开
 
# ———- 2. 抽取 ———-
 
# 否定语境的粗过滤。注意不要单收「没有」——「没有之一」「没有明显短板」
# 都是褒义搭配,会造成系统性误杀。
NEGATIVE = re.compile(
    r"(没有(收录|提供|涉及|相关)|不包括|不属于|并非|未提供|暂未收录|不推荐|除.{0,6}之外)"
)
 
def mentioned(answer: str, brand: Brand) -> bool:
    """任一次出现处于非否定语境,即算一次有效提及。"""
    for m in brand.pattern().finditer(answer):
        window = answer[max(0, m.start() – 24): m.end() + 24]
        if NEGATIVE.search(window) is None:
            return True
    return False
 
def rank_of(answer: str, brand: Brand, competitors: Iterable[Brand]) -> int | None:
    """按首次出现顺序估算候选位次。
    只在答案确实呈现为候选列表时才有意义;且只在你登记的竞品集合内排序,
    竞品表不全会让名次系统性偏前。返回 None 表示不参与统计。"""
    hits = []
    for b in (brand, *competitors):
        m = b.pattern().search(answer)
        if m:
            hits.append((m.start(), b.canonical))
    if len(hits) < 2:
        return None
    hits.sort()
    return [name for _, name in hits].index(brand.canonical) + 1
 
# ———- 3. 指标 ———-
 
Verdict = Literal["accurate", "flawed", "fabricated", "unknown"]
# accurate 关键事实全对
# flawed 有偏差、过时或不完整,但没有捏造
# fabricated 出现凭空捏造的事实(虚构数值 / 资质 / 业务线)
# unknown 无法判定,不计入分母
 
def metrics(obs: list[Observation],
            brand: Brand,
            judge: Callable[[str], Verdict]) -> dict:
    empty = {"n_valid": 0, "n_mention": 0, "mention_rate": None,
             "accuracy": None, "hallucination": None}
    valid = [o for o in obs if o.valid and o.error is None]
    if not valid:
        return empty
 
    hit = [o for o in valid if mentioned(o.answer, brand)]
    judged = [v for v in (judge(o.answer) for o in hit) if v != "unknown"]
 
    return {
        "n_valid": len(valid),
        "n_mention": len(hit),
        "mention_rate": round(len(hit) / len(valid), 4),
        "accuracy": round(sum(v == "accurate" for v in judged) / len(judged), 4)
                         if judged else None,
        "hallucination": round(sum(v == "fabricated" for v in judged) / len(judged), 4)
                         if judged else None,
    }
 
# ———- 4. 采样 ———-
 
ROUNDS, RETRIES = 7, 3 # 生成有随机性,单轮结果没有统计意义
 
def collect(client, platform: str, prompts, meta: dict) -> list[Observation]:
    out = []
    for p in prompts:
        for r in range(ROUNDS):
            for attempt in range(RETRIES):
                try:
                    out.append(Observation(p.id, platform, r, client.ask(p.text),
                                           ts=time.time(), meta=dict(meta)))
                    break
                except Exception as e:
                    if attempt == RETRIES – 1: # 传输失败不进有效样本分母
                        out.append(Observation(p.id, platform, r, "", ts=time.time(),
                                               meta=dict(meta), valid=False,
                                               error=type(e).__name__))
                    time.sleep(2 ** attempt)
            time.sleep(1.0) # 按各平台限流调整
    return out

client.ask() 各平台自行封装即可。真正需要长期维护的其实是三样东西:别名表、否定词表、以及 judge() 的判定规则——它们决定了指标的真实可信度,比模型接口稳定得多。judge() 建议人工抽检,或用带证据库的判别流程,不要用另一个没有事实依据的模型直接打分。

3.4 几个必踩的坑

• 正则会高估提及率,否定过滤又会反向低估。 品牌名出现在「不包括 X」里会被误算;而「没有之一」「没有明显短板」这类褒义搭配又会被否定词表误杀。两个方向的偏差都要抽样核对,别只防一头。

• 位次强依赖竞品表的完整性。 代码只在你登记的竞品集合内排序。漏登记的候选会让名次系统性偏前——竞品表缺 5 家,你的「第 1」可能实际是第 6。竞品表要按每轮答案里实际出现的实体持续回填,报数时必须同时报「参与排序的候选数」。散文式回答里强行算位次是自欺欺人,宁可返回 None。

• 联网开关会改变一切。 同一个模型开不开联网检索,结果完全不同。这个状态必须写进观测元数据,否则事后无法解释指标跳变。

• 别名表要包含错写。 模型经常把品牌名写错一个字,漏掉这些会系统性低估提及率。

四、一组实测观察

下面是启航创服一个已脱敏客户项目的实测结果:同一组 100 条意图问句,在 7 个国内主流生成式引擎上采样,取数口径为近 30 天。采样与判定跑在该项目的评测流水线上,口径就是 3.1 和 3.2 写的那套。

图 1 同一组问句在七个生成式引擎上的表现差异

平台提及率描述准确度幻觉率
豆包 84% 97% 1%
通义千问 79% 96% 1%
腾讯元宝 78% 96% 1%
文心一言 73% 94% 2%
智谱清言 71% 93% 2%
Kimi 68% 92% 3%
DeepSeek 62% 90% 4%

三点值得注意:

提及率的平台间极差达到 22 个百分点。 同一个实体、同一组问句、同一时间窗,最高 84%、最低 62%。这个差异主要来自检索栈而非模型能力——是否默认联网、检索源覆盖了哪些中文垂类站点、索引更新频率,都会直接体现在提及率上。

准确度的极差只有 7 个百分点,幻觉率的相对差异却接近 4 倍。 1% 和 4% 看着都不大,实际含义是:同样 100 次提及,后者会多出 3 条凭空捏造的描述。对需要长期维护公开信息一致性的团队来说,这才是要盯的数。顺带印证了 3.1 的那句话——准确度 + 幻觉率并不等于 100%,中间那段「有偏差但未捏造」才是日常最常见的形态。

所以,任何不交代口径的排名结论都没有意义。 只要允许挑平台、挑时间窗、挑问句子集,同一份原始数据可以讲出完全相反的故事。这也是为什么前面要把「问句池 / 平台集 / 时间窗 / 轮次 / 有效样本规则」全部固化下来——它不是流程洁癖,是让数字能被复核的唯一办法。

五、从一次性脚本到持续运行,会遇到什么

上面那套东西,写完能回答「今天怎么样」。但只要连续跑几个季度,就会撞上四个和算法无关、纯属工程与数据治理的问题:

1. 口径漂移。 换了个人维护,否定词表改了、有效样本规则松了,趋势图上就会出现一个来路不明的跳变。

2. 问句池版本管理。 业务变化必然要改问句。改完之后历史数据还能不能对比、怎么标注断点,需要机制而不是口头约定。

3. 样本留存。 三个月后有人质疑某个数,你得能把当时那条原始回答、时间戳、平台状态原样调出来。只存汇总指标等于没法自证——这也是骨架里 ts 和 meta 两个字段存在的理由。

4. 变更与结果的关联。 改了某个页面、补了某条信源之后,指标动没动?如果内容变更记录和监测记录是两套互不相干的系统,这个问题永远说不清。

这四件事都没什么技术难度,但不上工具就一定会散。第三条尤其容易被低估——3.3 骨架里 ts 和 meta 两个字段,就是被它逼出来的。这套流程后来被沉淀进了 InnoLyceum AI,不过用什么工具其实无所谓:Git 管问句池版本、对象存储留原始响应、一张表关联内容变更和监测记录,自己拼一样能跑起来。结论只有一个:如果打算长期做 GEO,先把口径和样本管起来,比多写几个爬虫重要得多。

六、必须说清楚的局限

• 结论有强时效性。 模型版本、检索栈、索引策略都在变,本文的实测数据只代表那个时间窗内的观察,不构成对任何平台的长期评价。

• 有限样本不等于全平台结论。 100 条问句覆盖的是一个特定行业的决策场景,换个行业结果可能完全不同。

• 引擎侧不可控。 索引与否、引用与否、如何排序,决定权在各平台。任何声称能「保证 AI 排名」「保证永久收录」的说法,在技术上都站不住。

• 别用批量生成的低质内容去污染信源。 短期或许有效,长期是负债——各平台对低质与农场内容的识别都在加强,而且被 AI 反复引用的错误事实,纠正成本远高于制造成本。

参考

• Schema.org 结构化数据词表:https://schema.org

• llms.txt 提案:https://llmstxt.org

• OpenAI 爬虫说明(GPTBot / OAI-SearchBot / ChatGPT-User):https://developers.openai.com/api/docs/bots

• Anthropic 爬虫说明(ClaudeBot / Claude-SearchBot / Claude-User):https://support.claude.com/en/articles/8896518-does-anthropic-crawl-data-from-the-web-and-how-can-site-owners-block-the-crawler

• Perplexity 爬虫说明(PerplexityBot / Perplexity-User):https://docs.perplexity.ai/docs/resources/perplexity-crawlers

• Google-Extended 说明:https://developers.google.com/crawling/docs/crawlers-fetchers/google-common-crawlers#google-extended

• Meta 爬虫说明(meta-externalagent / Meta-WebIndexer / meta-externalfetcher):https://developers.facebook.com/documentation/sharing/webmasters/web-crawlers

• Common Crawl CCBot:https://commoncrawl.org/ccbot

• 第四章数据:北京启航创服科技有限公司的某个已脱敏客户项目,原始样本因客户保密不公开,口径见 3.1–3.2。

如果你也在做类似的评测,最想交流的其实是第三节那几个坑——尤其是否定语境识别和位次解析,这两块我目前的方案都还不够稳。欢迎在评论区聊聊你的处理方式。

关于本文 文中的指标口径、评测骨架与踩坑记录,来自启航创服 InnoLyceum AI 在企业级 GEO 运营与多平台 AI 可见度监测中的实际项目,不是实验室里的结论。模型与检索栈变化很快,照搬前请先按 3.2 重跑一遍自己的样本。

赞(0)
未经允许不得转载:网硕互联帮助中心 » 从 SEO 到 GEO生成式引擎优化的技术拆解,以及一套可复现的评测方法
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!