🔄《京东JOS宙斯迁移实录:jingdong.ware.get → jingdong.ware.read.get 平滑切(2026版)》(附Python源码)
京东宙斯(JOS)在2024~2026年间持续推进接口标准化,老接口
jingdong.ware.get(及别名360buy.ware.get)已进入维护期,新应用不再支持申请,官方推荐统一迁移至jingdong.ware.read.get(只读商品读取接口,权限更清晰、支持SKU快照字段)。迁移只需改method + 微调fields,网关/签名/AccessToken完全不变,下面给你一份可直接执行的平滑切换实录。
一、老 vs 新接口对照(一目了然)
维度 | 老接口(即将下线) | 新接口(推荐) |
|---|---|---|
Method | jingdong.ware.get / 360buy.ware.get | ✅ jingdong.ware.read.get |
网关 | https://api.jd.com/routerjson | 完全相同 |
签名算法 | MD5(AppSecret+KV_ASCII+AppSecret) 秒级timestamp | 完全相同(一行不改) |
必传参数 | wareId, fields | 完全相同( wareId, fields) |
返回根节点 | jingdong_ware_get_response.ware | jingdong_ware_read_get_response.ware |
SKU库存可见 | 需传seller token | 相同(传seller token可看 stockNum in skus_json) |
权限申请 | 老入口(已关闭新申请) | 应用→接口权限→申请 jingdong.ware.read.get(即时生效) |
✅ 迁移量:代码中只改 method 字符串 + fields 补全skus_json,stock_num,其余(Client/签名/网关/token)一行不动。
二、Python:老接口 vs 新接口调用对比 + 兼容封装
# jd_ware_migration_2026.py
"""
京东JOS宙斯迁移实录
old: jingdong.ware.get → new: jingdong.ware.read.get
同一个 JdClient 复用,仅 method + fields 不同
支持 沙箱/生产 切换
"""
import hashlib
import json
import requests
import time
from typing import Dict, Optional
class JdWareClient:
"""京东JOS商品接口客户端(兼容新旧两套)"""
GW_PROD = "https://api.jd.com/routerjson"
GW_SANDBOX = "https://api.sandbox.jd.com/routerjson"
def __init__(self, app_key: str, app_secret: str, sandbox: bool = False):
self.ak = app_key
self.ask = app_secret
self.gw = self.GW_SANDBOX if sandbox else self.GW_PROD
# ───── JOS MD5签名(秒级timestamp,紧凑JSON)─────
def _sign(self, params: Dict) -> str:
filt = sorted(
(k, v) for k, v in params.items()
if k != "sign" and v is not None and str(v).strip() != ""
)
qs = "".join(f"{k}{v}" for k, v in filt)
raw = f"{self.ask}{qs}{self.ask}"
return hashlib.md5(raw.encode("utf-8")).hexdigest().upper()
def _call(self, method: str, biz: Dict, token: str = None) -> Dict:
p = {
"app_key": self.ak,
"method": method,
"timestamp": str(int(time.time())), # ← 秒级!不是毫秒
"format": "json",
"v": "2.0",
"sign_method": "md5",
"360buy_param_json": json.dumps(biz, ensure_ascii=False,
separators=(',', ':'))
}
if token:
p["access_token"] = token
p["sign"] = self._sign(p)
r = requests.post(self.gw, data=p, timeout=15)
r.raise_for_status()
d = r.json()
# 提取响应(兼容新旧method对应的resp_key)
resp_key = method.replace(".", "_") + "_response"
if resp_key not in d:
for k in d:
if k.endswith("_response"):
resp_key = k
break
data = d.get(resp_key, d)
if "error_response" in str(data):
err = d.get(resp_key, {}).get("error_response") or d.get("error_response")
if err:
code = str(err.get("code", ""))
zh = err.get("zh_desc") or err.get("en_desc")
sub = err.get("sub_code", "")
if "no permission" in zh:
raise PermissionError(
f"❌ 无权限:确认已申请 {method} 接口,订单类需传卖家AccessToken\n原始: {zh}"
)
raise Exception(f"JOS[{code}]: {zh} sub={sub}")
raise Exception(f"JOS error: {d}")
return data
# ─── 老接口(不推荐,仅作对比/过渡期兼容)───
def get_ware_old(self, ware_id: str,
fields: str = None, token: str = None) -> Dict:
fields = fields or (
"ware_id,title,price,jd_price,stock_num,skus_json,image_path"
)
return self._call(
"jingdong.ware.get", # ← 老 method
{"wareId": ware_id, "fields": fields},
token
).get("ware", {})
# ─── ✅ 新接口(推荐迁移目标)───
def get_ware_new(self, ware_id: str,
fields: str = None, token: str = None) -> Dict:
fields = fields or (
"ware_id,title,brand_name,cid,category_name,"
"price,jd_price,market_price,stock_num,"
"skus_json,image_path,desc,outer_id,approve_status"
)
return self._call(
"jingdong.ware.read.get", # ← 新 method
{"wareId": ware_id, "fields": fields},
token
).get("ware", {})
# =========================================================
# 使用示例:平滑切换
# =========================================================
if __name__ == "__main__":
client = JdWareClient(
app_key="YOUR_JD_APP_KEY",
app_secret="YOUR_JD_APP_SECRET",
sandbox=False # 沙箱测试 = True
)
WARE_ID = "100012345678" # ← 替换真实 wareId
SELLER_TOKEN = None # 查自己店铺SKU库存需传卖家AccessToken
try:
# ★ 迁移后只用这一行 ★
ware = client.get_ware_new(WARE_ID, token=SELLER_TOKEN)
print("✅ [新接口] 商品标题:", ware.get("title"))
print(" 京东价:", ware.get("jd_price"))
print(" SPU库存:", ware.get("stock_num"))
# skus_json 解析(查自己店铺时才有真实库存)
import json as _j
skus_raw = ware.get("skus_json") or ""
if skus_raw:
skus = _j.loads(skus_raw)
print(f" SKU数: {len(skus)}")
for s in skus[:3]:
print(f" skuId:{s.get('skuId')} 价:{s.get('jdPrice')} "
f"库:{s.get('stockNum')} 商家码:{s.get('outerId','')}")
print("\n→ 迁移完成!仅需改 method='jingdong.ware.read.get' + fields补skus_json")
except PermissionError as pe:
print(pe)
except Exception as e:
print("❌", e)
print("→ 确认: AppKey/Secret正确、接口已申请、wareId有效")三、迁移 Checklist(照着做,5分钟切完)
- 应用后台申请新权限→ 接口权限 → 勾选
jingdong.ware.read.get→ 提交(通常即时生效) - 代码改动(极小)
- method = "jingdong.ware.get" + method = "jingdong.ware.read.get" - fields = "ware_id,title,price" + fields = "ware_id,title,price,jd_price,stock_num,skus_json,image_path"
wareId参数名不变(驼峰,同老接口) - 回归测试(5分钟)
- 沙箱调通签名 ✓
- 生产比对新旧返回字段一致性(标题/价格/SKU数应一致)
- 自家店铺查 SKU 库存需传
access_token(同旧逻辑) - 下线老调用确认所有环境切新 method 后,删除
get_ware_old()分支
四、避坑清单
坑 | 现象 | 解决 |
|---|---|---|
新接口返回 403 | 未申请 jingdong.ware.read.get | 应用→接口权限→申请 |
skus_json 空 | 公开查询别人商品 / 未传 token | 查自己店铺商品须传卖家 AccessToken |
老代码 wareId 写成 ware_id | 部分SDK自动映射,推荐用 wareId(首字母大写) | 与文档保持一致 |
返回结构 key 变 | jingdong_ware_get_response → jingdong_ware_read_get_response | 解析用 .get("ware") 不依赖外层 key |
timestamp毫秒 | Invalid Timestamp | JOS用秒级 int(time.time()) |
沙箱返回 mock 数据 | 正常,沙箱只验签不返真数据 | 切生产验证 |
五、面试/方案一句话
京东JOS宙斯迁移:老接口jingdong.ware.get→ 新接口jingdong.ware.read.get,只改 method 字符串 + fields 补skus_json,stock_num,网关/签名/AccessToken 完全不变;应用需申请新接口权限,返回值结构外层key变但内层ware节点字段一致,解析取.get("ware")即兼容,5分钟平滑切换。
需要我补 京东订单增量同步(
jingdong.pop.order.search) 或 库存快照定时校正脚本 的完整迁移示例吗?