×

未分类

淘宝开放平台 vs 1688 vs 京东宙斯:能力、权限、坑点全梳理(附 Python 源码)

万邦科技Lex 万邦科技Lex 发表于2026-10-27 浏览16 评论0
# 淘宝开放平台 vs 1688 vs 京东宙斯:能力、权限、坑点全梳理(附 Python 源码)

这篇跟前几篇不一样:没有故事线,是一篇**查表文**。

它解决的是每个接入团队都会遇到的一个问题——三个平台的文档各说各话,你没办法把三份文档摊在一起比。这篇就是把它们摊在一起比完了的样子。

建议先收藏,用到哪条查哪条。文章末尾有可直接跑的签名与请求代码,以及一份能力声明表——那部分是能直接抄进项目里的。

---

## 一、三个平台,三种"人设"

动手之前先建立整体印象,后面查表才有方向:

**淘宝开放平台(TOP)**  
年纪最大、接口最多、文档最全,也因此**历史包袱最重**。同一种数据往往有三四个接口能拿到,字段命名横跨三个时代的风格。它的沙箱环境是三家里最不靠谱的——这是它最大的坑。

**京东宙斯**  
流程最"企业化":资质要求最严、回调要求 HTTPS + 备案域名、审核规范。回报是**类目属性 schema 三家里最完整**,做商品发布类需求它最省心。它的坑集中在参数格式和返回体结构上,都是小坑,但数量多。

**1688 开放平台**  
唯一的 B2B。它没有"零售"概念:没有 SKU 库存、价格是区间、按起订量阶梯。**不能把它当第三个淘宝来接**——这是接 1688 最大的认知错误。它的坑集中在签名和图片上。

---

## 二、能力对照大表

这张表是全文的核心。✓ = 支持,△ = 部分支持或有条件,✗ = 不支持。

| 维度              | 淘宝 TOP                  | 京东宙斯                          | 1688                    |
| --------------- | ----------------------- | ----------------------------- | ----------------------- |
| **签名算法**        | MD5 三明治                 | MD5 三明治                       | **HMAC-SHA1 + urlPath** |
| **时间戳(签名用)**    | 秒,`yyyy-MM-dd HH:mm:ss` | 秒,同左                          | **毫秒**,`_aop_timestamp` |
| **业务时间参数**      | `yyyy-MM-dd HH:mm:ss`   | `yyyy-MM-dd HH:mm:ss`,部分接口只到天 | 秒级时间戳                   |
| **业务参数传递**      | 平铺 query                | 打包进 `360buy_param_json`       | 平铺 query                |
| **返回结构**        | 平铺                      | **按接口名嵌套,要剥壳**                | 平铺                      |
| **分页方式**        | `page_no / page_size`   | `page / pageSize`             | `startIndex / pageSize` |
| **分页上限**        | 100 / 页                 | 100 / 页                       | 部分接口 startIndex 有上限     |
| **商品详情**        | ✓                       | ✓                             | ✓                       |
| **SKU 级库存**     | ✓                       | △(部分接口才返回)                    | **✗(按起订量卖,无 SKU 库存)**   |
| **订单列表**        | ✓                       | ✓                             | ✓(分销单为主)                |
| **物流轨迹**        | ✓(增量接口 + 轨迹)            | △(部分按天查)                      | △(以发货状态为主)              |
| **消息推送(TMC 类)** | ✓ TMC                   | △ 部分消息                        | ✗(基本靠拉)                 |
| **沙箱环境**        | △ **长期不同步**             | ✓ 相对可用                        | ✗                       |
| **图片外链**        | ✓ 可外链                   | ✓ 可外链                         | △ **有防盗链**              |
| **token 有效期**   | 自用型长,授权型短               | 短 + refresh_token             | 长                       |
| **审核周期**        | 1~3 周                   | 1~2 周                         | 3~7 天                   |
| **限流模型**        | appkey × 接口,双限          | 应用 × 接口,双限                    | appkey 流量包 + QPS        |
| **适合谁**         | 淘系卖家、全域比价               | 京东 POP / 自营                   | 分销、一件代发、选品              |

三个格子单独说一下,因为它们最容易被误判:

