Shopify商品数据有个没人关的出口,一次能拿走250条

Shopify商品数据有个没人关的出口,一次能拿走250条
张文保 更新 23 分钟阅读 3,195 阅读
本文目录
  1. 一个不用登录的地址,能把整个店端出来多少?
  2. 怎么确认它真的开着,而不是错误页换了个门牌?
  3. 这份JSON里到底写了些什么?
  4. 库存和折扣也在里面?
  5. 图片和文案是一整份,不是缩略图
  6. 那5300个标签里,有多少是给内部看的?
  7. 除了商品目录,还有哪几扇门也开着?
  8. 购物车状态:50个站
  9. 搜索联想:44个站
  10. 那几个真正该关的没开
  11. 上新节奏能不能从这里读出来?
  12. robots.txt在这件事上基本是缺席的
  13. 这些地址会不会自己进搜索引擎的索引?
  14. 关得掉吗,关掉会不会伤到自己?
  15. 第一条:在边缘层拦
  16. 第二条:把敏感字段清出去
  17. 第三条:接受它,然后利用它
  18. 你该怎么查自己的、怎么看别人的?
  19. 顺手能校准的三件事
  20. 常见问题解答
  21. 这个地址是Shopify独有的吗?
  22. 竞争对手真的会去看这个吗?
  23. 把robots.txt里加一条Disallow能解决吗?
  24. 无货的商品要不要从这份数据里去掉?
  25. updated_at为什么不能当改动时间用?
  26. 清理标签会影响前台功能吗?
  27. 我的站不是电商,这件事跟我有关吗?
  28. 权威参考资料
摘要:在103个能判定的电商站上挨个试了几个平台自带的只读地址,45个站的商品目录是敞开的,不用登录、不用密钥,一个链接返回250条。拿到手的22个站样本里有3934个商品、18917个变体,价格、划线原价、SKU、重量、上架日期、商品描述全在,连库存都写着——整体无货率20.1%。更意外的是标签:5300种标签里有52种明显是给内部看的,test、hidden、employee-30、exclude-from-gmc,出现在15个站上。

上一篇量的是站点对“我没有这个”这句话花了多少钱。这一篇反过来:有些东西它其实不打算给你,但你问了,它也照样给。

起因是查一个客户的商品数据同步问题,顺手在浏览器地址栏里给商品页后面加了个后缀,回车。屏幕上刷出一整屏JSON:商品、变体、价格、库存、SKU,一条不落。这个地址没有任何鉴权,任何人都能打开。

那么问题来了:这是个案,还是大家都这样?

一个不用登录的地址,能把整个店端出来多少?

还是那131个国际电商站。剔掉首页进不去的17个、以及对任何地址都回200所以判不了的11个,剩下103个可以判定。结果:

端点返回真JSON且200的站它给的是什么
/products.json45商品目录,含全部变体与价格
/collections/all/products.json44同上,走集合路由
/cart.js50当前购物车状态与币种
/search/suggest.json44站内搜索联想,可枚举商品
/wp-json/wp/v2/posts1内容接口
/graphql(GET)2接口探测

按平台拆开看更清楚:Shopify站59个里有40个的商品出口是开着的,占67.8%;Salesforce Commerce的10个站一个都没有,Magento的3个也没有。这不是谁配错了,这是平台默认行为的差别。

剩下那5个开着的分布也值得一提:认不出平台的19个站里有3个、Next.js的10个里有1个、BigCommerce的2个里有1个。也就是说这件事并不完全绑定Shopify,只是Shopify把它做成了默认。而那40个开着的Shopify站里,没有一个是店主主动打开的——它出厂就是这样。

接着往深处问:一次能拿走多少?把参数调到平台允许的上限再要一次,41个站给了完整回应,其中22个站一次就吐满250条,中位数正好是250。也就是说这不是它的全部家底,只是单页上限——翻页参数一改,剩下的照给。

怎么确认它真的开着,而不是错误页换了个门牌?

这一步不做,后面所有数字都是废的。

