×

《京东JOS API:落地方案、接口边界与业务踩坑 —— 联盟免费 vs 商家按量的双轨制》(附python源码)

万邦科技Lex 万邦科技Lex 发表于2026-10-13 09:16:31 浏览31 评论0

抢沙发发表评论

下面按「双轨制定位 → 接口边界 → 落地架构 → 常见踩坑 → Python 双 Client 源码」给你一套可直接抄的工程化方案。
结论先拍:
京东联盟 API(jd.union.open.)= 选品/CPS/比价,调用本身接近免费,赚钱靠成交抽佣*
京东商家 JOS(jingdong. / jd.)= 店铺订单/真实库存/发货/电子面单,有日免额,超量按量计费**
两套 Key、两套授权、两套配额,绝不能混用。

一、双轨制全景对照

维度
京东联盟 API
京东商家 JOS
前缀
jd.union.open.*
jingdong.* / jd.pop.order.* / jd.order.*
目的
选品、券价、佣金、CPS 推广
ERP、订单同步、真实库存、发货回写
资质
联盟实名(可不绑店铺)
企业认证 + 卖家 OAuth AccessToken
库存
有货 / 无货 / 预售
可售库存、锁定库存、区域仓存
价格
促销价、券后价、佣金率
商家后台基准价、协议价
费用
调用免费,成交抽佣 5%~20%
日免额内免费,超量云内 ¥0.01~0.05/百次,云外 ×3~×10
电子面单
无
按单计费(几分/单)
典型 QPS
5~30/s(可申)
个人 1~2/s,企业 5~10/s
适合
导购站、比价、铺货、达人系统
商家自建 ERP / WMS / 打单系统
⚠️ 最贵误区:
用联盟 API 拿“自己店铺真实库存”→ 拿不到;
用商家 JOS 批量爬竞品比价 → 3000 次/天或企业免额瞬间烧完,云外再放大 10 倍账单。

二、接口边界(什么时候用哪套)

✅ 用「联盟 API」

  • 商品搜索:jd.union.open.goods.query

  • 商品详情:jd.union.open.goods.detail.query

  • 推广链接:jd.union.open.goods.promotiongoodsinfo.query

  • 券后价 / 佣金率 / 类目 CPS

  • 跨店选品、内容电商、返利机器人

✅ 用「商家 JOS」

  • 商品主数据:jingdong.ware.read.get

  • SKU:jingdong.ware.sku.read

  • 真实库存:jingdong.stock.get / ware.stock.get

  • 店铺订单:jingdong.pop.order.search / jd.order.detail.get

  • 发货回写 / 物流轨迹:jingdong.etms.trace.get

  • 电子面单:jingdong.etms.waybill.get

❌ 禁止用法

  • 联盟 Key 查自己店铺订单 → 无权

  • 商家 Key 爬全站竞品 → 超量 + 合规风险

  • 把联盟调用量“无限轮询”思维套到商家 Key → 月烧几千块


三、落地架构(生产可用)

┌──────────────┐
 定时任务/Webhook│              │
 ───────────────▶│  API Router  │
                 └──────┬───────┘
                        │
        ┌───────────────┴───────────────┐
        ▼                                ▼
 UnionClient                      JosMerchantClient
(联盟:选品/比价/推广)          (商家:订单/库存/发货)
        │                                │
        ▼                                ▼
 Quota: 只记调用量                  Quota: 日额度 + 超量预警
 Billing: CPS 佣金                   Billing: 按量 + 电子面单
工程原则:
  1. Key 不混用:配置层就隔离

  2. 商家接口走令牌桶:QPS 保守(企业 8/s,个人 1/s)

  3. 订单/库存优先消息推送,别轮询

  4. 商家调用打 meter:日调用量、接口级、店铺级

  5. 联盟接口可高频,但别用来“补商家数据”


四、业务踩坑清单(很值钱)

  1. 时间戳用秒不是毫秒
    JOS 要 int(time.time()),用毫秒 → Invalid Timestamp

  2. 签名必须 ASCII 升序 + 空值剔除 + 紧凑 JSON
    数组/JSON 字段别用 json.dumps(obj) 默认空格

  3. 公开商品接口 ≠ 自己店铺数据
    jingdong.ware.read.get 不传 token 也能查公开字段
    但库存 / 商家编码 / 区域仓 → 必须卖家 AccessToken

  4. 订单类接口要“企业应用 + 接口权限申请 + token”
    个人应用直接 403 / no permission

  5. 日免额耗尽返回类似
    accesscontrol.limited-by-app-access-count
    不是代码错,是没钱了

  6. 电子面单“取号即计费”
    取消未揽收通常不扣,但取号动作本身计费

  7. 沙箱只验签名
    沙箱返 mock 商品/空订单是正常的,别调试到怀疑人生

  8. 云外调用贵到离谱
    商家系统尽量放京东云/聚石塔体系内


五、Python 双 Client 源码(可直接用)

# jd_dual_api_client.py
"""
京东双通道 API Client
1. UnionClient       -> 京东联盟 API (jd.union.open.*)
2. JosMerchantClient -> 京东商家 JOS  (jingdong.* / jd.*)
网关统一: https://api.jd.com/routerjson
"""

