Files
2026-09-20 15:23:44 +08:00

16 KiB
Raw Permalink Blame History

类目属性与字典值 API

官方文档:https://docs.ozon.ru/api/seller/zh/#operation/DescriptionCategoryAPI_GetAttributes


1. 获取类目属性

接口信息

项 值
方法 POST
路径 /v1/description-category/attribute
鉴权 需要 Client-Id + Api-Key
用途 获取指定类目的所有属性(发布商品时需填写)

请求

{
  "description_category_id": 17033876,
  "type_id": 97114,
  "language": "RU"
}

参数说明

字段 类型 必填 说明
description_category_id integer ✅ 是 类目 ID(从类目树获取)
type_id integer ✅ 是 商品类型 ID(从类目树获取,与 category_id 配对)
language string 可选 语言代码:DEFAULT / RU / EN / ZH_HANS。默认 DEFAULT

响应

{
  "result": [
    {
      "id": 85,
      "name": "Бренд",
      "description": "Укажите бренд товара",
      "type": "String",
      "is_collection": false,
      "is_required": true,
      "is_aspect": false,
      "max_value_count": 1,
      "dictionary_id": 28732,
      "category_dependent": false,
      "group_id": 0,
      "group_name": ""
    },
    {
      "id": 8229,
      "name": "Цвет товара",
      "description": "",
      "type": "String",
      "is_collection": false,
      "is_required": false,
      "is_aspect": true,
      "max_value_count": 1,
      "dictionary_id": 61405,
      "category_dependent": false,
      "group_id": 1,
      "group_name": "Варианты"
    },
    {
      "id": 9048,
      "name": "Объем",
      "description": "Укажите объем в миллилитрах",
      "type": "Integer",
      "is_collection": false,
      "is_required": true,
      "is_aspect": false,
      "max_value_count": 1,
      "dictionary_id": 0,
      "category_dependent": false,
      "group_id": 2,
      "group_name": "Основные"
    }
  ]
}

字段说明

字段 类型 说明
id integer 属性 ID(发布时填 attributes[].id)
name string 属性名称(如"品牌"、"颜色")
description string 属性说明(填写提示)
type string 值类型:String / Integer / Decimal / Boolean / URL
is_required boolean 是否必填。true = 必须填写,否则发布失败
is_aspect boolean 是否变体属性(如颜色/尺码)。true = 该属性用于区分 SKU 变体
is_collection boolean 是否多值。true = 可填多个值(如"适用场景:家用,办公")
max_value_count integer 最多值数量(is_collection=true 时有效)
dictionary_id integer 字典 ID。> 0 = 有预设值字典(需调字典值接口),0 = 自由输入
category_dependent boolean 字典值是否依赖类目(true = 不同类目的字典值不同)
group_id integer 属性分组 ID
group_name string 属性分组名(如"基本信息"、"变体")

关键字段组合

组合 含义 示例 填写方式
is_required=true 必填 品牌、尺寸、重量 必须有值,否则发布失败
dictionary_id > 0 有字典 品牌、颜色、材质 值必须从字典选(dictionary_value_id)
dictionary_id = 0 自由输入 商品名、描述、数值 直接填文本/数字
is_aspect=true 变体属性 颜色、尺码 用于区分 SKU(不同颜色 = 不同 SKU)
is_collection=true 多值 适用场景、材质组成 可传数组 ["值1", "值2"]

2. 获取属性值字典

接口信息

项 值
方法 POST
路径 /v1/description-category/attribute/values
鉴权 需要 Client-Id + Api-Key
用途 获取属性的预设值字典(dictionary_id > 0 的属性)

请求

{
  "attribute_id": 85,
  "description_category_id": 17033876,
  "type_id": 97114,
  "language": "RU",
  "limit": 1000,
  "last_value_id": 0
}

参数说明

字段 类型 必填 说明
attribute_id integer ✅ 是 属性 ID
description_category_id integer ✅ 是 类目 ID
type_id integer ✅ 是 商品类型 ID
language string 可选 语言代码
limit integer ✅ 是 每页数量,范围 1–2000;Atlas 当前首批请求使用 1000
last_value_id integer 可选 分页游标(上一页最后一个值的 id),首页传 0

响应

{
  "result": [
    {
      "id": 971082156,
      "value": "Thermos",
      "info": "",
      "picture": ""
    },
    {
      "id": 971317107,
      "value": "Stanley",
      "info": "",
      "picture": ""
    }
  ],
  "has_next": true
}

字段说明

字段 类型 说明
id integer 字典值 ID(发布时填 attributes[].values[].dictionary_value_id)
value string 字典值文本(如品牌名"Thermos")
info string 补充说明
picture string 值配图 URL(部分属性有,如颜色)
has_next boolean 是否有下一页(true = 用最后一个 id 继续分页)