**1688 没有 SKU 库存**。这不是接口没开放,是业务模型不同——1688 的商品按"起订量 → 单价"的区间卖,压根没有"某个 SKU 剩几件"的概念。你在第 8 篇见过的 `NotSupported` 异常,主要就是为它准备的。硬要接的话,能拿到的是"可售状态 + 阶梯价",拿不到库存数。

**淘宝沙箱长期不同步**。新接口没有沙箱、旧接口沙箱返回的字段和线上不一致,这两件事都是常态。所以我们的冒烟测试打的是线上真实接口(用固定样本、极低频率),沙箱只用来验证签名和连通性。**别信沙箱的返回体**。

**1688 图片有防盗链**。商品图直接 `<img src>` 塞进自己的页面,会有一部分图挂掉(referer 校验)。转存的时候要么带 Referer 伪造,要么走它提供的图片网关——前者不稳定,后者要申请。这个坑在第 2 篇的图片转存里埋过,这里说清楚原因。

---

## 三、权限申请对照:代码之外花时间最多的事

第 6 篇说过:代码三天写得完,权限可能等三周。这里把三个平台的申请路径摊开。

| 环节      | 淘宝 TOP             | 京东宙斯             | 1688              |
| ------- | ------------------ | ---------------- | ----------------- |
| 主体要求    | 企业/个人开发者入驻         | **企业资质**(营业执照等)  | 企业认证              |
| 应用类型    | 自用型 / 他用型(ISV)     | 自用 / 工具型         | 自用应用 / 他用         |
| 接口权限    | 按**权限包**申请,敏感接口单独审 | 控制台勾选 + 部分白名单    | 权限包,B2B 类目相关要单独申请 |
| 回调要求    | 域名备案               | **HTTPS + 备案域名** | HTTPS             |
| 最容易卡的地方 | "他用型"的场景说明要写清楚     | 资质材料一次过审的很少      | 类目权限漏申请           |

三条实战建议:

1. **权限包按"最终要调的接口"倒推**,不是按"类目"申请。淘宝的权限包和接口是多对多关系,最容易犯的错是以为申请了商品权限包就能调所有商品接口——`item.seller.get` 和 `items.list.get` 在不同的包里。
2. **场景说明写具体业务**。"数据同步"会被打回,"为本店已成交订单同步物流轨迹用于售后跟踪"能过。审核看的是你会不会拿数据干别的。
3. 三个平台的 token 机制不一样,**自用型和授权型混着用迟早出事**。自用型 token 长期有效但只能操作自己的店;授权型短,要走 refresh。如果你的产品要服务多家店,从第一天就按授权型设计,别混。

---

## 四、签名:三个平台,三种脾气

签名是接入期最先撞上的东西,也是最值得写成代码的部分。三家的算法其实是"一个思路 + 两个变体":

- 淘宝、京东:`md5(secret + k1v1k2v2... + secret)`,**几乎一样**
- 1688:`hmac_sha1(secret, urlPath + k1v1k2v2...)`,**urlPath 也参与签名**,且时间戳是毫秒

```python
# signers.py
import hashlib
import hmac


def _sorted_concat(params: dict) -> str:
    """k1v1k2v2...,按 key 的 ASCII 排序——三平台一致的要求"""
    return "".join(f"{k}{params[k]}" for k in sorted(params))


def sign_taobao(secret: str, params: dict) -> str:
    """TOP:md5(secret + 拼接 + secret),大写"""
    raw = secret + _sorted_concat(params) + secret
    return hashlib.md5(raw.encode("utf-8")).hexdigest().upper()


def sign_jd(secret: str, params: dict) -> str:
    """宙斯:算法同 TOP,但业务参数打包在 360buy_param_json 里一起签"""
    raw = secret + _sorted_concat(params) + secret
    return hashlib.md5(raw.encode("utf-8")).hexdigest().upper()


def sign_alibaba(secret: str, url_path: str, params: dict) -> str:
    """
    1688:hmac-sha1(secret, urlPath + 拼接),大写。
    两个独特点:
    1. urlPath 参与签名——漏了它,签名永远对不上,而且报错信息看不出原因
    2. 时间戳用毫秒(_aop_timestamp),用秒的话签名同样对不上
    """
    raw = url_path + _sorted_concat(params)
    return hmac.new(secret.encode("utf-8"), raw.encode("utf-8"),
                    hashlib.sha1).hexdigest().upper()
```

