二手和翻新商品在 AI 眼里成了全新货:OfferItemCondition 写错位置的 45 天对照

2026-09-26 01:16:06 1 次浏览
GEOSchema.orgJSON-LD电商商品数据

适用读者:负责 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、电商结构化数据

🤖
本内容由 AI 辅助生成,经人工校对审核;部分素材、资料来源于公开网络,仅作个人观点分享与交流使用,无任何商业侵权意图。若内容、图片、文字涉及您的合法著作权、版权权益,请联系本人,核实后将第一时间删除、修改相关内容。