🤝 京东联盟API(jd.union.open.goods.detail.query)获取佣金与券信息实战(附Python源码)
京东联盟(Jingdong Union / 京挑客)API 用来按京东SKU/SPU查佣金比例、券面额、券链接、券后价,是个人选品、比价、铺货系统的数据源头——不需要店铺授权,只需要京盟应用 AppKey + AppSecret,备案后免费调用。
一、前置条件
- 注册 京东开放平台→ 创建 京东联盟应用(网站/APP/微信)
- 京盟后台绑定推广位 PID(格式
unionpid___或jd_union_pid,推广位ID在京盟后台获取) - 应用 → 接口权限 → 申请:
jd.union.open.goods.detail.query(商品详情+佣金+券)jd.union.open.goods.query(关键词搜商品,可选)- AppKey / AppSecret 同 JOS 通用
⚠️ 京盟接口不传access_token(公开选品数据),与商家订单接口不同。
二、接口关键信息
项目 | 说明 |
|---|---|
Method | jd.union.open.goods.detail.query |
网关 | https://api.jd.com/routerjson(京盟无独立沙箱,可用生产测) |
必传 | skuIds(最长20个SKU ID)、fields(推荐全传) |
返回 | 券面额、佣金比例(万分之)、券链接、券后价估算、商品图文 |
签名 | 标准 JOS MD5(AppSecret + KV_ASCII + AppSecret,秒级timestamp) |
三、Python完整调用 + 佣金/券解析
# jd_union_goods_detail.py
"""
京东联盟 API - 商品详情(佣金+券)
jingdong.union.open.goods.detail.query
依赖: requests (pip install requests)
"""
import hashlib
import json
import requests
from typing import Dict, List
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
class JdUnionClient:
GW = "https://api.jd.com/routerjson"
def __init__(self, app_key: str, app_secret: str):
self.ak = app_key
self.as_ = app_secret
# ───── JOS MD5签名(秒级timestamp)─────
def _sign(self, params: Dict) -> str:
filt = sorted((k, v) for k, v in params.items()
if v is not None and str(v).strip() != '' and k != 'sign')
qs = ''.join(f"{k}{v}" for k, v in filt)
return hashlib.md5(f"{self.as_}{qs}{self.as_}".encode()
).hexdigest().upper()
def _call(self, method: str, biz: Dict) -> Dict:
api_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=(',', ':'))
}
api_p["sign"] = self._sign(api_p)
r = requests.post(self.GW, data=api_p, timeout=15)
r.raise_for_status()
d = r.json()
# 提取
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:
raise Exception(f"JOS Union Err[{err.get('code')}]: "
f"{err.get('zh_desc') or err.get('en_desc')}")
raise Exception(f"JOS Union error: {d}")
return data
# ───── 商品详情(佣金+券) ─────
def get_goods_detail(self, sku_ids: List[int],
fields: str = None,
pid: str = None) -> List[Dict]:
"""
sku_ids: 京东SKU ID列表(最长20)
pid: 推广位ID(部分字段需传,建议填)
fields: 推荐全传 ↓
"""
fields = fields or (
"skuId,skuName,price,couponList,couponPrice,couponStartTime,"
"couponEndTime,commissionRate,commission,afterPrice,"
"picUrl,detailUrl,materialUrl,categoryInfo"
)
biz = {"skuIds": sku_ids, "fields": fields}
if pid:
biz["pid"] = pid
resp = self._call("jd.union.open.goods.detail.query", biz)
# 标准返回: {jd_union_open_goods_detail_query_response:{data:[...]}}
data_wrapper = resp.get("data") or resp.get("result")
if isinstance(data_wrapper, list):
return data_wrapper
if isinstance(data_wrapper, dict):
return data_wrapper.get("data") or [data_wrapper]
return []
# ───── 扁平化解析 ─────
def parse_one(self, raw: Dict) -> Dict:
"""返回平铺选品关键字段"""
coupon_list = raw.get("couponList") or []
best_coupon = max(coupon_list,
key=lambda c: float(c.get("couponPrice") or 0),
default={})
commission_rate_wan = int(raw.get("commissionRate") or 0) # 万分之
price = float(raw.get("price") or 0)
coupon_amt = float(best_coupon.get("couponPrice") or 0)
est_after = max(price - coupon_amt, 0)
est_commission = round(est_after * commission_rate_wan / 10000, 2)
return {
"sku_id": str(raw.get("skuId")),
"title": raw.get("skuName"),
"price": price,
"coupon_amount": coupon_amt,
"coupon_start": best_coupon.get("couponStartTime"),
"coupon_end": best_coupon.get("couponEndTime"),
"coupon_link": best_coupon.get("link") or best_coupon.get("url"),
"commission_rate_wan": commission_rate_wan,
"commission_rate_pct": commission_rate_wan / 100,
"est_after_coupon": est_after,
"est_commission": est_commission,
"pic_url": raw.get("picUrl"),
"material_url": raw.get("materialUrl"),
"category": raw.get("categoryInfo")
}
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
# =========================================================
# 使用示例
# =========================================================
if __name__ == "__main__":
client = JdUnionClient(
app_key="YOUR_JD_APP_KEY",
app_secret="YOUR_JD_APP_SECRET"
)
PID = "YOUR_UNION_PID" # ← 京盟后台获取的推广位ID(可选但推荐)
SKU_IDS = [100012345678, 100087654321] # ← 替换真实京东SKU ID
try:
items = client.get_goods_detail(SKU_IDS, pid=PID)
for raw in items:
g = client.parse_one(raw)
print(f"\n• {g['title'][:30]}")
print(f" 原价:¥{g['price']} 券¥{g['coupon_amount']} "
f"券后≈¥{g['est_after_coupon']}")
print(f" 佣金率:{g['commission_rate_pct']:.2f}% "
f"预估佣金:¥{g['est_commission']}")
if g['coupon_link']:
print(f" 领券: {g['coupon_link'][:60]}...")
except Exception as e:
print("❌", e)
print("→ 确认: AppKey/Secret正确、京盟应用已备案、skuId有效")四、关键返回字段说明(选品核心)
字段 | 类型 | 含义 |
|---|---|---|
skuId | long | 京东SKU ID(与 ware.read.get中 skuId 一致) |
price | string | 商品原价(元) |
couponList[].couponPrice | string | 券面额(最高面额建议取 max) |
couponList[].link | string | 领券URL(带联盟参数) |
commissionRate | int | 佣金比率(万分之),例 5000 = 5% |
afterPrice | string | 平台计算的券后价(参考) |
picUrl/ materialUrl | string | 主图 / 推广链接 |
categoryInfo | obj | 一二三级类目ID/名称 |
预估佣金公式(与官方一致):
est_commission = (price - coupon_amount) * commissionRate / 10000
五、避坑清单
坑 | 现象 | 解决 |
|---|
|
|
skuIds传字符串 | 无数据/param error| 传 List[int] 或 JSON数组 [1000123]|| 京盟应用未备案 |
no permission| 京盟后台完成备案(身份证/企业信息) || 佣金率=0 | 部分商品不分佣/预售 | 正常现象,选品时注意过滤 |
| timestamp毫秒 |
Invalid Timestamp| JOS用秒级 int(time.time())||
fields不传 | 返回极少字段 | 显式传含 couponList,commissionRate,afterPrice|| pid不传 | 个别推广链接为空 | 建议填京盟推广位PID |
六、面试/方案一句话
京东联盟选品 = 京盟应用备案 →jd.union.open.goods.detail.query(skuIds, fields=couponList+commissionRate+afterPrice)→ 取couponPrice最高券 +commissionRate(万分之)估算佣金,不传 AccessToken,签名同 JOS MD5(秒级时间戳,AppSecret首尾拼KV_ASCII)。
需要我补 京东联盟商品搜索API(
jd.union.open.goods.query) 关键词选品+分页 或 京东商家订单同步(jingdong.pop.order.search) 增量APScheduler脚本 吗?