本文约 4,986 字,预计阅读 13 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:为什么你改了价格,Google 还显示旧价格
外贸与 WooCommerce 站点最常收到的一类警告是同一个:后台把价格从 39.9 调到 29.9,用户却发现 Google 搜索结果里的商家列表仍然写着旧价,甚至点击进去价格对不上,被用户当成”钓鱼”。这背后不是 Google 缓存慢,而是 Product 结构化数据里的几个关键字段没有跟上:priceValidUntil 还写着半年前的日期(Google 会认为价格已失效而不更新),或者 availability 仍旧是 https://schema.org/InStock 而实际已下架。Google 的商家列表(Merchant listings)富媒体结果对字段的时效性要求远高于文章类标记,一份”写了就不更新”的 Product JSON-LD,比不写更危险。本文就按字段级别把这件事拆清楚。
原理分析:Product、Offer 与商品标识三件套
Product 节点描述”这是什么东西”,Offer 节点描述”此刻怎么买”。两者必须分层,不能把所有信息拍平塞进 Product。Google 对商家列表的必填字段包括 name、image、offers.price、offers.priceCurrency;强烈推荐的字段包括 offers.priceValidUntil、offers.availability、review/aggregateRating、brand、sku、gtin13 等商品标识,以及 aggregateRating 之外的 shippingDetails 与 hasMerchantReturnPolicy。其中 sku/gtin/mpn 组合被称为商品标识,缺了它们 Google 难以把你的页面和购物图谱里的实体对齐。
| 字段 | 归属节点 | 是否必填 | 常见错误 |
|---|---|---|---|
| name | Product | 必填 | 把 SEO 标题写了进去,冗长且含促销词 |
| image | Product | 必填 | 只给一个 200px 缩略图,不满足尺寸要求 |
| price / priceCurrency | Offer | 必填 | 币种写 CNY 但价格是美元数值 |
| priceValidUntil | Offer | 推荐 | 写死过去日期,导致价格被判失效 |
| availability | Offer | 推荐 | 全部写 InStock,从不更新 |
| shippingDetails | Offer | 推荐 | 缺失导致”不含运费”争议 |
在 WordPress 上还有一层现实问题:WooCommerce 本身已经输出 Product JSON-LD,很多 SEO 插件(Yoast、Rank Math)也会各自输出一份,再加上主题里的手工代码,同一个商品页常常有三份 Product 节点互相打架。解决思路与避免结构化数据重复输出的方案一致——先确定唯一输出源,再在其上补字段,而不是层层叠加。
保姆级实操
第一步:确认 WooCommerce 默认输出的边界
先看清楚 WooCommerce 自己产出了什么:
curl -sL "https://shop.mimangfei.com/product/demo/" \
| python -c "import sys,re,json;[print(json.dumps(json.loads(m),ensure_ascii=False,indent=1)) for m in re.findall(r'<script type=\"application/ld\+json\">(.*?)</script>',sys.stdin.read(),re.S)]"
WooCommerce 的默认 Product schema 覆盖 name、image、sku、offers(含 price、priceCurrency、availability),但通常不含 shippingDetails、hasMerchantReturnPolicy。
第二步:缺什么补什么,而不是整体重写
正确做法是用 WooCommerce 的 woocommerce_structured_data_product 过滤器往既有 Product 节点里补字段,避免产生第二份 Product。
add_filter( 'woocommerce_structured_data_product', function ( $markup, $product ) {
if ( ! isset( $markup['offers'][0] ) ) {
return $markup;
}
$markup['brand'] = [
'@type' => 'Brand',
'name' => 'Mimangfei',
];
$markup['shippingDetails'] = [
'@type' => 'OfferShippingDetails',
'shippingRate' => [
'@type' => 'MonetaryAmount',
'value' => '15',
'currency' => 'USD',
],
'deliveryTime' => [
'@type' => 'ShippingDeliveryTime',
'handlingTime' => [ '@type' => 'QuantitativeValue', 'minValue' => 1, 'maxValue' => 2, 'unitCode' => 'd' ],
'transitTime' => [ '@type' => 'QuantitativeValue', 'minValue' => 5, 'maxValue' => 12, 'unitCode' => 'd' ],
],
];
$markup['hasMerchantReturnPolicy'] = [
'@type' => 'MerchantReturnPolicy',
'applicableCountry' => 'US',
'returnPolicyCategory' => 'https://schema.org/MerchantReturnFiniteReturnWindow',
'merchantReturnDays' => 30,
'returnMethod' => 'https://schema.org/ReturnByMail',
'returnFees' => 'https://schema.org/FreeReturn',
];
return $markup;
}, 10, 2 );
第三步:把 priceValidUntil 做成”活”的
写死日期是失效的根源。用今天的 30 天后动态生成,并在每天商品保存或每日 Cron 时更新:
add_filter( 'woocommerce_structured_data_product', function ( $markup, $product ) {
if ( isset( $markup['offers'][0] ) ) {
$markup['offers'][0]['priceValidUntil'] = gmdate( 'Y-m-d', time() + 30 * DAY_IN_SECONDS );
$markup['offers'][0]['url'] = get_permalink( $product->get_id() );
}
return $markup;
}, 20, 2 );
第四步:库存状态必须走 WooCommerce 的真状态
不要手写 InStock,直接读 $product->is_in_stock(),让缺货商品自然落到 OutOfStock。WooCommerce 默认已做了这层映射,你重复覆盖反而容易写反。
第五步:变体商品的处理
可变商品要输出 ProductGroup 并用 variesBy、productVariant 表达 SKU 组合,而不是只输出父商品的平均价。若暂时无法完整实现,至少要让 offers 落在具体子商品 URL 上。
第六步:用 Rich Results Test 复検并在 GSC 看收敛
把真实商品 URL 提交给 Rich Results Test,重点检查”价格””可用性”是否出现红色缺失,确认无误后在 Search Console 的 Product snippets 报表里持续观察 2 周。
常见踩坑
- 多币种插件切换后 priceCurrency 不跟着变:导致价格被判定严重偏离,建议在
wc_get_product_price链路同步输出当前币种。 - 促销价与普通价冲突:
price应始终是当前实际结算价,折扣信息用Offer之外的方式表达,不要塞两个 offer 让 Google 猜。 - 图片用 WebP 却被 CDN 屏蔽:
image要求可被 Googlebot 直接抓取,被防盗链拦截的图片等于没标。 - category 字段的写法:写
Product.category是合法的,但很多人误写成productcat,字段落空。商品标识更推荐用gtin13与sku。 - 把评论数 0 的聚合评分照样输出:
aggregateRating必须有真实 review 支撑,虚构会触发Review 与 AggregateRating 的合规边界里说的处罚。 - 案例:某户外用品站促销期间改价后没更新
priceValidUntil(写死为 2025-01-01),Google 判定价格过期,直接把该商品的商家列表接入了”缺少有效价格”的错误组,一周内该品类的自然曝光跌了近三成。修复办法是把该字段改成动态 +30 天,并补上hasMerchantReturnPolicy,两周后recovery。价格类字段的时效性,远比别的类型苛刻。 - XML sitemap 里排除了变体 URL:Google 看不到就无法抓,务必让包含 offers 的 URL 可被发现。
总结
判断标准:一份合格的 Product 标记,应该做到”Google 显示的价格与库存,跟用户点进去看到的完全一致”。做不到这一点的 Product JSON-LD,不如先删掉,等数据链路打通再上线。行动清单:确定唯一输出源 → 用 WooCommerce 过滤器补 brand/shippingDetails/退货政策 → 动态生成 priceValidUntil → 变更情况的 availability 直接读商品对象 → 变体走 ProductGroup → Rich Results Test 验证 → GSC 观察两周。同批里Core Web Vitals 三指标(LCP/INP/CLS)详解则决定了这些 Pages 能否在购物车环节留住用户。
