下面按「双轨制定位 → 接口边界 → 落地架构 → 常见踩坑 → 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: 按量 + 电子面单
工程原则:
- Key 不混用:配置层就隔离
- 商家接口走令牌桶:QPS 保守(企业 8/s,个人 1/s)
- 订单/库存优先消息推送,别轮询
- 商家调用打 meter:日调用量、接口级、店铺级
- 联盟接口可高频,但别用来"补商家数据"
四、业务踩坑清单(很值钱)
- 时间戳用秒不是毫秒
JOS 要int(time.time()),用毫秒 →Invalid Timestamp - 签名必须 ASCII 升序 + 空值剔除 + 紧凑 JSON
数组/JSON 字段别用json.dumps(obj)默认空格 - 公开商品接口 ≠ 自己店铺数据
jingdong.ware.read.get不传 token 也能查公开字段
但库存 / 商家编码 / 区域仓 → 必须卖家 AccessToken - 订单类接口要"企业应用 + 接口权限申请 + token"
个人应用直接 403 / no permission - 日免额耗尽返回类似
accesscontrol.limited-by-app-access-count
不是代码错,是没钱了 - 电子面单"取号即计费"
取消未揽收通常不扣,但取号动作本身计费 - 沙箱只验签名
沙箱返 mock 商品/空订单是正常的,别调试到怀疑人生 - 云外调用贵到离谱
商家系统尽量放京东云/聚石塔体系内
五、Python 双 Client 源码(可直接用)
python
# 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
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
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},
)
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"))
六、配额护栏(生产必加)
商家侧一定要加:
python
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 统一抽象层
你更想先要哪一个?