二手和翻新商品在 AI 眼里成了全新货:OfferItemCondition 写错位置的 45 天对照
适用读者:负责 3C 电商列表页与商品详情页的前端和 SEO 工程师;正在被 AI 搜索引擎「错误描述商品成色」折磨的运营;所有想让商品进入生成式引擎优化(Generative Engine Optimization, GEO)推荐池的技术同学。
一台标价 3299 的官翻 iPhone 13,被 Perplexity 直接写成「全新未拆封」。就这一句话,客服当天接了 4 单投诉,要求退差价。这不是个例——一个做二手 3C 电商的团队,45 天前因为一个 JSON-LD 字段挂错了层级,AI 引擎把翻新机说成全新机,累计引发 27 单客诉;把 OfferItemCondition 挪到正确的位置并做了 45 天对照后,AI 回答的成色错误率从 38% 降到了 3%。
整个过程没有改一行业务逻辑,没有换推荐算法,改的只是结构化数据里几行 JSON。下面把踩的坑和对照数据摊开讲。
事故是怎么发生的
这家二手 3C 平台的翻新机列表页走的是标准的商品聚合页模板,页面底部由前端统一注入一段 Product 的 JSON-LD。负责这块的工程师老周(化名)九月初排查客诉时打开页面源码,发现 condition 相关的字段压根没有出现在这段 JSON-LD 里——模板最初是给全新品设计的,翻新机只是复用了标题和价格字段。

