搜索全站
Google SEO 13 分钟阅读

WordPress 电商 Product 与 Offer 结构化数据完整配置

价格变了搜索结果没变、库存不同步、富媒体结果缺价格——本文拆解 Product 与 Offer 的必填与推荐字段,给出 WooCommerce 上修正默认输出、补足配送与退货政策、并用动态日期修复 priceValidUntil 的可抄代码。

U
UFO_PF 2016-08-13 · 阅读 13 分钟
Wo GOOGLE SEO
阅读提示
本文约 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 周。

常见踩坑

  1. 多币种插件切换后 priceCurrency 不跟着变:导致价格被判定严重偏离,建议在 wc_get_product_price 链路同步输出当前币种。
  2. 促销价与普通价冲突:price 应始终是当前实际结算价,折扣信息用 Offer 之外的方式表达,不要塞两个 offer 让 Google 猜。
  3. 图片用 WebP 却被 CDN 屏蔽:image 要求可被 Googlebot 直接抓取,被防盗链拦截的图片等于没标。
  4. category 字段的写法:写 Product.category 是合法的,但很多人误写成 productcat,字段落空。商品标识更推荐用 gtin13 与 sku。
  5. 把评论数 0 的聚合评分照样输出:aggregateRating 必须有真实 review 支撑,虚构会触发Review 与 AggregateRating 的合规边界里说的处罚。
  6. 案例:某户外用品站促销期间改价后没更新 priceValidUntil(写死为 2025-01-01),Google 判定价格过期,直接把该商品的商家列表接入了”缺少有效价格”的错误组,一周内该品类的自然曝光跌了近三成。修复办法是把该字段改成动态 +30 天,并补上 hasMerchantReturnPolicy,两周后recovery。价格类字段的时效性,远比别的类型苛刻。
  7. 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 能否在购物车环节留住用户。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。