上一篇量到的11个站对任何地址都返回200,包括那些编出来的、根本不存在的路径。在这种站上问/products.json,它当然也回200——可那是它的首页外壳,不是商品数据。所以判定得过三道关:

  • 第一关,首页要能正常打开,否则连样本都算不上;
  • 第二关,这个站对不存在的地址得会说“没有”,不然它的200一文不值;
  • 第三关,响应要真能解析成带商品数组的JSON,不能只看状态码和Content-Type。

第三关拦下来的假阳性有13例。最夸张的几个:soundcore.com的集合端点回了3969446字节的HTML,misen.com的接口探测回了5962542字节的HTML,casetify.com的搜索联想回了2615582字节。还有三个站的响应头写着application/json,体积是0字节——类型对了,内容空了。

只看状态码会把这13例全算成“出口开着”,把45虚报成58。接口和feed这类地址没有head标签可写,能拿来判断的信号本来就少,就更不能只信一个。

这份JSON里到底写了些什么?

41个站的字段结构完全一致,一个字段都不差。商品对象这一层:

  • 标识与文案:idhandletitlebody_html(商品详情页正文的完整HTML)
  • 归类:product_typevendortagsoptions
  • 时间:created_atpublished_atupdated_at
  • 素材:images(每张图的完整地址与尺寸)

变体那一层更细:pricecompare_at_priceskuavailablegramstaxablerequires_shipping、三个规格维度。

逐个字段看过去,会发现每一个都能拿去干点别的事。sku是你的内部编码,很多品牌的SKU里带着年份、批次、供应商代号;grams是净重,配上尺寸就能反推物流成本结构;vendorproduct_type合起来是一张品类地图——22个站的样本里出现了53个不同的vendor、360个不同的product_type,对方的产品线怎么划分、哪条线在扩张,一眼就能看出来。

body_html是商品详情页的完整正文,连同里面的HTML标签一起给。这意味着对手可以整段复制你的文案,也意味着你自己可以拿它做文案质量的批量体检——22个站里描述长度中位数只有456字符,短的那一批基本是一句话带过。用户扫完一屏商品一个都没点开这种事,源头常常就在这几百个字符里。

拿到完整字段样本的22个站合起来是3934个商品、18917个变体。其中95.9%的变体带着SKU,超过一半带着重量。商品描述的HTML长度中位数456字符,最长的一条55238字符;每个商品的图片数中位5张,最多的一个商品挂了250张图。

这里要说明一个分母:第二轮取完整字段的时候只成功了22个站,另外20个把请求挡在外面了——和第一轮返回429的基本是同一批。所以字段统计是22个站的,不是45个站的,往后看到的比例都按22算。

库存和折扣也在里面?

在,而且是逐个变体写着的。

available这个字段18917个变体全都有,没有一个缺省。整体无货率20.1%,站级的无货率中位数是11.5%。分布拉得很开:

站点无货变体占比样本变体数
everlane.com56.2%1846
flyingtiger.com56.0%250
decathlon.com49.3%1381
liquiddeath.com44.0%573
functionofbeauty.com0.0%

一半的库存缺口在竞争对手那儿是一览无余的。对方不用买任何数据,写十行脚本每天跑一遍,就能知道你哪个色号断了、断了几天、什么时候补上。

价格这一侧同样完整。compare_at_price就是页面上那个被划掉的原价,18917个变体里有4499个挂着它,占23.8%。能算出折扣的4049条里,折扣率中位数39.3%。挂划线价最多的是everlane.com的1285条、brooklinen.com的762条。

划线价这件事在合规上本来就敏感,欧盟那边要求你能证明这个原价真的卖过。现在它连同你的全部SKU一起放在一个公开地址上,取证成本对监管方和对竞争对手一样低。

反过来这也是个便利:每单位价格这类需要现算的字段,用这份JSON里的pricegrams两秒就能批量算完,比从页面上抠可靠得多。合规自查、价格带分析、毛利结构复盘,都可以从这里起手。

图片和文案是一整份,不是缩略图

images数组给的是每张图的完整地址,带宽高。22个站的样本里每个商品图片数中位5张,最多的一个商品挂了250张。这些地址都是CDN上的原图,不需要任何令牌就能直接下载——也就是说对方拿到的不是你的商品截图,是你的原始素材。

配上body_html里那份完整文案,一个竞品的商品页可以被整页复刻,只剩下品牌名要换。那几张在页面上没露出来的商品图也在这份数组里,页面上因为版面被截断的图,在JSON里一张不少。