import hashlib
import json
import time
import requests
from typing import Any, Dict


class BaseJDClient:
    GATEWAY = "https://api.jd.com/routerjson"

    def __init__(self, app_key: str, app_secret: str):
        self.app_key = app_key
        self.app_secret = app_secret

    def _build_sign(self, params: Dict[str, Any]) -> str:
        """
        MD5 签名:
        - 剔除 sign
        - 空字符串可保留(京东一般要求非空,但实现上建议剔除 None)
        - key 按 ASCII 升序
        - secret + kv + secret
        """
        filtered = {
            k: v for k, v in params.items()
            if k != "sign" and v is not None and v != ""
        }
        raw = self.app_secret
        for k in sorted(filtered.keys()):
            v = filtered[k]
            if isinstance(v, (dict, list)):
                v = json.dumps(v, ensure_ascii=False, separators=(",", ":"))
            raw += f"{k}{v}"
        raw += self.app_secret
        return hashlib.md5(raw.encode("utf-8")).hexdigest().upper()

    def _request(self, method: str, extra: Dict[str, Any], token: str = None) -> dict:
        params = {
            "app_key": self.app_key,
            "method": method,
            "timestamp": str(int(time.time())),
            "format": "json",
            "v": "1.0",
            "sign_method": "md5",
        }
        if token:
            params["access_token"] = token

        # 业务参数
        for k, v in extra.items():
            if isinstance(v, (dict, list)):
                v = json.dumps(v, ensure_ascii=False, separators=(",", ":"))
            params[k] = v

        params["sign"] = self._build_sign(params)

        resp = requests.post(self.GATEWAY, data=params, timeout=10)
        resp.raise_for_status()
        return resp.json()


class UnionClient(BaseJDClient):
    """
    京东联盟 API
    场景:选品 / 比价 / 券后价 / 佣金 / CPS
    """

    def goods_query(self, keyword: str, page_index: int = 1, page_size: int = 20):
        biz = {
            "goodsReqDTO": {
                "keyword": keyword,
                "pageIndex": page_index,
                "pageSize": page_size,
            }
        }
        return self._request(
            method="jd.union.open.goods.query",
            extra={"360buy_param_json": biz},
        )

    def goods_detail(self, sku_ids: list):
        biz = {
            "skuIds": sku_ids
        }
        return self._request(
            method="jd.union.open.goods.detail.query",
            extra={"360buy_param_json": biz},
        )

# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
class JosMerchantClient(BaseJDClient):
    """
    京东商家 JOS
    场景:商品主数据 / 真实库存 / 店铺订单 / 发货
    """

    def ware_read(self, ware_id: int, access_token: str):
        biz = {
            "wareId": ware_id
        }
        return self._request(
            method="jingdong.ware.read.get",
            extra={"360buy_param_json": biz},
            token=access_token,
        )

    def stock_get(self, sku_id: int, access_token: str):
        biz = {
            "skuId": sku_id
        }
        return self._request(
            method="jingdong.stock.get",
            extra={"360buy_param_json": biz},
            token=access_token,
        )

    def pop_order_search(self, start_date: str, end_date: str, page: int, access_token: str):
        biz = {
            "orderReq": {
                "startDate": start_date,
                "endDate": end_date,
                "page": page,
                "pageSize": 100,
            }
        }
        return self._request(
            method="jingdong.pop.order.search",
            extra={"360buy_param_json": biz},
            token=access_token,
        )


if __name__ == "__main__":
    union = UnionClient("UNION_APP_KEY", "UNION_APP_SECRET")
    print(union.goods_query("机械键盘"))

    merchant = JosMerchantClient("JOS_APP_KEY", "JOS_APP_SECRET")
    print(merchant.ware_read(1000123, "SELLER_ACCESS_TOKEN"))

六、配额护栏(生产必加)

商家侧一定要加:
from collections import defaultdict
from datetime import date

daily_counter = defaultdict(int)

def count_and_guard(app_name: str, cost: int, daily_limit: int):
    today = str(date.today())
    key = f"{app_name}:{today}"
    daily_counter[key] += cost
    if daily_counter[key] > daily_limit:
        raise RuntimeError(f"{app_name} 日调用量即将超免额:{daily_counter[key]}/{daily_limit}")
规则建议:
  • 联盟:只统计,不扣费

  • 商家:日调用 > 80% 免额 → 告警

  • 订单列表:能用 Webhook/消息就不翻页

  • 库存:本地缓存 30~60s,别每秒查


七、一句话选型

**“看别人商品”用联盟,“管自己店铺”用 JOS;
联盟烧的是佣金,JOS 烧的是调用次数和面单。”**
如果你愿意,我可以把这套再扩成:
  • ✅ 联盟推广链生成 + 佣金回执校验

  • ✅ 商家订单增量同步(cursor / 消息 / 防超卖)

  • ✅ 京东 + 淘宝 + 拼多多 三平台 API 统一抽象层

你更想先要哪一个?


群贤毕至

访客