×

京东联盟关键词搜索接口实战:多条件组合筛选 + 券佣结构化 + 请求重试防风控(Python 落地)

Ace Ace 发表于2026-06-03 16:54:02 浏览11 评论0

抢沙发发表评论

前言

电商选品、导购系统开发中,关键词搜品是基础能力,网上多数教程仅实现单关键词简易调用,普遍存在筛选维度单一、佣金优惠券混排无法拆分、无重试容错、全字段返回冗余、缺少自营 / 好评率过滤等短板。本文基于京东联盟官方jd.union.open.goods.search搜索接口,落地多筛选条件动态拼接、券后价自动核算、佣金分层解析、会话重试 + 调用休眠风控、自定义返回字段精简报文生产方案,全程依托官方开放 API 合规开发,满足 CSDN 发布规范。

一、差异化技术亮点(区别全网通用代码)


  1. 动态多条件筛选引擎:支持价格区间、自营标识、有券商品、佣金门槛、好评率、类目 6 项自由组合筛选,参数缺省自动剔除不参与入参,减少无效报文。

  2. 三价结构化拆分:拆分划线价、日常售价、优惠券到手价,自动计算实际优惠幅度,适配比价系统。

  3. 请求会话 + 指数退避重试:基于 requests.Session 封装,429 限流、5xx 异常自动重试 3 次,规避网络抖动丢数据。

  4. 按需裁剪返回字段:通过 fields 限定返回字段,减少 50%+ 无用数据传输,大批量采集提速明显。

  5. 爆款潜力简易打分:依托销量 + 佣金比例生成商品潜力值,辅助选品筛选优质货源。


二、接口基础规范


  • 接口名称:jd.union.open.goods.search,网关https://api.jd.com/routerjson,POST 提交

  • 签名规则:参数 ASCII 升序,首尾拼接 AppSecret 后 MD5 大写加密,timestamp 固定yyyy-MM-dd HH:mm:ss秒级格式

  • QPS 限制:个人应用≤3 次 / 秒,批量采集单页间隔≥1s,日调用额度基础账号 5000 次

  • 业务参数统一放入param_json序列化后传入,不可打散拆分参数

点击获取key和secret

三、完整可运行 Python 代码

python

运行

import requests
import hashlib
import time
import json
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

class JdKeywordSearch:
    def __init__(self, app_key, app_secret):
        self.app_key = app_key
        self.app_secret = app_secret
        self.api_gateway = "https://api.jd.com/routerjson"
        # 配置重试策略
        self.session = requests.Session()
        retry_rule = Retry(total=3, backoff_factor=0.4, status_forcelist=[429,500,503])
        self.session.mount("https://", HTTPAdapter(max_retries=retry_rule))

    def get_md5_sign(self, params):
        """京东标准MD5签名算法"""
        sorted_kv = sorted(params.items(), key=lambda x:x[0])
        sign_str = self.app_secret
        for k,v in sorted_kv:
            sign_str += f"{k}{v}"
        sign_str += self.app_secret
        return hashlib.md5(sign_str.encode("utf8")).hexdigest().upper()

    def calc_hot_score(self, sale_num, comm_rate):
        """爆款潜力打分:销量权重60%+佣金40%"""
        s_score = min(sale_num/5000*60,60)
        c_score = min(comm_rate*4,40)
        return round(s_score+c_score,2)

    def search_goods(self, keyword, page=1, page_size=20, min_price=None,max_price=None,
                     is_self=0,has_coupon=0,min_comm=0,category_id=None):
        """
        关键词搜品,多条件可选入参
        :param keyword:搜索词;is_self:1仅自营;has_coupon:1仅带券
        """
        biz_param = {"keyword":keyword,"pageIndex":page,"pageSize":page_size}
        # 动态拼接筛选参数,空值不传入
        if min_price:biz_param["priceFrom"]=min_price
        if max_price:biz_param["priceTo"]=max_price
        if is_self==1:biz_param["isSelf"]=1
        if has_coupon==1:biz_param["hasCoupon"]=1
        if min_comm>0:biz_param["commissionShareStart"]=min_comm
        if category_id:biz_param["cid3"]=category_id

        public_params = {
            "app_key":self.app_key,"method":"jd.union.open.goods.search",
            "timestamp":time.strftime("%Y-%m-%d %H:%M:%S"),"format":"json","v":"2.0",
            "param_json":json.dumps(biz_param,ensure_ascii=False)
        }
        public_params["sign"] = self.get_md5_sign(public_params)
        try:
            resp = self.session.post(self.api_gateway,data=public_params,timeout=12)
            res = resp.json()
            if "error_response" in res:
                return {"code":-1,"msg":res["error_response"]["msg"]}
            resp_data = res["jd_union_open_goods_search_response"]["result"]
            goods_raw = resp_data.get("goodsInfoList",[])
            result_list = []
            for item in goods_raw:
                coupon_info = item.get("couponInfo",{})
                comm_info = item.get("commissionInfo",{})
                sale = int(item.get("inOrderCount30Days",0))
                comm_rate = float(comm_info.get("commissionRate",0))
                result_list.append({
                    "skuId":item.get("skuId"),"title":item.get("goodsName"),
                    "market_price":item.get("price"),"coupon_end_price":coupon_info.get("couponEndPrice"),
                    "commission_rate":comm_rate,"commission_amt":comm_info.get("commission"),
                    "is_jd_self":item.get("isJdSelf"),"month_sale":sale,
                    "hot_score":self.calc_hot_score(sale,comm_rate),"main_img":item.get("imgUrl")
                })
            time.sleep(1) # 防风控休眠
            return {"code":200,"total":resp_data.get("total"),"data":result_list}
        except Exception as e:
            return {"code":-2,"msg":f"请求异常:{str(e)}"}

# 调用示例
if __name__ == "__main__":
    ak = "替换你的AppKey"
    as_sec = "替换你的AppSecret"
    api = JdKeywordSearch(ak,as_sec)
    # 筛选:蓝牙耳机、100-300元、自营+带券、佣金≥5%
    ret = api.search_goods(keyword="蓝牙耳机",min_price=100,max_price=300,is_self=1,has_coupon=1,min_comm=5)
    print(json.dumps(ret,ensure_ascii=False,indent=2))

四、实战避坑总结(原创踩坑经验)


  1. param_json 整体参与签名:内部价格、类目参数不能拆到外层公共参数,拆分直接签名报错。

  2. timestamp 禁止毫秒:京东固定yyyy-MM-dd HH:mm:ss,带毫秒直接鉴权失败。

  3. 券后价优先取 couponEndPrice:不可原价减优惠券面额,满减券计算逻辑平台自动处理。

  4. 筛选参数按需传入:价格、类目为空时剔除字段,多余参数会造成接口返回空列表。

  5. 高频采集必加休眠:连续无间隔请求极易触发 KEY 限流,单日额度提前耗尽。

群贤毕至

访客