签名调试有一个万能技巧,比任何文档都有用:**把待签字符串打出来,和平台报错页给的"期望签名"对比**。三个平台的签名错误报错都一样没用(就一句 `sign error`),唯一的调试方法就是对着拼。我们第 6 篇说"签名预估 0.5 天实际 1 天",多出来的半天基本都耗在这里。

---

## 五、三个客户端:把平台的怪脾气消化在适配器里

签名之上是请求。三个平台的"怪脾气"——京东的参数打包、京东的返回剥壳、淘宝的错误藏 body、1688 的毫秒时间戳——都在这一层消化掉,业务层不该看见它们。

```python
# clients.py
import json
import time

import httpx

from signers import sign_taobao, sign_jd, sign_alibaba
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex

class PlatformError(Exception):
    def __init__(self, code, msg, platform):
        super().__init__(f"[{platform}] {code}: {msg}")
        self.code = str(code)
        self.msg = msg
        self.platform = platform


# ---------- 淘宝 TOP ----------

async def taobao_call(client: httpx.AsyncClient, cfg: dict, session: str,
                      method: str, api_params: dict) -> dict:
    p = {
        "method": method,
        "app_key": cfg["app_key"],
        "session": session,
        "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
        "format": "json",
        "v": "2.0",
        "sign_method": "md5",
        **{k: str(v) for k, v in api_params.items()},
    }
    p["sign"] = sign_taobao(cfg["app_secret"], p)

    r = await client.get("https://gw.api.taobao.com/router/rest", params=p)
    data = r.json()

    # TOP 的 HTTP 永远 200,错误藏在 body 的 error_response 里
    # ——只看状态码,监控永远是绿的(第 9 篇的口径问题就出在这)
    if "error_response" in data:
        err = data["error_response"]
        raise PlatformError(err.get("code"), err.get("msg"), "taobao")
    return data


# ---------- 京东宙斯 ----------

async def jd_call(client: httpx.AsyncClient, cfg: dict, token: str,
                  method: str, body: dict) -> dict:
    p = {
        "method": method,
        "app_key": cfg["app_key"],
        "access_token": token,
        "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
        "v": "2.0",
        # 业务参数整体打包成一个 JSON 字符串,再作为普通参数参与签名
        # 字段名大小写敏感:文档里的 snake_case 只是显示,实际要 camelCase
        "360buy_param_json": json.dumps(
            body, ensure_ascii=False, separators=(",", ":")
        ),
    }
    p["sign"] = sign_jd(cfg["app_secret"], p)

    r = await client.get("https://api.jd.com/routerjson", params=p)
    return jd_unwrap(r.json(), method)


def jd_unwrap(data: dict, method: str) -> dict:
    """
    宙斯的返回体按接口名嵌套一层,且不同网关下 key 的写法不一致
    (点号 / 下划线都见过),做兼容处理。
    这是京东接入手册里最常被问的一句话:"返回怎么多包了一层"
    """
    if "errorResponse" in data:
        err = data["errorResponse"]
        raise PlatformError(err.get("code"), err.get("zh_desc"), "jd")

    for key in (method, method.replace(".", "_")):
        if key in data:
            inner = data[key]
            # 部分接口在剥完第一层后还有个 result 包裹,一并处理
            if isinstance(inner, dict) and "result" in inner:
                return inner["result"]
            return inner

    raise PlatformError("UNWRAP", f"返回体里找不到 {method}", "jd")


# ---------- 1688 ----------

async def alibaba_call(client: httpx.AsyncClient, cfg: dict,
                       url_path: str, api_params: dict) -> dict:
    p = {
        **{k: str(v) for k, v in api_params.items()},
        "_aop_key": cfg["app_key"],
        # 毫秒!用秒的话签名对不上,而且报错不说原因
        "_aop_timestamp": str(int(time.time() * 1000)),
    }
    p["_aop_signature"] = sign_alibaba(cfg["app_secret"], url_path, p)

    # url_path 形如 /openapi/param2/1/com.alibaba.trade/alibaba.trade.get.buyerView
    r = await client.get(f"https://gw.open.1688.com{url_path}", params=p)
    data = r.json()

    if not data.get("success", True):
        err = data.get("error_info") or {}
        raise PlatformError(err.get("code"), err.get("desc"), "alibaba")

    result = data.get("result", data)
    # 1688 有一种"软失败":success 是 true,但业务字段是空的。
    # 对空数据做一次必填校验,别把空当成功传下去
    if isinstance(result, dict) and result.get("datas") == []:
        raise PlatformError("EMPTY", "业务字段为空的软失败", "alibaba")
    return result
```