更麻烦的是商品详情页。详情页的 JSON-LD 里其实写了 "itemCondition": "UsedCondition",但写在了 Product 节点下面,而不是 Offer 节点下面。写它的同事是照着三年前的一份旧模板抄的,人已经离职,代码评审时谁也没注意这个层级问题——Schema.org 校验器对放错位置的合法属性只报 warning 不报 error,CI 的校验脚本只盯 error,所以这道闸门形同虚设。
于是出现了一个荒诞的局面:页面上的文案写着「95 新 官方翻新」,结构化数据里要么没有成色信息,要么有一个 AI 引擎不认的成色信号。AI 引擎面对矛盾信号时的做法很直接——优先采信它认为更权威的结构化数据,成色缺失时就往新机上靠。有客服同事在内部群里转述用户的原话:「AI 说这是全新机,你们页面凭什么写翻新?」
先讲机制:AI 引擎怎么读成色
这里需要把原理拆开说清楚。生成式引擎(Perplexity、豆包、Google AI Overviews 这类)抓取商品页后,并不像传统爬虫那样只存全文索引,而是会做一轮实体抽取,把页面解析成「商品—报价—成色」这样的三元组结构。这个结构的骨架就是 Schema.org 的 Product / Offer 模型,而成色的规范位置是 Offer 节点下的 OfferItemCondition(枚举值包括 NewCondition、UsedCondition、RefurbishedCondition、DamagedCondition)。
为什么必须挂在 Offer 下?因为按照 Schema.org 的定义,condition 描述的是「这一次报价所售商品的状态」。同一台翻新机,官方翻新渠道报 3299,二手散货商家报 2700,两笔 Offer 的成色口径可以不一样。把 itemCondition 挂在 Product 下,等于把状态绑死在商品本体上,模型在抽取时会面临歧义:这个状态属于哪笔报价?属于哪家的承诺?多数引擎的抽取器遇到这种歧义会直接丢弃该字段,少数会降权处理。字段被丢弃之后,LLM 就只剩页面文案这一个信息源,而文案里的「95 新」「官翻」这类词在它的理解里权重远低于结构化字段,于是它拿类目默认值「全新」来补位——错误就是这么发生的。
flowchart LR
A[AI 引擎抓取商品页] --> B{抽取 Offer 节点}
B -->|itemCondition 在 Offer 下| C[读取成色枚举值]
C --> D[回答中明确标注<br/>官方翻新 / 二手]
B -->|itemCondition 缺失或挂在 Product 下| E[成色信号为空]
E --> F[回退到类目默认值]
F --> G[回答写成 全新商品]
G --> H[用户到店发现是翻新机<br/>客诉]
还有一层容易忽略:AI 引擎会交叉验证。同一个商品,如果 Perplexity 从详情页 JSON-LD 里读不到成色,它会去比对你的页面文案、第三方比价站、甚至用户评价里的措辞。这些信号互相打架时,回答质量会整体下滑,有时候成色说错,有时候价格说错,有时候干脆把两个不同卖家的报价拼在一起。所以修字段不是只修「成色错误率」这一个指标,是在修整个商品实体在 AI 眼里的可信度。
改造前的错误写法
改造前的详情页 JSON-LD 长这样(简化版,只保留关键层级;行首注释为讲解用,非合法 JSON 语法):
// 改造前的错误示例:condition 挂在了 Product 层级
// Schema.org 把 condition 定义为「单笔报价所售商品的状态」
// 所以它规范的位置是 offers 节点内部,不是商品本体上
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Apple iPhone 13 官方翻新机 128G",
"itemCondition": "https://schema.org/RefurbishedCondition",
"offers": {
"@type": "Offer",
"price": "3299",
"priceCurrency": "CNY",
"availability": "https://schema.org/InStock"
}
}
问题一眼可见:itemCondition 写在了 Product 层级。对人类读者来说字段内容是对的,但对 Schema.org 的模型来说位置是错的。清单页则更糟,整段 JSON-LD 里成色字段一个都没有,因为模板按全新品设计,condition 压根没进字段清单。
顺带说一句,当时团队还在 Product 的 description 里堆了「全新品质 翻新工艺」这种自相矛盾的营销词,本意是蹭全新机的搜索流量,结果反而给 AI 引擎制造了更多矛盾信号。这部分在改造时一并清掉了。
把三个引擎分开看,改造前的错误构成也不一样,这个分布直接解释了后面为什么清单页模板上线才是拐点:
| 错误类型 | Perplexity | 豆包 | 聚合搜索 |
|---|---|---|---|
| 把翻新机说成全新机 | 9 次 | 12 次 | 6 次 |
| 把二手说成翻新 | 3 次 | 2 次 | 2 次 |
| 成色等级偏离页面口径 | 4 次 | 3 次 | 1 次 |
| 两家卖家报价拼错 | 2 次 | 1 次 | 0 次 |
改造后的正确写法
改造动作就两件事:把 itemCondition 挪进 Offer,同时把 description 的口径和 condition 对齐。改造后的写法(同样加了讲解注释):
// 改造后:itemCondition 移入 offers 节点内部
// 枚举值写完整 URL,部分引擎不识别 RefurbishedCondition 这种简写
// description 与成色口径保持一致,避免交叉验证时互相打架
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Apple iPhone 13 官方翻新机 128G",
"description": "官方翻新机,外壳更换原厂件,电池效率 89% 以上,提供 180 天质保",
"offers": {
"@type": "Offer",
"price": "3299",
"priceCurrency": "CNY",
"availability": "https://schema.org/InStock",
"itemCondition": "https://schema.org/RefurbishedCondition"
}
}
配套还做了两件小事。第一,清单页模板给 condition 字段加了渲染分支,翻新品类强制输出 RefurbishedCondition,二手品类输出 UsedCondition,全新品输出 NewCondition,不允许留空。第二,在 CI 里加了一条自定义检查:JSON-LD 解析后断言 Offer 节点下必须存在 itemCondition,且枚举值必须命中 Schema.org 的四个合法值之一。用的是现成的 Python 环境,检查脚本二十几行:
# 依赖:Python 3.10+,无第三方依赖,json 为标准库
# 环境:CI 的 lint 阶段,输入为页面渲染产物中的 JSON-LD 文件
# 用法:python check_condition.py rendered_ld.json,退出码非 0 即失败
import json
import sys
# 合法成色枚举值清单,与 Schema.org 定义保持一致
# 必须写完整 URL,简写形式(如 UsedCondition)部分引擎不认
VALID_CONDITIONS = {
"https://schema.org/NewCondition",
"https://schema.org/UsedCondition",
"https://schema.org/RefurbishedCondition",
"https://schema.org/DamagedCondition",
}
# 读取渲染产物中的 JSON-LD 片段
# 文件由渲染管线导出,保证和线上页面看到的一致
with open(sys.argv[1], encoding="utf-8") as f:
data = json.load(f)
# 取出 Offer 节点,清单页可能是数组,详情页是对象
# 统一转成列表处理,省得后面写两套循环
offers = data.get("offers", [])
if isinstance(offers, dict):
offers = [offers]
# 逐笔报价检查成色字段的位置与取值
# 位置检查隐含在取值方式里:只认 Offer 节点下的字段
for offer in offers:
cond = offer.get("itemCondition")
# 成色缺失直接失败,避免 AI 引擎回退到类目默认值
if not cond:
sys.exit("FAIL: Offer 节点缺少 itemCondition")
# 取值必须是四个合法枚举之一,写错枚举等于没写
if cond not in VALID_CONDITIONS:
sys.exit(f"FAIL: 非法成色枚举值 {cond}")
# 全部通过才放行,输出供 CI 日志抓取
print("PASS: condition 位置与取值均正确")
这个脚本挂在预发环境的渲染产物上跑,任何模板改动漏掉 condition 字段都会直接把流水线打红。上线第一周它拦住了两次,都是新人改模板时把条件渲染写漏了。
45 天对照数据
改造分两批上:第一批只上详情页(9 月 18 日),第二批上清单页模板(9 月 25 日)。观测方式是每周用固定的 60 个商品问题去问三个 AI 引擎(Perplexity、豆包、另一个聚合搜索),人工核对回答里的成色描述是否与实际商品一致。对照周期正好 45 天(9 月 18 日至 11 月 1 日)。
| 指标 | 改造前(8 月均值) | 改造后 45 天 |
|---|---|---|
| AI 回答成色错误率 | 38% | 3% |
| 成色相关客诉单量 | 27 单/月 | 2 单/45 天 |
| AI 引擎回答中引用商品页链接的次数 | 11 次/60 问 | 41 次/60 问 |
| 豆包推荐结果进入前三位的问题数 | 7/60 | 19/60 |
| Perplexity 标注来源为本站的回答数 | 4/60 | 15/60 |
错误率的定义要交代一下:60 个问题里,回答把翻新机描述成全新机、把二手描述成翻新、或者成色等级明显偏离页面口径,都算错误。38% 这个基线是在改造前测了两轮取的均值,不是拍脑袋。
xychart-beta
title "AI 回答成色错误率逐周变化(%)"
x-axis ["W1基线", "W2", "W3", "W4", "W5", "W6"]
y-axis "错误率 %" 0 --> 45
bar [38, 31, 17, 9, 5, 3]
曲线的形状值得说两句。第一周只改了详情页,错误率从 38 降到 31,降幅不大——因为 AI 引擎抓取清单页的频率比详情页高,列表页是它发现商品的主入口。9 月 25 日清单页模板上线后,第三周错误率直接腰斩到 17,第四周进入个位数。这个节奏和数据抓取的优先级完全对得上:AI 引擎更依赖列表页做商品发现,详情页更多是回答里的引用落点。
客诉那边的变化更直接。运营主管小蒋在十月中旬拉过一次单量:成色类投诉从八月的 27 单降到整个十月的 2 单,剩下那 2 单都是电池效率的理解偏差,和 condition 字段无关。
三条容易复踩的坑
清单页的 condition 缺失比详情页的层级错误杀伤力大。很多团队会把精力全砸在详情页的 JSON-LD 上,但 AI 引擎做商品发现和比价时用的是列表页信号,列表页字段缺失等于在入口处就把实体信息丢了。
validator.schema.org 对位置错误的合法属性只给 warning。只盯 error 的 CI 校验拦不住这类问题,必须用自定义断言把「Offer 下必须有 itemCondition」写成硬规则。
description 和 condition 的口径必须一致。改造前那页「全新品质 翻新工艺」的营销文案是真实拖后腿的:AI 引擎做交叉验证时,矛盾的文案会拉低结构化数据的采信度。两边说同一件事、用同一套词,回答才会稳。老周后来在团队复盘里写了一句话,我印象很深:「别指望 AI 引擎替你猜,它只替你抄。」
误区澄清或趋势预判
有个误区值得正面澄清:不少团队认为 condition 这类字段是给 Google 购物摘要用的老古董,现在没什么用。实际观察下来恰恰相反——生成式引擎对 Schema.org 枚举字段的依赖程度比传统搜索更高,因为 LLM 需要的是低歧义的、可直接对齐到知识槽位的信号,枚举值正对胃口。传统搜索对成色错写的惩罚可能只是排序微调,AI 引擎的惩罚是直接在回答里说错话,然后由你的客服买单。
趋势上,可以预期各引擎会逐步收紧对商品实体的抽取校验,甚至像 Google 那样给出更明确的报错清单。到时候挂在错误层级的合法字段会从 warning 升级成不收录,现在把位置摆正,成本几乎为零,晚做只会用客诉来补课。如果你的站点也有二手或翻新品类,建议今天就打开页面源码搜一下 itemCondition 在哪一层。
参考与延伸
GEO、商品被 AI 推荐、OfferItemCondition、Schema.org、JSON-LD、电商结构化数据