那5300个标签里,有多少是给内部看的?

这是整轮实测里最让我意外的一格。

tags字段本来是给主题模板做筛选和分组用的,理论上都是面向顾客的词。3934个商品上一共出现了44582次标签,去重之后5300种。

常见的那些确实很正常:all-productsmost-lovedtop-ratednew-releasesseason:aw26。但接着往下翻,画风就变了:

  • over-40-offover-50-offover-60-offover-70-off——完整的折扣分层,每一档都是250次
  • winback-codes——挽回优惠码的商品池
  • percentageinstockv2lower-bucket——内部的库存与分层口径,还带着版本号
  • employee-30——员工价三折
  • costco - titanium——渠道名直接写在标签里

按“像内部运营用语”这个判据数了一遍:52种标签、共出现464次,分布在22个站里的15个上。出现次数最多的几个是test(234次)、exclude_rebuy(39次)、employee-30(29次)、hidden(28次)、exclude_feeds(24次)、exclude-from-gmc(14次)、hide(11次)。

后面那几个尤其值得琢磨。exclude-from-gmc的意思是“这个商品不要推送到Google购物”,exclude_feeds是“不要进任何数据源”。一个用来表达“别公开这个”的标记,自己正躺在一个完全公开的地址上。顺着这批标签能反推出对方的Merchant Center投放策略——哪些SKU故意不上购物、哪些是清仓池、哪些还在测试。

还有test那234次。测试商品留在生产库里不算罕见,但它们同时也出现在这份对外的JSON里,意味着爬虫和竞品也能看见你的半成品。

除了商品目录,还有哪几扇门也开着?

商品出口是最扎眼的一个,但同一批站上还有两个开得更广。

购物车状态:50个站

/cart.js在50个站上返回了正常的JSON,比商品出口还多5个。它给的是当前会话的购物车:币种、地区、行项目、总价、以及一串购物车令牌。单看一次请求,它只反映你自己的购物车,不含别人的数据,所以泄露风险跟商品目录不是一个量级。

它真正的用处是当探针。这个端点在官方文档里是公开设计,返回体里的币种和地区字段,能一眼看出这个站给你分了哪个市场——不用去翻页面上那个语言切换器,也不用管它有没有做地区跳转。做国际站排查的时候这比看页面快得多。

搜索联想:44个站

/search/suggest.json开着的有44个。这个端点接受任意关键词,返回匹配的商品、集合和页面。它的问题在于可枚举:拿一份常见词表跑一遍,同样能把商品目录拼出来,只是慢一些、脏一些。

站内搜索结果页本身的SEO处理是另一个老问题——查得到和查无结果长得一模一样。而这个JSON端点是它的后台,两边的口径经常对不上:页面上说“没有找到相关商品”,接口里却明明返回了三条。排查搜索相关的收录问题时,先比一下这两边。

那几个真正该关的没开

值得说句公道话:这次试的地址里,涉及订单、客户、后台的那些一个都没有开着。/wp-json那一族在103个站里只有1个返回了真数据,Magento的管理接口有2个返回200但都是要鉴权的错误体,/graphql用GET方式探测只有2个给了正常响应。开着的全是平台设计上就打算公开的店面数据,没有一个是配置事故。这件事的性质因此变了:它不是漏洞,是默认值。

上新节奏能不能从这里读出来?

能读一半,另一半是个陷阱。

能读的那一半是created_at。22个站的3934个商品,上架日期距今的中位数是288天,最老的一个是3961天前——差不多十一年。90天内的新品占13.7%。逐站看,brooklinen.com的250条样本落在100个不同日期上、跨度从2018年到上个月,dreametech.com是106个不同日期。把这些日期按周聚一下,对方的上新节奏就是一条画得出来的曲线。

陷阱在updated_at。第一眼看到这个字段的时候我以为捡到宝了:改价、改文案、改库存,不都会刷新它吗?结果一算,3934个商品的updated_at100%落在最近7天内,中位数是1天

一个字段如果对所有对象都给出同一个答案,它就没有区分度。这个数不是“最近改过什么”,而是平台的内部写入时间——库存变动、缓存刷新、后台任何一次触碰都会把它推到今天。拿它去判断“对手多久改一次价”,会得出“所有人每天都在改”这种一看就不对的结论。

