×

《拼多多商品API:pdd.goods.detail.get 与 pdd.goods.cats.get 的类目与规格踩坑》(附 Python 源码)

万邦科技Lex 万邦科技Lex 发表于2026-10-15 13:19:13 浏览32 评论0

抢沙发发表评论

《拼多多商品API:pdd.goods.detail.get 与 pdd.goods.cats.get 的类目与规格踩坑》(附 Python 源码)

先拍结论:
拼多多商品模型里最坑的不是“拿不到数据”,而是类目有两套、规格是组合爆炸、cat_id 不能直接当发布类目、spec 不是 SKU。
  • pdd.goods.cats.get:标准类目树(全平台类目字典),不是“当前店铺能发什么”

  • pdd.goods.authorization.cats:店铺可发布类目(受经营范围限制)

  • pdd.goods.detail.get:商品视图,cat_id / cat_ids 是归属类目,sku_list / goods_specs 才是可售单元

  • 拼多多的“规格”是 维度(spec_key) × 值(spec_value) → SKU 组合,直接平铺就会下错“红色 / M”


一、两个接口的职责边界

接口
视角
主要用途
常见误用
pdd.goods.cats.get
平台标准类目树
类目 ID ↔ 名称、层级、父子关系
拿它当“本店可发类目”
pdd.goods.authorization.cats
商家可发布类目
发布商品时选类目
忘了循环下钻到叶子
pdd.goods.detail.get
单个商品详情
标题/价格/图/类目/规格/SKU
以为 specs 就是可售 SKU
官方商品发布说明里写得很清楚:
  • 标准类目用 pdd.goods.cats.get

  • 商家可发类目用 pdd.goods.authorization.cats

  • 要发商品必须循环下钻到最子集(叶子)类目,拼多多商品必须有三级或四级分类


二、类目踩坑 1:cat_id ≠ 可发布类目

pdd.goods.detail.get 返回:
{
  "cat_id": 12345,
  "cat_ids": [100, 200, 12345]
}
含义:
  • cat_id:商品当前归属类目(可能是三级/四级)

  • cat_ids:一级 → 二级 → 三级 → 四级 路径

但:
店铺能不能发这个类目,要看 pdd.goods.authorization.cats。
不在店铺经营范围 → 用 cat_id 调 pdd.goods.add 直接报:
“当前商品分类不在店铺经营范围内”
✅ 正确动作:
  1. authorization.cats(parent_cat_id=0) 拿一级可发类目

  2. 逐级下钻到叶子

  3. 再用叶子 cat_id 发商品

  4. goods.cats.get 只做“类目 ID → 名称 / 路径展示 / 跨平台映射”


三、类目踩坑 2:类目会到四级,不是淘宝三级就完事

pdd.goods.cats.get 返回结构:
{
  "goods_cats_get_response": {
    "goods_cats_list": [
      {
        "cat_id": 239,
        "cat_name": "男装",
        "level": 1,
        "parent_cat_id": 0
      },
      {
        "cat_id": 242,
        "cat_name": "T恤",
        "level": 3,
        "parent_cat_id": 241
      }
    ]
  }
}
规则:
  • level=1 一级,level=2 二级,level=3 三级,level=4 四级

  • 有些类目三级是叶子,有些要四级

  • 发布必须用叶子;用父级 cat_id 发商品 → 审核挂起/类目校验失败


四、规格踩坑 3:specs 不是 SKU,是“维度值清单”

