商品价格不一致的六条报警,扣完之后一条不剩

商品价格不一致的六条报警,扣完之后一条不剩
张文保 更新 22 分钟阅读 2,308 阅读
本文目录
  1. 一件商品的价格,在你的站上到底存着几份?
  2. 按出现顺序比,还是按商品编号比?
  3. 配对要认哪一层的编号
  4. 先问它自己跟自己一不一样
  5. 报出来的那几条不一致,是怎么一条条被扣掉的?
  6. 一个商品页上摆着几十份商品声明,有几份在说这件商品?
  7. 比价格写错更常见的,是根本没写全
  8. 出口把价格发给了你,却不说那是什么货币
  9. 同一个价,一个出口写1799.99,另一个写179999
  10. 后台那个库存数量,是怎么跑到公网上去的?
  11. 那么真正该担心的是哪几件事?
  12. 三步把四份副本摆到同一屏上
  13. 常见问题解答
  14. 我不用Shopify,这篇跟我有关系吗?
  15. 审计工具报的价格不一致,是不是可以全部忽略?
  16. 为什么一定要按offers.sku而不是product.sku来配对?
  17. 页面上有推荐商品的结构化数据,算不算写错了?
  18. 变体太多,结构化数据要不要每个都写?
  19. 库存数量泄露怎么关掉?
  20. 货币这件事,最小的修法是什么?
  21. 这批数字能代表我的站吗?
  22. 做这套核对要花多久?
  23. 权威参考资料

摘要:从131个站里筛出181个能正常打开的商品页,把同一件商品的价格从四个地方各取一份,按报价层的SKU逐条配对,一共对上519条。相同513条,剩下6条差价全部出自地区定价,扣干净之后真正对不上的是0条,库存说法矛盾的只剩1条。真正该操心的在别处:数据出口把价格发给你,却有85.8%不说那是什么货币;一个商品页上摆着的Product声明有38.0%根本不在说这件商品;13个站把后台库存数量直接发到了公网,其中18条是负数。

这件事的起点,是一次没抓到的错。

做电商技术SEO的人,大多在某份审计报告里见过一行红字:商品页的结构化数据价格与页面价格不一致。工具给的证据看着无懈可击,一边是页面上那个数,一边是JSON-LD里那个数,明晃晃差着几十块。于是排期、开工单、找前端,折腾两周。保哥这回想把这行红字彻底查清楚,就从131个海外独立站里捞商品页,把同一件货的价格从四条互不相干的路径各取一份,摆到一起硬碰硬地比。

结果出乎意料:按商品编号严格配对之后,519条报价里真正对不上的是0条。六条看起来不一样的,扣到最后全是量的方式造成的。这一篇讲的就是那六条假警报怎么一条条被扣掉,以及扣完之后,剩在桌面上的真问题长什么样。

一件商品的价格,在你的站上到底存着几份?

先把牌摊开。一个跑在Shopify上的商品页,同一件货的价格不止一份,而是散在五个地方,来路各不相同。

副本谁生成的长什么样谁在读
页面可见文字主题模板$1,799.99
og:price:amount主题的head片段1,799.99社交平台、部分抓取器
JSON-LD的offers.price主题或第三方应用1799.99搜索引擎、AI助手
商品地址加.json后缀平台自带,关不掉字符串形式的元任何人
商品地址加.js后缀平台自带,关不掉整数179999,单位是分店铺自己的前端脚本

前三份是店主这边的产物,改主题、装应用都会动它们。后两份不一样,它们是平台送的,店主没开过,也关不掉——那个一次能拿走250条商品的数据出口,就是同一套东西的列表形态。这次要比的,正是这几份彼此之间对不对得上。

还得先说清楚两件与口径有关的事,不然后面的数字都站不住。第一,页面渲染层没有进严格统计。抽样用真实浏览器渲染了十个站之后发现,一个页面上可见的金额里混着分期月供、划线原价、配件单价、礼品卡面额,casper那一页最靠上的可见数字是46美元的月供,跟商品价599美元完全不是一回事。所以严格比对只在三份机器可读的副本之间做,渲染层只用来抽样核对。

第二,采集全程单线程、间隔2秒。131个站一轮跑下来,商品页能正常打开的181个,数据出口还开着的56个站。第二轮把请求顺序整个反过来重跑,顺便让同一个出口连着问两次——先确认它自己跟自己一样,再谈它跟别人一不一样。

按出现顺序比,还是按商品编号比?

第一版脚本差点让这篇稿子从头错到尾。

