课程评价被 AI 断章取义之后:ClaimReview 事实核查标记的落地与校验
上周三上午,运营同事甩给我一个截图:某 AI 搜索引擎在回答「XX 课程值不值得买」时,引用了一句学员评价——「讲得太浅,根本学不到东西」——但完整评价的原句是「对零基础同学来说讲得太浅,根本学不到东西;不过有两年开发经验的话节奏刚好」。AI 把前半句截出来当了结论,评论区底下的用户已经开始问退款流程了。
这条评价的原始页面上,我们其实早就标了 Review 结构化数据。问题在于:Review 描述的是"用户打了什么分",AI 引擎引用时并不会替你做上下文还原。它只挑语义上最"结论化"的片段。要对抗这种断章取义,思路不是删差评,而是把平台自己核实过的结论——比如「7 天可退款是否属实」「就业率数据出处」——用事实核查标记明确告诉爬虫:这句话是经过核查的,核查结论是什么,证据在哪。这就是 Schema.org 的 ClaimReview 类型要干的事。
这篇文章记录我们平台在 40 天里把 ClaimReview 从零落到线上、并通过 Google 事实核查搜索功能(Fact Check Explorer / 搜索结果中的事实核查标签)收录的全过程,包括字段逐项写法、三次真实的校验报错,以及改造前后 AI 回答口径的对照。
为什么 Review 挡不住断章取义
先把三个容易混的类型掰开。我们团队最初也在这三者之间摇摆过一周,最后靠一张表定下来:

| 类型 | 语义 | 典型场景 | 对抗断章取义的效果 |
|---|---|---|---|
| Review | 对某事物的个体评价,含评分 | 学员给课程打 3 星并写评语 | 无。只声明"这是个评价" |
| AggregateRating | 多条评价聚合出的均分 | 课程页显示 4.6 分(1287 人评价) | 无。AI 可能只引用均分,照样不看上下文 |
| ClaimReview | 对某条具体主张的核查结论 | 平台核实「7 天可退款」属实,附证据链接 | 有。把"结论 + 证据"结构化给爬虫 |
关键区别在于:Review 和 AggregateRating 的对象是「课程」这个实体,而 ClaimReview 的对象是「一句可被判断真伪的主张」。断章取义的本质是 AI 把一段话压缩成了主张却不给出处和限定条件——那你就主动把带限定条件的主张核查结果递给它。
我们上线 ClaimReview 后两周,那类「引用半句差评当结论」的 AI 回答,从抽样 20 条中出现 7 条降到 1 条。数据是我们自己抽样统计的,样本小,只当参考。
还有一个此前写过的 reviewAspect(评价维度拆分),它解决的是「评价覆盖了哪些维度」,和「某条结论是否经过核查」是两码事,别混着用。
ClaimReview 字段逐项拆解
ClaimReview 的完整定义在 Schema.org 官网,实际能通过 Google 校验的核心字段是 5 个。逐个说,重点是每个字段的坑。
claimReviewed:被核查的主张原文
必须是一句完整、可独立判断真伪的话,不能是关键词拼接。
"claimReviewed": "该课程支持购买后 7 天内无理由全额退款"
我们第一版写的是「7 天退款」,被 Rich Results Test 提示缺少可核查语义。改成完整陈述句后通过。这条规律很朴素:主张必须自带主谓宾,因为 AI 引擎会直接把这段文本作为被核查对象的展示文本。
reviewRating:用 Rating,不要用 AggregateRating
这是最容易踩的坑。reviewRating 字段的类型是 Rating,字段值里 bestRating 最常用 1(属实)到 3(不属实)的刻度——Google 事实核查功能的惯例是:
| ratingValue | 语义 |
|---|---|
| 1 | 属实(True) |
| 2 | 部分属实(Mixture) |
| 3 | 不属实(False) |
"reviewRating": {
"@type": "Rating",
"ratingValue": "1",
"bestRating": "3",
"worstRating": "1",
"alternateName": "属实:退款政策以服务协议第 4.2 条为准"
}
用 AggregateRating 会直接报错「Invalid type for reviewRating」,后面校验一节细说。
url:必须指向核查页面本身
注意,不是指向被核查内容所在页,而是这次核查结论自己的落地页。我们最初的写法是 url 指向课程详情页,校验通过但 Google 收录时被判定为"核查结论与页面内容不匹配"。正确做法是给每个核查结论建一个独立的核查说明页(哪怕内容不长),url 就填这个页面。
author:核查主体
平台自己核查就写平台名。type 用 Organization。如果配合第三方核查机构,写对方的名字——但 author 是谁,谁就要对结论负责,这个字段在 E-E-A-T 语境下权重不低。
reviewBody:核查过程的说明
放核查依据、证据摘要。我们固定写三段式:核查了什么材料、材料来源、结论适用范围。比如「依据《服务协议》4.2 条及退款系统日志,7 天无理由退款自 2025 年 3 月起生效,不适用于直播课首节开课后超过 48 小时的订单」——适用范围这句尤其重要,它是防止 AI 再次断章取义的第二道保险。
原理:AI 引擎为什么会优先引用带核查标记的内容
这一节讲机制,不操作。
生成式搜索引擎(无论是 Google AI Overviews 还是独立 AI 问答产品)在检索阶段普遍有一条隐式的内容分层:带可信元数据的内容 > 纯文本内容。ClaimReview 之所以有效,是因为它把三件事压缩进了一个机器可读的单元:
- 主张的可识别性。claimReviewed 是一句自包含的陈述,AI 做引用抽取时可以直接对齐"我要回答的问题"和"已核查的主张",不需要自己做语义压缩——而断章取义恰恰发生在压缩环节。
- 结论的权威锚点。reviewRating + author 提供了"谁说的、说什么结论",这在生成回答时给了模型一个可以直接引用的权威节点,优先级高于从评论文本里自己提炼。
- 可追溯性。url 让 AI 可以回链到核查页,用户点进去看到的是带上下文和适用范围的完整说明。生成式引擎普遍偏好给"点进去不会打脸"的来源加权——这也是 GEO 的底层逻辑之一:不是让 AI 多提你,而是让 AI 引用你时更省事、更安全。
另外一层数据流是这样的:
flowchart LR
A[学员评价/平台政策原文] --> B[人工筛选可核查主张]
B --> C[核查小组取证]
C --> D[生成 ClaimReview JSON-LD]
D --> E[注入核查说明页]
E --> F[Google 事实核查收录]
E --> G[AI 爬虫抓取]
G --> H[AI 回答直接引用核查结论]
平台内部也有一条流水线,主张从哪来、谁审、谁发布,用 Mermaid 画出来给新同事看:
flowchart TD
A[来源: 差评/退款投诉/就业率数据] --> B{是否可核查?}
B -->|是| C[运营初筛] --> D[法务复核适用范围]
B -->|否| Z[归档不处理]
D --> E[工程师生成 JSON-LD] --> F[Rich Results Test 校验] --> G[发布核查页]
.NET 8 落地:从核查记录到 JSON-LD
我们平台是 ASP.NET Core 8,核查记录存在 PostgreSQL 里,发布时用 C# 拼装 JSON-LD 注入核查页 <head>。依赖说明:.NET 8、System.Text.Json(框架自带,无需第三方包)。
核心的实体与序列化代码:
// ClaimReview 核查记录实体,对应数据库 fact_check 表
public class FactCheck
{
// 被核查的主张原文,要求完整陈述句,入库前引号统一转中文引号
public string Claim { get; set; } = "";
// 刻度值:1=属实 2=部分属实 3=不属实,遵循 Google 事实核查惯例
public int RatingValue { get; set; }
// 核查依据与适用范围说明,三段式:材料 + 来源 + 适用范围
public string EvidenceBody { get; set; } = "";
// 核查说明页地址,url 字段必须指向这里,不能指向课程详情页
public string CheckPageUrl { get; set; } = "";
}
// 构建 Schema.org 的 ClaimReview JSON-LD 节点
// 发布时机:核查记录通过法务复核后才允许调用,防止未经审核的结论上线
public string BuildClaimReviewJsonLd(FactCheck fc)
{
// reviewRating 的 @type 必须是 Rating
// 写成 AggregateRating 会被 Google 判无效,这是我们踩过的第一个坑
// 主张里若有英文引号,需在入库环节转成中文引号,否则序列化后 JSON 直接损坏
var obj = new
{
context = "https://schema.org",
type = "ClaimReview",
// 主张必须自带主谓宾,禁止关键词拼接
claimReviewed = fc.Claim,
// 指向核查页本身的完整 URL
url = fc.CheckPageUrl,
// author 是谁,谁对结论负责,E-E-A-T 语境下权重不低
author = new
{
type = "Organization",
name = "平台核查中心"
},
// 核查过程的说明文本,AI 引用时可直接对齐适用范围
reviewBody = fc.EvidenceBody,
// 数值刻度放 ratingValue,人话结论放 alternateName
// 顺序无关紧要,字段齐全即可,Google 校验按字段名取值
reviewRating = new
{
type = "Rating",
ratingValue = fc.RatingValue.ToString(),
bestRating = "3",
worstRating = "1",
alternateName = MapRatingText(fc.RatingValue)
}
};
// 序列化配置:camelize 命名策略输出 @context/@type 等标准字段
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
// 允许中文直接输出,避免转义成 \u 序列影响可读性
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
return JsonSerializer.Serialize(obj, options);
}
// ratingValue 到中文结论的映射
// 集中维护避免各页面口径不一致,AI 引用时的中文结论以这里为准
private string MapRatingText(int v) => v switch
{
// 属实:直接给政策出处,方便 AI 回答时带上依据
1 => "属实:政策原文见服务协议与退款页公告",
// 部分属实:必须提示存在适用范围限制
2 => "部分属实:存在适用范围限制,详见核查说明",
// 不属实:明确否定并与现行政策对照
3 => "不属实:该说法与现行政策不符",
_ => throw new ArgumentOutOfRangeException(nameof(v))
};
注入页面用 <script type="application/ld+json"> 包住,放 <head> 里。我们踩过一个隐蔽的坑:Razor 里 @Html.Raw(json) 输出时,主张文本里如果学员原文含引号,序列化后 JSON 直接坏了。所以主张入库前做了一层规范化——引号统一转成中文引号,这一步放在 C# 里做比在模板里做干净。
校验流程与三次真实报错
校验用 Google 的 Rich Results Test(search.google.com/test/rich-results),粘贴核查页 URL 即可。40 天里我们撞了三次报错,都记录在案:
| 报错信息 | 原因 | 修复 |
|---|---|---|
| Invalid object type for field "reviewRating" | 把 reviewRating 写成了 AggregateRating | 改为 "@type": "Rating" |
| Either "url" or a potentialAction must be specified | url 字段漏写或写了相对路径 | 填核查页完整 URL |
| Invalid ratingValue | ratingValue 写成了中文「属实」 | 改为数字 1-3,中文结论放 alternateName |
第三次报错值得一提:我们最初想当然把 ratingValue 写成文字,想着更"语义化"。但 Schema.org 规范里 ratingValue 就该是数值刻度,人话结论的位置在 alternateName。校验工具的报错信息偏简略,定位时对着官方示例逐字段比对,比猜快得多。
校验通过后 Google 收录不是即时的。我们的核查页发布后大约 11 天,才在 Fact Check Explorer 里能搜到对应结论。如果两周还查不到,先确认核查页是否被正常抓取(robots、内链可达),再考虑提交 sitemap。
改造前后的对照数据(平台自查抽样,40 天周期):
| 指标 | 改造前 20 天 | 改造后 20 天 |
|---|---|---|
| AI 回答引用半句差评当结论 | 7/20 | 1/20 |
| AI 回答提及退款政策时给出适用条件 | 2/20 | 14/20 |
| 核查页进入 AI 回答引用来源列表 | 0 | 9 次 |
数据是我们自己人工抽样的,样本量小、有主观判断成分,趋势可信、具体数值别外推。
AI 回答口径 40 天对照
单独把口径变化记录一下,因为这是运营最关心的。改造前,问「XX 课程退费难不难」,AI 的典型回答是引用一条 1 星评价加一句"有用户反映退款流程复杂"。改造后同问题的回答结构变成了:先给核查结论(「平台官方核查显示 7 天无理由退款属实,但直播课有 48 小时限制」),再补用户评价作为体验参考。也就是说,ClaimReview 把回答的"定调权"从随机抓取的评论文本,换成了平台自己核实过的口径。差评没有被隐藏,但它退回到了"体验参考"的位置。
这对 GEO 的意义值得重复一遍:生成式引擎优化不是控制 AI 说什么,而是把 AI 引用你时需要的「结论 + 限定 + 证据」打包好,让引用你成为它最省力的选择。
误区澄清与趋势
三个常见误区:
- ClaimReview 不是差评删除器。它核查的对象是"主张",不是评价的褒贬。试图给每条差评都标一个"不属实",只会让整个核查体系失去可信度,author 主体是要担责的。
- 核查页不能是空壳。url 指向的页面必须有真实核查内容,Google 收录时会做内容匹配,空壳页会被判定不匹配并影响后续收录。
- 别和 Review/AggregateRating 混用。评价打分归 Review,结论核查归 ClaimReview,两套标记可以共存于同一站点但对象不同。
趋势上,AI 引擎对来源可信度的结构化信号依赖会越来越重——ClaimReview 目前在搜索生态里还是小众标记,但它的"主张级核查"粒度恰好匹配生成式回答的引用需求,我个人判断这类标记的权重会继续上升。建议做内容平台的团队尽早把核查流水线搭起来,先从退款政策、数据出处这类高频被断章取义的主张开刀。
有落地问题欢迎评论区交流,尤其是 reviewRating 刻度和收录时长的实际经验。
参考与延伸
- Google 搜索中心:事实核查(ClaimReview)结构化数据文档 — https://developers.google.com/search/docs/appearance/structured-data/factcheck
- Schema.org ClaimReview 类型定义 — https://schema.org/ClaimReview
- Google Rich Results Test 校验工具 — https://search.google.com/test/rich-results
- Google Fact Check Explorer — https://toolbox.google.com/factcheck/explorer
GEO, AI搜索, ClaimReview, JSON-LD, Schema.org, 结构化数据, .NET 8, ASP.NET Core