这是这轮实测里第三次尺子出问题。前两次一次是采集速率把被测对象逼成了429,一次是压缩口径把数字夸大了五倍多。这一次它甚至不报错,就是安安静静地给了你一个漂亮的、错的答案。

robots.txt在这件事上基本是缺席的

查了这103个站的robots.txt,79个站有,24个没拿到。它们对这几个地址的态度是这样的:

路径robots里写了不许抓的站服务器实际开着的站
/cart.js(含/cart5550
/search/suggest.json(含/search3344
/collections/all344
/products.json045

零。一个都没有。购物车和搜索路径倒是被拦得很勤——那是平台默认的robots模板里就带的,跟商家的意志没什么关系。而商品目录这个真正把家底端出去的地址,模板里没写,也就没人补。

这跟robots.txt分组不继承那件事是同一个成因:绝大多数站的robots是模板给的,商家从来没读过第二遍,更没往里加过东西。模板覆盖到的,站站都有;模板没覆盖的,站站都缺。

就算写了也拦不住人。robots.txt的规范约束的是自愿遵守它的自动程序,浏览器不看它,curl不看它,写脚本的人更不看。Google自己在文档里也把这两件事分得很清楚:Disallow管的是抓不抓,不是能不能访问。robots挡不住内容进模型是同一个道理,Google自己也有一整类抓取器不看robots.txt

robots说允许、实际却进不去的情况之前量过;这次是反过来的一面:robots没说不许,实际就真的给。

这些地址会不会自己进搜索引擎的索引?

会,而且已经有人踩过。JSON端点返回的是纯数据,没有<head>可以写noindex,唯一能声明的手段是HTTP响应头里的X-Robots-Tag——而这批站里没有一个发了这个头。爬虫抓到它,看到的是一份内容独特、体积可观的文本。

体积这一点值得单独算一笔。页面体积会直接吃掉抓取额度,而一份250条的商品JSON压缩前动辄一两MB,比同站任何一个页面都大。它被抓一次的开销,抵得上几十个商品页。

更麻烦的是重复内容:这份JSON里的body_html跟商品页正文是同一份文字。喂给平台的那份数据本来就是页面的另一个副本,再加上这个公开端点,同一段文案在你的域名下至少有三个可抓取的位置。

关得掉吗,关掉会不会伤到自己?

先说结论:在Shopify上,商品JSON这个出口在主题层面关不掉,robots.txt.liquid能改的只是robots那份声明,改不了服务器给不给。真要收口只有三条路,每条都有代价。

第一条:在边缘层拦

用CDN规则匹配路径,对非浏览器请求返回403或者限速。这条最直接,但会误伤——很多第三方应用、比价插件、你自己的中台同步任务,走的正是这个地址。拦之前先去日志里看一遍谁在调它。

第二条:把敏感字段清出去

标签是你自己写的,这一条完全可控。内部口径、渠道名、员工价、测试标记,全都不该写在tags里,该放到元字段(metafield)里去——元字段默认不出现在这份JSON中。这是投入最小、见效最快的一步。

清理的顺序建议这样走:先把全站标签词表导出来按出现次数排序,人工扫一遍前两百个;把明显是内部口径的挑出来,在主题代码里全局搜一遍有没有被引用;没被引用的直接删,被引用的先在模板里换成元字段判断再删。22个站的样本里内部标签一共464次,摊到单站是几十条的量级,一个下午能做完。

顺带把测试商品也清一遍。test这个词出现了234次,测试数据留在生产库里本身就是隐患,它们同时还会被按模板抽样的审计算进有效商品数,把你的品类分布也带偏。

第三条:接受它,然后利用它

数据本来就是要给顾客看的,价格和库存在页面上也能看到,只是没这么整齐。与其纠结关不关,不如承认这是双向的:你的对手同样开着这扇门。

你该怎么查自己的、怎么看别人的?

自查三条命令,一分钟出结果:

curl -s "https://你的域名/products.json?limit=1" | head -c 400

curl -s "https://你的域名/products.json?limit=250" \
  | grep -o '"tags":\[[^]]*\]' | tr ',' '\n' | grep -iE 'test|hidden|hide|exclude|employee|internal'

curl -s "https://你的域名/products.json?limit=250" \
  | grep -o '"available":false' | wc -l

第一条看门开没开,第二条把内部标签揪出来,第三条数一眼有多少变体正处在无货状态。三条都拿到数之后,先做第二条对应的清理,那是纯收益、零代价的。

至于看别人的,得说清楚边界。这些地址是平台公开文档记录过的店面接口,读它跟打开对方的商品页没有本质区别,做竞品分析时用它是常规操作。但有三条线别越:

  • 频率要克制。这次实测里有20个站在第二轮直接把我挡了,理由很正当——我要得太密。限速规则不认你的来意,只认你的节奏
  • 别做全量镜像。取样本判断趋势是研究,把对方整个目录连图带文案抄回来是另一回事。
  • 拿到的数据自己用,别再分发出去。

顺手能校准的三件事

这份数据对自己站的用处比对竞品的更大。第一,它是你商品数据的真实快照,可以拿来跟页面上的结构化数据逐条对账,看两边的价格与库存说的是不是同一件事。第二,无货率这个数没有任何后台会直接告诉你,而它直接影响那些零流量SKU的处置判断。第三,created_at加上published_at能算出你自己的上新到上线的时间差,这个数多数团队从来没量过。

保哥给一个做户外装备的客户做过一次这个清理,光是把tags里的渠道名和测试标记摘干净就花了两个小时,改完对外的商品JSON少了七百多个标签实例。电商SEO审计按模板抽样的那套方法在这里同样适用——不用逐个商品看,把标签词表拉出来扫一遍就够。

常见问题解答

这个地址是Shopify独有的吗?

商品JSON这个具体形态是Shopify的店面惯例,实测里45个开着的站有40个是Shopify。但“平台自带一个公开只读接口”这件事不是它独有的:WordPress的REST接口、各类框架的数据路由都属于同一类。区别在于默认是开还是关——Salesforce Commerce的10个站和Magento的3个站在这次实测里一个都没开。

竞争对手真的会去看这个吗?

比价工具和选品工具早就在用了,这不是什么秘密手法。真正值得担心的不是价格被看到——价格在页面上本来就是公开的——而是那些页面上看不到的东西:内部标签、测试商品、员工价、渠道标记、以及全量SKU的一次性导出。

把robots.txt里加一条Disallow能解决吗?

不能。robots.txt只对自愿遵守它的自动程序生效,浏览器和脚本完全不看。加这一条唯一的效果是让守规矩的搜索引擎不去抓这个地址,避免它进索引,这本身有点用,但跟“防止被拿走”是两回事。

无货的商品要不要从这份数据里去掉?

不建议去掉,因为主题模板要靠它渲染售罄状态。更值得做的是缩短无货商品在目录里停留的时间:长期无货又没有补货计划的SKU应该下架或者做重定向处理,那既是数据卫生问题,也是抓取预算问题。

updated_at为什么不能当改动时间用?

实测22个站3934个商品,这个字段100%落在最近7天内、中位数只有1天。它记录的是平台侧任何一次写入,库存扣减、缓存刷新、后台点开看一眼都会刷新它,跟“内容有没有改过”不是一回事。想追踪对手的改价,只能自己定期取数、自己存历史、自己做差分。

清理标签会影响前台功能吗?

会,所以顺序很重要:先在主题里全局搜一遍这个标签有没有被if判断引用,确认没有再删。渠道名、员工价、测试标记这类通常只在后台筛选时用,删了不影响前台;而over-50-off这类很可能正驱动着某个促销集合页,动之前必须确认。

我的站不是电商,这件事跟我有关吗?

有关,只是端点不同。任何一个内容管理系统都可能带着默认开启的只读接口,WordPress的REST接口能列出全部文章、用户名甚至草稿状态。判断方法一样:拿一个不存在的地址先探出这个站“说没有”的样子,再去试那些默认路径,看回来的是不是真数据。

权威参考资料

分享到
标签
版权声明

本文标题:《Shopify商品数据有个没人关的出口,一次能拿走250条》

本文链接:https://zhangwenbao.com/shopify-products-json-open-data-endpoint-audit.html

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

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