那天在jackery的储能电源页面上,脚本报了一个漂亮的数字:页面上写着3,149.00,JSON-LD里第一个Product节点写着12139,差3.86倍。这要是真的,就是一条能上标题的重大发现——给人看一个价,给谷歌报另一个价,差近四倍。

然后用浏览器打开亲眼看了一遍。那一页从上往下滚,3,149、4,639、5,479、7,909、12,139,五档套餐的价钱全都明明白白印在页面上;JSON-LD里那五个Product节点,一个不多一个不少,跟五档一一对应。它没有虚报任何东西,只是节点从贵往便宜排,而og:price取的是最便宜那一档。差3.86倍这个数,完全是按顺序取第一个取出来的。

这件事的教训比它本身值钱:当一页上有多个商品节点时,这一页的价格根本不是一个数。爬虫只会取走其中一个,取哪个由取法决定,跟这个站写得对不对没关系。于是整套比对推倒重来,改成按编号配对——把每个节点的SKU抓出来,去数据出口的变体清单里找同一个SKU,找到了才比价格、比库存、比货币。

顺手把这件事量了一下,好确认jackery不是孤例。在能同时拿到og:price和多档JSON-LD价格的页面里,og取到最低那一档的27个,取到最高那一档的1个,还有1个页面的og:price压根不落在JSON-LD的任何一档上。同一页上两个都是写给机器读的价格字段,本身就未必指向同一档货。另有13个页面的JSON-LD只写了一个价,却和og:price不相等,这里面混着地区定价和划线价两种情况,得逐个看才能断,不能一律算成错。

配对要认哪一层的编号

这里还有个更细的坑,是在baseus那台户外摄像头上撞见的。同一个Product节点里,商品层写着sku为S0003401,报价层的offers.sku却写着S0TW002130,两个是不同的变体,价格差30美元,库存一个有一个无。

价格和库存这两个字段,按schema.org对Offer类型的定义是挂在报价层的,不挂在商品层。所以配对必须以offers.sku为准;拿商品层的编号去配报价层的价,等于把一个节点自己的内部错位,算成了两份副本之间的不一致。这类错位实测中撞到两个站,数量不多,但每一个都会被工具报成价格不符。

先问它自己跟自己一不一样

还有一层不做就不敢下结论的功课。所有跨副本的差异,都建立在一个默认前提上:每份副本在两次请求之间是稳定的。这个前提得自己去验。

做法有两条。一条是让同一个数据出口连着问两次,中间隔着几秒和一次页面请求,比对两次返回的变体清单是否逐字一致;另一条是把整轮请求的先后顺序反过来重跑——第一轮是页面在前、出口在后,第二轮改成出口在前、页面在后。如果一个差异在正序里出现、反序里消失,那它量的就不是数据,是顺序。上一批做HTTP/2帧大小对照时,正是靠反序跑才发现下载窗口那个48%的差距根本不存在,这次把同一套纪律直接搬了过来。

两条都跑完了,结果比预想的干净。51个站做了连问两次这一项,两次返回的变体清单逐字一致的是51个,一个例外都没有。反序那一轮重新配上199条报价,相同197条、不同2条,而这2条正是正序里揪出来的那两个站,一条不多一条不少。再往细里对,49个商品在两轮之间的出口价格一个都没动过。

这一步花掉的时间不算少,一轮下来又是几百个请求。但它换来的是一句能站住的话:后面所有关于差异的结论,都不是抖动、不是缓存、也不是请求顺序变出来的。少了这一步,就只能写成量到了几条差异;有了这一步,才敢写成这几条差异是真的、而且只有这几条。

报出来的那几条不一致,是怎么一条条被扣掉的?

换成按报价层编号配对之后,519条报价对上了,其中513条一模一样,剩6条不同。接下来的活儿就是逐条问:这一条到底是它写错了,还是量错了。

扣除项判据扣掉多少
划线价结构化数据里那个价等于出口的compare_at_price0条
地区定价页面自报货币不是店铺基准货币,或规范地址带地区前缀6条
层间错位商品层SKU与报价层SKU指的不是同一个变体配对阶段已规避
剩下的真差异0条

划线价这一关本来是重点防的。商品页上那个被划掉的原价非常容易被结构化数据当成现价写出去,一写出去就是几十块的差额。这次一条都没撞上,算是个意外之喜,但检查还是得留着。

