×

《京东联盟API月100万次免费+商家3000次/天:两套额度别搞混》(附python源码)

万邦科技Lex 万邦科技Lex 发表于2026-07-21 09:28:26 浏览19 评论0

抢沙发发表评论

京东开放平台里“京东联盟API”“商家JOS接口”是完全两套独立的体系,很多人容易把它们的额度、权限和网关搞混,导致调用超限或拿不到想要的数据。核心区别一句话先给:联盟API走选品推广路线(免店铺授权、量大但库存只有状态),商家JOS走ERP履约路线(需店铺授权、量小但拿真实库存)

一、两套额度与定位全景对照

维度
京东联盟API (jd.union.open.*)
京东商家JOS (jingdong.ware/read/get 等)
核心用途
选品比价、CPS推广、价格/佣金监控
ERP订单同步、真实库存管理、发货回写、区域仓存
授权要求
联盟实名应用(无需店铺授权
企业认证 + 卖家OAuth2 AccessToken
免费额度
备案后通常 月100万次 级别(日约数万~十万)
企业应用通常 3000次/天 左右基础免费(订单/库存类更低,超量按¥0.02~0.10/百次)
默认QPS
5~10/s(可申请提)
个人1~2/s,企业5~10/s(需买包提频)
库存数据
仅返回 有货/无货/预售状态,无具体stockNum
返回 真实可售库存、锁定库存、在途库存
价格数据
促销价、券后价、佣金比例
商家后台基准价、协议价
网关
同JOS网关 https://api.jd.com/routerjson(签名逻辑一致)
同上(但access_token类型不同)
⚠️ 致命误区:用联盟API去拿自己店铺的真实库存数 → 拿不到;用商家JOS去批量爬竞品比价 → 额度瞬间耗尽且涉嫌越权。

二、Python源码:双通道Client区分调用(防混用)

下面给你一个统一网关、分离Client的示例,明确区分联盟应用与商家应用的调用逻辑,避免把3000次/天的商家额度浪费在选品上。
# jd_dual_api_client.py
"""
京东双通道API Client
1. UnionClient  -> 京东联盟API (jd.union.open.*) 月百万次级,免店铺授权
2. JosMerchantClient -> 京东商家JOS (jingdong.ware.read.get等) 日3000次级,需卖家Token
共用:MD5签名、秒级timestamp、routerjson网关
"""
import hashlib
import json
import time
import requests
from typing import Dict, List

封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
class _BaseJdClient:
    GW = "https://api.jd.com/routerjson"

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

    def _sign(self, params: Dict) -> str:
        """JOS通用MD5签名:AppSecret + 排序KV + AppSecret -> 大写"""
        filtered = 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 filtered)
        raw = f"{self.ask}{qs}{self.ask}"
        return hashlib.md5(raw.encode("utf-8")).hexdigest().upper()

    def _post(self, method: str, biz: Dict, token: str = None) -> Dict:
        params = {
            "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:
            params["access_token"] = token
        params["sign"] = self._sign(params)

        r = requests.post(self.GW, data=params, 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[{err.get('code')}]: {err.get('zh_desc') or err.get('en_desc')}")
        return data


## ================= 联盟通道(免店铺授权) =================
class JdUnionClient(_BaseJdClient):
    """
    适用:jd.union.open.goods.detail.query / goods.query / promotion.goodsByPid
    额度:月100万次级免费,QPS~10
    数据:有货/无货状态、券后价、佣金
    """

    def get_goods_detail(self, sku_ids: List[int], fields: str = None) -> Dict:
        fields = fields or (
            "skuId,productName,price,promotionPrice,couponInfo,"
            "stockState,commissionInfo,salesCount,shopName"
        )
        biz = {"skuIds": sku_ids, "fields": fields}
        return self._post("jd.union.open.goods.detail.query", biz)

    def parse_stock_status(self, item: Dict) -> str:
        """联盟仅返回库存状态:1有货 0无货"""
        st = item.get("stockState", item.get("skuList", [{}])[0].get("stock"))
        if st == 1 or str(st) == "1":
            return "有货(状态值1)"
        return "无货/未知(状态值0)"


## ================= 商家JOS通道(需卖家Token) =================
class JosMerchantClient(_BaseJdClient):
    """
    适用:jingdong.ware.read.get / jingdong.stock.get / pop.order.search
    额度:企业约3000次/天基础免费(订单类更低),超量计费
    数据:真实stockNum、订单明细、区域库存
    """

    def get_ware_with_real_stock(self, ware_id: str, seller_token: str) -> Dict:
        """查自己店铺商品,必须传卖家AccessToken"""
        fields = (
            "ware_id,title,jd_price,stock_num,"
            "skus_json,outer_id,approve_status"
        )
        biz = {"wareId": ware_id, "fields": fields}
        return self._post("jingdong.ware.read.get", biz, token=seller_token).get("ware", {})

    def get_orders_incremental(self, seller_token: str, start: str, end: str, page: int = 1) -> Dict:
        """订单增量拉取(消耗商家额度,慎用)"""
        return self._post("jingdong.pop.order.search", {
            "start_modified": start,
            "end_modified": end,
            "order_state": "WAIT_SELLER_STOCK_OUT,FINISHED",
            "page": page,
            "page_size": 50
        }, token=seller_token)


# =========================================================
# 使用示例:明确分流,别把商家额度用在联盟场景上
# =========================================================
if __name__ == "__main__":
    # ---- 联盟应用(无店铺也能跑)----
    union_cli = JdUnionClient("UNION_APP_KEY", "UNION_APP_SECRET")
    try:
        detail = union_cli.get_goods_detail([100012345678])
        data_list = detail.get("data", detail.get("jd_union_open_goods_detail_query_response", {}).get("data", []))
        if isinstance(data_list, dict):
            data_list = data_list.get("data") or []
        for it in (data_list or []):
            print(f"[联盟] SKU:{it.get('skuId')} 价:{it.get('promotionPrice')} "
                  f"库存:{union_cli.parse_stock_status(it)}")
    except Exception as e:
        print("联盟API异常:", e)

    # ---- 商家应用(必须卖家Token)----
    merchant_cli = JosMerchantClient("MERCHANT_APP_KEY", "MERCHANT_APP_SECRET")
    SELLER_TOKEN = "YOUR_SELLER_ACCESS_TOKEN"
    try:
        ware = merchant_cli.get_ware_with_real_stock("100012345678", SELLER_TOKEN)
        print(f"[商家] 标题:{ware.get('title')} 真实库存:{ware.get('stock_num')}")
    except Exception as e:
        print("商家JOS异常(可能额度耗尽/无权限):", e)

三、避坑与选型建议

  • 额度隔离使用:选品、价格监控、竞品爬取 无脑用联盟API(月100万次免费,不占商家额度);订单同步、真实库存、发货回写必须用商家JOS(日3000次精打细算,超了按量扣费)。

  • 库存认知差:联盟返回的 stockState=1 只是“前端显示有货”,可能是区域有货或虚拟库存;商家JOS的 stockNum 才是可售实物库存,做ERP防超卖只能信后者。

  • Token别混传:联盟接口不传店铺seller_access_token(传了也没额外数据);商家接口必须传,且Token过期(通常24h)要用RefreshToken刷新,否则直接403。

  • QPS防护:联盟虽然额度大但默认QPS约10,批量跑时加令牌桶;商家接口免费QPS极低(2~5),高频调用前务必确认是否已购QPS资源包,否则频繁429会导致订单漏拉。

一句话记死:联盟管“看”(免费大方、浅数据),商家管“干”(额度抠门、深数据),两套Key两套逻辑,千万别串线。
要不要我帮你在这个双通道Client基础上,补一个每日调用计数器(分别统计联盟/商家额度消耗),并在接近商家3000次上限时自动熔断非核心同步任务的脚本?


群贤毕至

访客