分页示例

async def fetch_all_values(attribute_id, category_id, type_id):
    all_values = []
    last_id = 0
    
    while True:
        resp = await client.post(
            "https://api-seller.ozon.ru/v1/description-category/attribute/values",
            json={
                "attribute_id": attribute_id,
                "description_category_id": category_id,
                "type_id": type_id,
                "language": "RU",
                "limit": 2000,
                "last_value_id": last_id
            }
        )
        data = resp.json()
        values = data.get("result", [])
        all_values.extend(values)
        
        if not data.get("has_next") or not values:
            break
        
        last_id = values[-1]["id"]
    
    return all_values

3. 按关键词搜索属性值

接口信息

项 值
方法 POST
路径 /v1/description-category/attribute/values/search
鉴权 需要 Client-Id + Api-Key
用途 模糊搜索字典值(避免拉全量字典)

请求

{
  "attribute_id": 85,
  "description_category_id": 17033876,
  "type_id": 97114,
  "language": "RU",
  "value": "Ther",
  "limit": 100
}

参数说明

字段 类型 必填 说明
attribute_id integer ✅ 是 属性 ID
description_category_id integer ✅ 是 类目 ID
type_id integer ✅ 是 商品类型 ID
language string 可选 语言代码
value string ✅ 是 搜索关键词(≥2 个字符)
limit integer 可选 返回数量,最大 100,默认 50

响应

{
  "result": [
    {
      "id": 971082156,
      "value": "Thermos",
      "info": "",
      "picture": ""
    },
    {
      "id": 971982345,
      "value": "Thermocafe",
      "info": "",
      "picture": ""
    }
  ]
}

4. 属性映射工作流

场景:采集来的参数 → Ozon 属性

采集原文(raw.params):
[
  { "key": "Материал", "value": "Нержавеющая сталь" },
  { "key": "Объем", "value": "500 мл" },
  { "key": "Бренд", "value": "Thermos" }
]

↓ ① 自动匹配属性名

attributes = [
  { id: 8505, name: "Материал" },   // 材质
  { id: 9048, name: "Объем" },       // 容量
  { id: 85, name: "Бренд" }          // 品牌
]

↓ ② 对有字典的属性(dictionary_id > 0),搜索字典值

POST /attribute/values/search {
  attribute_id: 85,  // 品牌
  value: "Thermos"
}
→ { id: 971082156, value: "Thermos" }

↓ ③ 组装最终 attributes

products.attributes = [
  {
    "complex_id": 0,
    "id": 8505,
    "values": [{ "value": "Нержавеющая сталь" }]  // 材质无字典,直接填
  },
  {
    "complex_id": 0,
    "id": 9048,
    "values": [{ "value": "500" }]  // 容量是数值,提取数字
  },
  {
    "complex_id": 0,
    "id": 85,
    "values": [{ "dictionary_value_id": 971082156, "value": "Thermos" }]  // 品牌有字典
  }
]

5. 自动匹配策略

策略 A:归一化 + 模糊匹配

import re
from difflib import SequenceMatcher

def normalize(text: str) -> str:
    """归一化:小写 + 去标点 + 词干"""
    text = text.lower().strip()
    text = re.sub(r'[^\w\s]', '', text)
    # 俄文词干化(需 pymorphy2 库)
    # text = morph.parse(text)[0].normal_form
    return text

def fuzzy_match(采集key: str, 属性列表: list, threshold=0.8):
    """模糊匹配:相似度 > 0.8 即认为匹配"""
    norm_key = normalize(采集key)
    best = None
    best_score = 0
    
    for attr in 属性列表:
        norm_name = normalize(attr["name"])
        score = SequenceMatcher(None, norm_key, norm_name).ratio()
        if score > best_score:
            best = attr
            best_score = score
    
    return best if best_score >= threshold else None

策略 B:关键词映射表

# 预定义常见映射(中文采集 key → Ozon 属性 name)
KEYWORD_MAP = {
    "品牌": ["Бренд", "Brand"],
    "材质": ["Материал", "Material"],
    "重量": ["Вес", "Weight"],
    "尺寸": ["Размер", "Size"],
    "颜色": ["Цвет", "Color"],
    # ... 补充更多
}

def keyword_match(采集key: str, 属性列表: list):
    for cn_key, ru_keys in KEYWORD_MAP.items():
        if cn_key in 采集key:
            for attr in 属性列表:
                if any(rk in attr["name"] for rk in ru_keys):
                    return attr
    return None

6. 必填项校验

发布前校验

async def validate_required_attributes(
    category_id: int,
    type_id: int,
    attributes: list
) -> list[str]:
    """返回缺失的必填属性名列表"""
    # 获取该类目的所有属性
    attrs = await fetch_category_attributes(category_id, type_id)
    
    # 提取必填属性
    required = [a for a in attrs if a["is_required"]]
    
    # 已填写的属性 ID
    filled_ids = {a["id"] for a in attributes}
    
    # 找出缺失的
    missing = [a["name"] for a in required if a["id"] not in filled_ids]
    
    return missing