六条全部倒在了地区定价这一关,出自两个站,aloyoga三条、misen三条。aloyoga那条最典型:页面上、og:price里、JSON-LD里,三处齐刷刷写着110.0,货币是SGD;而数据出口给出的是78.00。两个数都没错——页面走了地区定价,数据出口不走。请求从一个被判成新加坡的出口IP发出去,站把它路由到带en-sg前缀的地址,页面那份价格跟着换算成新加坡元,而出口那份纹丝不动,还是店铺基准货币下的那个数。

库存那一侧同样干净:523条能配对的库存声明里,522条与出口完全一致,唯一那条矛盾出在beistravel,根子还是同一个——那个节点压根没写商品层的SKU。生鲜替换那一篇里提过一句缺货状态在页面、商品数据和抓取程序眼里是不是同一个,那时候只是提问,这次算是把它量出来了:在这批样本上是同一个。

所以那行红字,在这批样本里一次都没有真正成立过。上一次量爬虫和用户拿到的页面差异时也是这样,74%一路掉到2.2%;这一次更彻底,直接掉到了0。差别在于那一次剩了2.2%,这一次一条不剩。

一个商品页上摆着几十份商品声明,有几份在说这件商品?

既然价格本身没问题,那些让工具误报的节点又是从哪儿冒出来的?答案就在页面结构里。

153个能判定归属的商品页上,一共数出856个Product节点。按SKU回查出口的变体清单,属于本商品的只有531个,剩下325个、也就是38.0%,说的是页面上别的商品——推荐位、搭配购买、看了又看,那些卡片各自带着自己的结构化数据。45.1%的商品页不止一个Product节点,最多的一页有118个。

这里得交代一个口径,不然856这个数会被误读。采集时每页最多只留了前60个节点,181个页面里有3个因此被截断,其中taylorstitch那一页真实节点数是118个、brooklynbedding是84个、thirdlove是61个。所以856是个保守值,真实总量是1026个,而38.0%这个比例算的是留存下来的那部分。

被截断的那两页恰恰最极端。taylorstitch那件衬衫的页面上摆着118份商品声明,抽出来看的前60份里,属于本商品的是0个——一份都没有在描述你点进来看的那件货。allbirds那双跑鞋更好玩,53个节点里13个是本商品的十三个尺码,另外40个是别人的。工具要是不做归属判定,一头扎进去随便捞一个价出来,报错是必然的,不报才是运气。

比价格写错更常见的,是根本没写全

顺着这条线往下量,还有一个更值得管的数:153个商品页里,把自己所有变体都写进结构化数据的有82个,只写了一部分的43个,一个变体都没写的28个

多尺码多颜色的商品只标一个价,搜索结果里露出的价格区间就是残缺的。这件事在变体商品的Schema与canonical三层治理里讲过原理,实测下来它比价格写错常见得多,偏偏几乎没有工具会报——因为工具只会比对已经写出来的字段,不会替你数少写了几个。

出口把价格发给了你,却不说那是什么货币

把注意力从页面挪到数据出口,第一个跳出来的问题跟价格对不对没关系,跟价格是什么有关系。

1085个变体记录里,标了货币的154条,没标的931条,八成半以上的价格数字是裸着发出来的。另一个出口更干脆:按这个接口的官方说明,它的价格一律是以分为单位的整数,从头到尾没有货币字段,规范里也不打算给。你拿到一个179999,它可能是1799.99美元,也可能是1799.99新加坡元,出口本身不负责告诉你。

这不是纸上谈兵的风险。同一次采集里,从同一个出口IP出发,页面自报的货币收到了五种:USD一百二十四次、SGD二十一次、EUR九次、DKK三次,还有三次是ANG,荷属安的列斯盾。九个商品页的规范地址直接带上了地区前缀,指向en-sg那一版。也就是说,同一个网址,谁来问、从哪儿问,拿到的定价上下文就不同,而那个不标货币的出口对此一无所知。比价工具、竞品监控、喂给购物代理的数据管道,全都在这个缺口上默认成美元。

这一层的判据其实很简单:跨境价格本地化只做在渲染层是不够的。凡是机器要读的地方,价格和货币必须成对出现,缺一个另一个就没有意义。多市场站还要往前一步,让每个地区版本的结构化数据各写各的货币,而不是全站共用一个基准币。

同一个价,一个出口写1799.99,另一个写179999

平台自带的两个出口,来路相同、指向同一件商品,价格的写法却是两套。

一个给的是字符串形式的元,另一个给的是整数形式的分。930条记录全都遵守各自的规矩,没有例外。规范上两边都没错,可它们放在一起,就是一道等着人踩的题。

