上一篇拆了 TikTok Shop 商品页的数据结构,这篇换Ozon(毛子电商)。同样是做要读懂的东西:一个商品页里有哪些结构化数据、按什么方式组织、怎么解析成一份干净 JSON。
Ozon 的前端架构和 TikTok 很不一样,值得单独说说。只摆事实和结果,文末附真实解析产物。
一、数据在哪:不在 HTML 里
Ozon 的商品页是Nuxt(Vue SSR)+ widget 架构。这带来一个反直觉的点:你抓到的页面 HTML,里面没有商品数据。
document.content()抓下来的 HTML,只有一层layout 骨架——一堆{"component":"webProductHeading","stateId":"..."}这样的布局占位,声明"这里有个标题 widget、那里有个价格 widget",但每个 widget 的实际内容(state)不在 HTML 里。window.__NUXT__里也只有 CDN、菜单这些外壳。
商品数据要单独从一个接口取:
composer-api.bx/page/json/v2?url=<商品路径>它返回的widgetStates才是真货,按 widget 名索引:webProductHeading(标题) /webPrice(价) /webShortCharacteristics(规格) /webGallery(图) /webAspects(变体)。
一个容易卡住的点:如果传给这个接口的商品 URL 无效(比如 id 拼错),Ozon 会把它匹配到另一个商品(一个带
errorwidget 的降级页),此时widgetStates里只有 header/footer 外壳、没有商品内容。排查半天会以为接口用法错了——其实只要换成有效的商品 URL,就返回完整数据。所以用真实存在的商品链接。
二、数据怎么组织:变体是"独立商品"
解析widgetStates后,字段都很规整。这里说一个和 TikTok完全相反的设计:
- TikTok:一个商品内含上百个 SKU(颜色×机型都在同一个商品对象里);
- Ozon:每个颜色 / 尺码是一个独立商品(兄弟关系),各有自己的
sku、URL 和价格。webAspects里列出的是这些兄弟变体。
实测一件男士 T 恤,webAspects有两个维度:颜色 38 个 × 尺码 2 个,每个变体带独立的商品链接和价格。做选品时这反而更方便——每个变体本来就是一条能单独取价的商品。
三、取价:读懂三档价的字段语义
Ozon 的价格有三个字段,做成本核算前得分清:
price—— 实际售价cardPrice—— Ozon 卡支付价(更低)originalPrice—— 划线原价
关键在originalPrice:它是个划线展示价,注水很凶。实测一条耳机划线18 900 ₽,而实际售价才2 846 ₽。
如果把
originalPrice当商品价来算成本,会严重偏离。成本核算只能用price或cardPrice。这不是玄学,就是读懂字段语义:划线价是营销展示值,price才是真实成交价。
四、结果
一个真实商品页(一条 Air Pro 3 耳机,5074 条评价):
右侧价格
2 592 ₽(卡价) /2 846 ₽(实售) /(划线)。左边 О товаре 是规格。18 900 ₽
解析成结构化 JSON(截取):
{ "title": "Наушники беспроводные c шумоподавлением Air Pro 3", "product_id": "3577720626", "price": { "price": "2 846 ₽", "card_price": "2 592 ₽", "original_price": "18 900 ₽" }, "characteristics": [ { "name": "Тип", "values": ["Наушники"] }, { "name": "Шумоподавление", "values": ["Активное"] }, … ], "images": [ "…5 张 ozone.ru CDN 高清图…" ], "aspects": [] // 单款无变体 }带变体的(男士 T 恤):
{ "title": "Футболка SADIQ набор футболок мужских 3 шт", "price": { "price": "1 075 ₽", "card_price": "968 ₽", "original_price": "4 999 ₽" }, "aspects": [ { "name": "Цвет", "variants": [ /* 38 个, 每个带 sku/价/独立链接/库存 */ ] }, { "name": "Размер", "variants": [ {"text":"48 RU / 48","price":"968 ₽"}, {"text":"56 RU / 56","price":"1 041 ₽"} ] } ] }单款(耳机)+ 多变体(T 恤 38 色 × 2 码)两类都能稳定解析。
一个下游要注意的点:字段值是俄文,做匹配前要么翻译,要么直接用图(
ozone.ru的 CDN 图可用)。
小结
- Ozon 是 Nuxt + widget 架构:商品数据不在 HTML,在
composer-api的widgetStates里。 - 用有效商品 URL取,无效 URL 会被匹配到降级页、只返外壳。
- 变体是独立商品(各有 sku/url/价),和 TikTok 的"内部 SKU"相反。
- 取价读懂字段语义:
originalPrice是划线展示价,price/cardPrice才是真实价。
对比 TikTok:两家数据组织方式差很多,但"划线价/起步价 vs 真实价"的字段语义问题是共性——做成本核算都得挑对字段。
交流 各种电商数据
- 微信:
NarraData