# 使用
missing = await validate_required_attributes(17033876, 97114, product.attributes)
if missing:
    raise ValueError(f"缺少必填属性:{', '.join(missing)}")

7. 缓存策略

Ozon 只定义查询参数和分页规则,不规定 Atlas 的 TTL。Atlas 已按 2026-09-18 的架构决策将属性定义和字典候选值迁移为平台级参考数据:同一平台所有门店共用,本阶段不维护门店差异层。商品编辑器、草稿校验和发布预检都从 Atlas 平台缓存读取,Ozon 请求统一由 Worker 使用平台参考源凭据执行。

类型属性和字典默认 14 天内 fresh,14~21 天(含 21 天)stale 时旧缓存可用并低优先级刷新,严格超过 21 天或从未同步时必须高优先级刷新成功后才能作为当前 schema、候选值或发布依据。系统不周期性扫描属性和字典;刷新由管理员批次/单字典操作、普通用户访问或发布预检触发。

字典候选值至少以平台、类目类型、Atlas 属性 UUID、外部值 ID 和语言限定作用域。category_dependent=false 可以作为进一步去重的依据,但在跨账号/跨类目验证前不能只以 dictionary_id 合并。小字典可完整分页并标记 complete;大字典保存已同步页与搜索命中并明确标记 partial。搜索结果不是全集,不能据此清理未命中的旧值;partial 字典的规范化关键词有独立缓存状态。成功后原子更新缓存,失败保留上次成功值。


8. 前端交互设计

属性映射 UI(推荐)

┌─────────────────────────────────────────────────┐
│ 类目属性映射                                      │
├─────────────────────────────────────────────────┤
│ 采集属性               → Ozon 属性                │
├─────────────────────────────────────────────────┤
│ ✅ Материал (材质)     → [自动] Материал (8505)   │
│    值:Нержавеющая сталь                         │
├─────────────────────────────────────────────────┤
│ ✅ Бренд (品牌)        → [自动] Бренд (85) ⚠️必填 │
│    值:Thermos         → 字典值:[选择 ▼]         │
│                          ├ Thermos ✅             │
│                          ├ Stanley               │
│                          └ ...                   │
├─────────────────────────────────────────────────┤
│ ⚠️ 未匹配:包装重量                              │
│    → [手动选择属性 ▼]                            │
├─────────────────────────────────────────────────┤
│ ❌ 缺少必填属性:                                │
│    - Объем (容量)   [+添加]                      │
│    - Цвет (颜色)    [+添加]                      │
└─────────────────────────────────────────────────┘

9. 常见问题

Q1: 属性太多怎么办?

A: 一个类目可能有 50-100+ 属性,但常用的只有 10-20 个。策略:

  • 必填属性置顶 + 高亮
  • 已匹配属性展开,未匹配折叠
  • 提供搜索/筛选

Q2: 字典值有多大?

A:

  • 小字典(颜色/材质):几十到几百条
  • 大字典(品牌):数万条(如品牌字典 > 50,000)
  • 策略:优先用 /values/search,避免拉全量

Q3: 自由输入的属性怎么填?

A: dictionary_id=0 的属性直接填 value,无需 dictionary_value_id:

{
  "id": 9048,
  "values": [{ "value": "500" }]  // 容量,数值类型
}

Q4: 如何处理多值属性?

A: is_collection=true 时传数组:

{
  "id": 10096,
  "values": [
    { "value": "家用" },
    { "value": "办公" }
  ]
}

10. Atlas 当前集成

  • 商品编辑器通过 GET /api/product-platform-profiles/{profile_id}/attribute-schema 读取当前平台类型属性,响应包含 Atlas attribute_id 和统一 cache 元数据。
  • 字典通过 GET /api/product-platform-profiles/{profile_id}/attribute-dictionaries/{attribute_id} 读取;attribute_id 是平台属性 UUID,不以门店或单独 dictionary_id 定位。
  • fresh/stale 立即返回;expired/missing 创建或复用高优先级任务并最多等待 5 秒,仍未完成时返回 202。浏览器不会拿到平台凭据或未落库的 Ozon 临时结果。
  • 发布前使用 POST /api/product-platform-profiles/{profile_id}/publish-preflights,只刷新当前类型 schema 和所选 SKU 实际使用的字典,再执行必填、scope、字典有效性及其他首次上架校验。
  • 管理后台支持三种类型属性批次、单字典同步、缓存策略弹窗和最近任务观察;类目树固定每 7 天同步,属性与字典不做周期性全量扫描。

相关文档