踩法有两种,方向相反。一种是拿分当元,把179999当成十七万九千九百九十九,商品瞬间贵一百倍;另一种是拿元当分,1799.99当成十八块钱。前者会让着陆页价格与提交数据不一致这条政策直接生效,整批商品被判成价格异常;后者更危险,因为它看着像一次促销。字符串那一版还有第二层麻烦:它是字符串不是数字,直接参与算术运算的话,不同语言的隐式转换各有各的脾气。

保哥自己的判据是,任何跨系统传价格的地方都别传浮点数,也别传裸整数,传一个把金额和货币绑在一起的结构。这跟产品feed该谁管是同一个道理:数据一旦离开你的页面,接收方对你的单位约定一无所知。列表页那个每千克单价踩的也是这个坑,只不过它错在分母上。

后台那个库存数量,是怎么跑到公网上去的?

这一项是采集时顺手撞见的,撞见之后就没法当没看见。

那个AJAX形态的出口,在变体记录里附带一个字段,写的是这个变体当前还剩多少件。131个站里,13个站的304条变体记录把这个数字原样发到了公网上,中位数56件,最大的一条465件。不需要登录,不需要令牌,一条命令的事。

更有意思的是分布的两端:31条是0,18条是负数。负库存意味着卖出去的比记录里有的还多,也就是超卖已经发生了,正等着人工去收拾。这个状态平时只有运营在后台看得到,现在它挂在公网上,谁想看都能看。

要澄清一句:这个字段并不是所有站都发。同一次采集里626条记录没有它,说明它跟库存跟踪的开关以及主题的配置有关,不是平台强制。但反过来说,那13个站多半也不知道自己在发。库存数量本身不算机密,可它连起来能推的东西不少——上新节奏、动销速度、备货深度,竞品拿一个定时任务盯上几周,你的补货周期就摊开了。这类机读地址没有head标签,想拦只能在响应头和服务端做文章。

缺货的另一头也值得顺手看一眼。产品缺货下架之后的收尾一直是电商SEO的漏勺,而库存字段是判断该走哪条路的第一手依据——它是暂时缺货还是永久停售,页面该保留还是该410,都得先从这里读。

那么真正该担心的是哪几件事?

把这一轮的账结一下。价格一致性,在这批样本里是个伪问题;真正立得住的问题有四条,按轻重排下来是这样。

问题实测量级后果落在哪
变体没写全153页里43页只写一部分,28页一个没写搜索结果的价格区间残缺
出口不标货币931条裸价格,占85.8%下游管道默认成美元
页上混着别人的商品声明856个节点里325个不属于本商品任何按顺序取值的工具都会误报
库存数量外泄13个站304条,含18条负数补货节奏和超卖状态被看光

这四条有个共同点:它们都不会被现成的审计工具标红。工具擅长比对两个数字一不一样,不擅长回答这两个数字是不是在说同一件事。结构化数据审计工具能把字段缺漏扒清楚,但归属判定、货币上下文、层间对齐这几件事,目前还得自己动手。

再往前看一步,这几条的分量还会变。购物代理读你的站时不会像人一样把页面从上到下看一遍,它多半直接奔着结构化数据和数据出口去。人还能靠常识识破一个标错一百倍的价格,管道不会——它只会照单全收,然后把这个数写进比价结果里。免费商品收录这条通道同样吃的是这份数据。

三步把四份副本摆到同一屏上

不用装任何东西,浏览器和一条命令就够。挑一个自己站上有多变体的商品页,照下面三步走一遍。

第一步,把两个出口的原文取回来,先看货币和单位:

curl -s "https://你的域名/products/某个handle.json" | head -c 2000
curl -s "https://你的域名/products/某个handle.js"   | head -c 2000

重点看三处:变体里的价格是带小数点的字符串还是整数分、有没有货币字段、有没有一个写着剩余件数的字段。第三处如果有值,先去后台把库存跟踪的显示关掉,再决定要不要在服务端拦。

第二步,把页面里的结构化数据抠出来,数一数有几个Product节点,再数一数其中几个的SKU出现在第一步那份变体清单里。差额就是页面替别人做的声明。这一步用JSON-LD校验工具过一遍语法,顺便能发现尾逗号之类会让整块失效的毛病;字段该填哪些、哪些是必填,对照商品摘要的字段要求过一遍就清楚了。