拼多多常见返回(简化):
{
  "goods_specs": [
    {"spec_key": "颜色", "spec_value": "红色", "spec_id": 1},
    {"spec_key": "颜色", "spec_value": "蓝色", "spec_id": 2},
    {"spec_key": "尺码", "spec_value": "M", "spec_id": 3},
    {"spec_key": "尺码", "spec_value": "L", "spec_id": 4}
  ],
  "sku_list": [
    {"sku_id": "111", "specs": [1, 3], "price": 3900, "quantity": 10},
    {"sku_id": "112", "specs": [1, 4], "specs_info": [{"key":"颜色","value":"红色"},{"key":"尺码","value":"L"}], "price": 4100, "quantity": 8},
    {"sku_id": "113", "specs": [2, 3], "price": 3900, "quantity": 5}
  ]
}
❌ 错误模型:
options = ["红色", "蓝色", "M", "L"]   # 用户选规格时下成 红色 + M 两个标签
✅ 正确模型:
  • goods_specs:规格维度值池

  • sku_list[].specs:该 SKU 引用的 spec_id 组合

  • 真正下单/库存/价格单位是 sku_id


五、Python:类目树加载器

# pdd/category_tree.py
from typing import Optional


class PddCategoryTree:
    def __init__(self, client):
        self.client = client
        self.cache = {}   # cat_id -> node
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
    def load_children(self, parent_cat_id: int = 0):
        resp = self.client.execute("pdd.goods.cats.get", {
            "parent_cat_id": parent_cat_id,
        })
        nodes = (
            resp.get("goods_cats_get_response", {})
                .get("goods_cats_list", [])
        )
        for n in nodes:
            self.cache[n["cat_id"]] = n
        return nodes

    def load_full_path(self, cat_ids: list[int]):
        """cat_ids=[100,200,12345] -> 名称路径"""
        return [self.cat_name(cid) for cid in cat_ids]

    def cat_name(self, cat_id: int) -> Optional[str]:
        if cat_id in self.cache:
            return self.cache[cat_id]["cat_name"]
        # 没缓存就查一次
        children = self.load_children(0)
        return self.cache.get(cat_id, {}).get("cat_name")

    def is_leaf(self, cat_id: int) -> bool:
        children = self.load_children(cat_id)
        return len(children) == 0
店铺可发类目(发布用):
def shop_publishable_leaf_cats(client):
    leaves = []

    def dfs(parent):
        nodes = client.execute("pdd.goods.authorization.cats", {
            "parent_cat_id": parent
        }).get("goods_auth_cats_get_response", {}).get("goods_cats_list", [])
        if not nodes:
            leaves.append(parent)
            return
        for n in nodes:
            dfs(n["cat_id"])

    dfs(0)
    return leaves

六、Python:规格 → SKU 组合解析

# pdd/spec_resolver.py
from dataclasses import dataclass
from typing import List, Dict


@dataclass
class ResolvedSku:
    sku_id: str
    spec_combo: Dict[str, str]   # {"颜色": "红色", "尺码": "M"}
    price_yuan: float
    stock: int


def build_spec_index(goods_specs: list) -> Dict[int, dict]:
    """
    spec_id -> {key, value}
    """
    idx = {}
    for s in goods_specs or []:
        idx[s["spec_id"]] = {
            "key": s.get("spec_key"),
            "value": s.get("spec_value"),
        }
    return idx
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex

def resolve_skus(goods_detail: dict) -> List[ResolvedSku]:
    specs = goods_detail.get("goods_specs", [])
    spec_idx = build_spec_index(specs)

    out = []
    for sku in goods_detail.get("sku_list", []):
        combo = {}
        for sid in sku.get("specs", []):
            meta = spec_idx.get(sid)
            if meta:
                combo[meta["key"]] = meta["value"]

        # 拼多多价格单位:分
        price_fen = sku.get("price") or sku.get("sku_price") or 0
        stock = sku.get("quantity") or sku.get("sku_quantity") or 0

        out.append(ResolvedSku(
            sku_id=str(sku["sku_id"]),
            spec_combo=combo,
            price_yuan=price_fen / 100.0,
            stock=int(stock),
        ))
    return out
消费侧:
skus = resolve_skus(detail)
for s in skus:
    print(s.sku_id, s.spec_combo, s.price_yuan, s.stock)