这段代码里有三个细节,是接入期最容易各自踩一遍的:

1. **京东的 `360buy_param_json` 要压缩**。`ensure_ascii=False` + `separators=(",", ":")` 不是洁癖——参数里有中文时,默认的 `ensure_ascii=True` 会把中文转成 `\uXXXX`,签名按转义后的算,服务端按原文验,**永远对不上**。这是京东接入报错里出现频率最高的一条。
2. **淘宝的错误码是 int 不是 str**。`err.get("code")` 拿到的是 `7` 不是 `"7"`,如果你的错误分类表按字符串写,一条都匹配不上。所以 `PlatformError` 里统一 `str(code)`。
3. **1688 的"软失败"要校验**。`success: true` 但 `datas: []` 的情况真实存在(商品下架、权限降级时会出现),直接当成功处理会让下游静默拿到空数据。

---

## 六、能力声明表:把第二节的表变成代码

第二节的表不该只存在于文章里。把它变成数据,业务层就能在启动时校验,而不是等到运行时才发现 1688 没有 SKU 库存:

```python
# capabilities.py
from enum import Enum


class Cap(str, Enum):
    YES = "yes"
    PARTIAL = "partial"
    NO = "no"

# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
MATRIX: dict[str, dict[str, Cap]] = {
    "item.get":        {"taobao": Cap.YES, "jd": Cap.YES, "alibaba": Cap.YES},
    "sku.stock":       {"taobao": Cap.YES, "jd": Cap.PARTIAL, "alibaba": Cap.NO},
    "order.list":      {"taobao": Cap.YES, "jd": Cap.YES, "alibaba": Cap.YES},
    "logistics.trace": {"taobao": Cap.YES, "jd": Cap.PARTIAL, "alibaba": Cap.PARTIAL},
    "push.tmc":        {"taobao": Cap.YES, "jd": Cap.PARTIAL, "alibaba": Cap.NO},
    "sandbox":         {"taobao": Cap.PARTIAL, "jd": Cap.YES, "alibaba": Cap.NO},
}


class NotSupported(Exception):
    pass


def check(platform: str, *needs: str) -> None:
    """启动时调一次。缺什么当场报,别等运行时"""
    for need in needs:
        cap = MATRIX.get(need, {}).get(platform)
        if cap is None:
            raise NotSupported(f"未知能力 {need}")
        if cap is Cap.NO:
            raise NotSupported(f"{platform} 不支持 {need}")
        if cap is Cap.PARTIAL:
            # partial 不算致命,但要写日志,提醒实现的人去看条件
            print(f"[warn] {platform} 对 {need} 仅部分支持,先确认条件")
```

`check("alibaba", "sku.stock")` 会在启动那一刻就报 `alibaba 不支持 sku.stock`——比三个星期后客户投诉"库存显示不对"强得多。这套东西在第 8 篇展开过架构,这里给的是它的**数据来源**。

---

## 七、坑点清单(每平台五条,按踩的人次排序)

这部分建议直接转成团队的接入 checklist。

### 淘宝 TOP

| # | 坑                     | 症状             | 解法                     |
| - | --------------------- | -------------- | ---------------------- |
| 1 | 沙箱与线上不同步              | 沙箱全绿,上线全挂      | 冒烟打线上固定样本,沙箱只验签名       |
| 2 | `num_iid` 是长数字        | 前端/JS 侧 ID 丢精度 | 全链路按字符串传,落库也用 TEXT     |
| 3 | 分页 `total_results` 不准 | 大类目翻页翻到一半开始重复  | 超 1 万条改用**时间窗切片**,不靠翻页 |
| 4 | TMC 消息重复 + 乱序         | 库存被旧消息覆盖       | 消息带版本号,旧的丢弃(幂等)        |
| 5 | 错误码是 int              | 错误分类表匹配不上      | 统一 `str(code)` 再分类     |