第三步,做一次货币对照:换一个地区的出口去请求同一个商品页,看规范地址会不会变、页面自报的货币会不会变、而两个数据出口那份会不会跟着变。如果页面变了、出口没变,你的下游管道就有一个默认成基准货币的缺口。这一条在只做单一市场的站上不成立,跨境多市场的站必查。页面渲染那一份价格从哪来,可以顺着主题模板拿到的product对象倒推回去。

最后提醒一句关于工具报告的读法。看到价格不一致这类告警,先问三个问题:它是按什么配对的、扣没扣掉划线价、知不知道这一页有地区定价。三个问题里有一个答不上来,那条告警就先别排期。按页面模板抽样做这套核对,比逐条网址跑一遍省得多。

常见问题解答

我不用Shopify,这篇跟我有关系吗?

有一半关系。两个数据出口是平台特有的,别的系统未必有同样的地址;但四份副本这个结构是通用的——页面渲染一份、og标签一份、结构化数据一份、给前端脚本用的接口一份,WooCommerce、Magento、自研前端全都是这个套路。归属判定、层间对齐、货币上下文这三件事,换个平台一样要做。

审计工具报的价格不一致,是不是可以全部忽略?

不能。这批样本只有181个商品页、519条报价,样本里为零不等于全网为零,尤其是装了大量第三方应用、结构化数据由多个来源拼出来的站,出错概率明显更高。正确的读法是把它降级成待核实:用文中那三个问题过一遍,能解释掉的关掉,解释不掉的再排期。

为什么一定要按offers.sku而不是product.sku来配对?

因为价格和库存这两个字段挂在报价层,不挂在商品层。一个Product节点可以描述一个商品系列,而它的offers指向其中某一个具体变体,这在规范里是允许的。实测中撞到两个站的两层编号指向不同变体,如果按商品层配对,这两条就会被算成价格不一致,而它们其实是节点自己的内部错位。

页面上有推荐商品的结构化数据,算不算写错了?

不算错,是常见做法,问题在于没有边界。规范上推荐位带自己的Product标记是合法的,但如果这些节点跟主商品混在同一层、又不通过主实体之类的字段声明主次,机器就只能靠猜。稳妥的做法是主商品那份单独放,并且让页面的主实体明确指向它。

变体太多,结构化数据要不要每个都写?

写不写取决于价格是否相同。价格一致的多变体,写一个带价格区间的报价聚合就够;价格不同的必须逐个写,否则搜索结果里露出的价格区间是残缺的。实测里28个商品页一个变体都没写,这些站的多规格商品在搜索结果里只能显示一个孤零零的价。

库存数量泄露怎么关掉?

先分清是哪一层的事。数量字段跟着库存跟踪设置走,后台关掉跟踪字段就没值了,但这会影响超卖控制,多数站不会这么干。更实际的做法是在服务端拦:那个出口是固定路径,可以在反向代理层对非同源请求做限制,或者用响应头把它挡在索引之外。要注意它本身是主题脚本在用的,一刀切会把加购功能弄坏,改之前先看自己主题调没调它。

货币这件事,最小的修法是什么?

在结构化数据里把货币字段写成这个页面实际渲染的那个货币,而不是店铺基准货币,这是成本最低也最容易被忽略的一步。做多市场的话,同一件商品在不同地区版本上的结构化数据必须各写各的货币,配合地区版本的规范地址一起提交。数据出口那一侧改不动,只能在下游接收端约定好按什么货币解释。

这批数字能代表我的站吗?

机制能,数值不能。四份副本各有来路、报价层与商品层分属两层、出口不做地区定价、单位一分一元,这几条是结构性的,换个样本一样成立。至于零条真差异、38.0%的节点不属于本商品、85.8%不标货币,只描述这131个站在2026年9月这一次采集里的样子,换一批站、换个时间、换个出口IP,绝对值一定会动。

做这套核对要花多久?

单个商品页十分钟,全站要看你有几套模板。商品页模板通常不超过五种,每种挑一个有多变体的样本走一遍三步,半天能收工。真正费时间的不是采集,是判断——每发现一处差异都得回头问一遍是它错了还是你量错了,这一步没法省,省了就会得到一堆假警报。

权威参考资料

分享到
标签
版权声明

本文标题:《商品价格不一致的六条报警,扣完之后一条不剩》

本文链接:https://zhangwenbao.com/product-price-copies-false-positive-audit.html

版权声明:本文原创,转载与引用请注明作者与原文链接。许可协议: CC BY 4.0

继续阅读
发表评论
分享到微信 或在下方手动填写
支持 Ctrl + Enter 提交