# 111 {'颜色': '红色', '尺码': 'M'} 39.0 10
⚠️ 有的接口返回 sku_list[].specs_info=[{"key":"颜色","value":"红色"}],
有的只给 specs=[spec_id,...]。
生产里两个都兼容,别只信一种。

七、跨平台规格归一(接前几篇)

平台
规格模型
可售主键
1688
skuInfo / skuArray,属性对
offerId + skuId
淘宝
skus.sku.properties_name = pid:vid:名:值
num_iid + sku_id
京东
skuList[].specText
skuId + skuList.skuId
拼多多
goods_specs 维度值 + sku_list.specs 引用
goods_id + sku_id
统一模型:
{
  "platform": "pdd",
  "spu_id": goods_id,
  "sku_id": sku_id,
  "attrs": {"颜色": "红色", "尺码": "M"},
  "price": 39.00,
  "currency": "CNY",
  "stock": 10,
  "stock_trusted": True,
}
规则:
  • 跨平台别用“颜色/尺码”硬字段

  • 用 attrs: dict + sku_fingerprint = sha1(json(sorted(attrs)))


八、类目映射踩坑:用 cat_id 跨平台直连会翻车

淘宝 cid3=50012029 ≠ 京东 cid3=123 ≠ 拼多多 cat_id=12345
三者没有全局主键。
正确做法:
  1. 各平台拉类目树

  2. 建 platform_cat_id ↔ 标准类目词(女装/T恤/3C)

  3. 用“类目路径文本 + 属性词”做向量/规则匹配

  4. 低置信度 → 人工审

def pdd_cat_path(client, cat_ids):
    tree = PddCategoryTree(client)
    return " > ".join(tree.cat_name(c) for c in cat_ids)
# "男装 > T恤 > 短袖T恤"

九、发布商品时的类目/规格顺序(很重要)

1. authorization.cats 下钻到叶子 cat_id
2. goods.cat.rule.get 拿类目必填属性
3. goods.spec.get / goods.spec.id.get 建规格维度
4. 组装 sku_list:spec 组合 → 价格 → 库存
5. goods.img.upload 传图
6. goods.add / goods.edit.goods.commit
7. goods.commit.status.get 查审核
最常见失败:
  • 用标准类目 cats.get 的 id 直接发 → 不在经营范围

  • 用父级类目发 → 类目校验失败

  • 只传 specs 不传 sku_list → 没可售单元

  • 把“颜色:红”和“尺码:M”当两个 SKU → 下单串规格


十、字段缺失清单(拼多多特有)

  • cat_ids 可能空 → 用 cat_id 反查,但别信它完整

  • goods_desc 可能带 HTML / 被审核截断

  • min_group_price / min_normal_price 单位是分

  • sku_list 可能空(仅 SPU 无 SKU 的商品)

  • 多多进宝接口 pdd.ddk.goods.detail 返回结构和商家接口不同,别混 parser

  • goods_sign 是加密商品标识,goods_id 在某些场景会被引导用 goods_sign 替代


十一、和前几篇收口

  • 1688:offerId+skuId,批发视角,库存快照不可信

  • 淘宝:item.get 公开 / item.seller.get 私有,SKU 是 properties_name

  • 京东:jingdong.item.read.get 是 SPU+skuList+priceInfo+stockInfo

  • 拼多多:goods_specs 是维度值池,sku_list.specs 才是组合,类目有“标准类目 / 可发类目”两套

一句话:
淘宝把 SKU 藏在 properties_name 里,京东把 SKU 放在 skuList.specText 里,
拼多多把 SKU 拆成 spec 维度再让你自己笛卡尔——
谁直接把 颜色:红, 尺码:M 当两个下拉项,谁就会把“红色M”卖成“红色 + M”。
下一篇可以写:
《拼多多 × 淘宝 × 京东 × 1688 SKU 指纹统一:spec_dict → sha1 → erp_sku_id 的跨平台商品主数据模型》
要我直接写吗?


群贤毕至

访客