其中第 3 条值得展开一句:`page_no` 翻到几百页之后,`total_results` 会因为数据在变而漂移,翻页翻出重复或漏。**大结果的正确姿势永远是"时间窗 + 游标"**,这和第 3 篇讲订单增量的思路是同一个。

### 京东宙斯


| # | 坑 | 症状 | 解法 |
| --- | --- | --- | --- |
| 1 | `360buy_param_json` 中文转义 | 签名永远对不上 | `ensure_ascii=False` + 压缩分隔符 |
| 2 | 返回体按接口名嵌套 | "数据怎么多包了一层" | `jd_unwrap` 统一剥壳,兼容点号/下划线 |
| 3 | 字段大小写敏感 | 参数传了但没生效 | 文档里 snake_case 是给人看的,代码用 camelCase |
| 4 | 部分接口只支持按天 | 想查小时级查不了 | 按天拉 + 本地按秒过滤,别硬试 |
| 5 | 回调要 HTTPS + 备案 | 测试环境收不到推送 | 提前准备域名,这个等不起 |

### 1688

| # | 坑 | 症状 | 解法 |
| --- | --- | --- | --- |
| 1 | 签名带 urlPath + 毫秒时间戳 | 签名永远对不上,报错不说原因 | 用 `sign_alibaba`,别套淘宝的写法 |
| 2 | 没有 SKU 库存 | 拿到的"库存"其实是别的 | 按"可售状态 + 阶梯价"建模,别硬凑 |
| 3 | 图片防盗链 | 铺到自己页面图挂一半 | 转存走图片网关,或处理 Referer |
| 4 | startIndex 有上限 | 大供应商商品翻不全 | 改时间游标增量拉 |
| 5 | 软失败(success=true 但空数据) | 下游静默拿到空 | 对关键字段做非空校验 |

三张表放一起看,会发现一个规律:**淘宝的坑在"数据形态",京东的坑在"格式规范",1688 的坑在"业务模型"**。预判一个团队接哪个平台会卡多久,看他们擅长处理哪种问题就行。

---

## 八、选型:什么情况用谁

最后把三家的适用场景说透,免得这张表看着热闹,落不了地:

- **做淘系卖家工具、全域比价、竞品监控** → 淘宝 TOP 绕不开,沙箱的坑认了
- **给京东 POP 商家做上货、订单处理** → 京东宙斯,类目 schema 完整是最实在的优势
- **做分销、一件代发、选品比价** → 1688,且**从第一天就按"无 SKU 库存"设计数据模型**
- **三平台都要** → 别做三套代码。第 8 篇那套统一接口层 + 这篇的能力声明表,就是为这个场景准备的

以及一个提醒:三家的审核周期加起来比你的开发周期长。**第一天就把三个平台的权限申请提交了,再开始写代码**——这是第 6 篇 Day 0 清单里第一条的原因,这篇算是把 Day 0 的细节补全了。

---

## 九、最后

这篇的代码量是十篇里最小的,但查表密度最高。因为它回答的问题不是"怎么做",是"**差异在哪**"——而差异是接入期所有工时超支的根源。

第 6 篇里那句"签名预估 0.5 天实际 1 天",多出来的半天就在第四节;"错误处理预估 0.5 天实际 1 天",多出来的半天就是第五节那些剥壳和软失败。**把差异提前写下来,排期才敢往下压。**

我们是做这三平台数据接口服务的,这篇文章里的坑就是我们每天在替客户踩的。代码都是能直接跑的骨架,`signers.py` 和 `clients.py` 拿去改改配置就能用;`capabilities.py` 的矩阵按你们的接口清单补全。

下一篇打算写「多店铺授权管理」:token 的存储、刷新、并发刷新撞车、单店授权过期怎么隔离——那是这套接入体系的最后一个坑区。