一、接口体系概览
京东提供两套核心的商品搜索接口,分别服务于不同场景:
| 接口类型 | 核心接口 | 适用场景 | 数据特点 | 权限要求 |
|---|---|---|---|---|
| 京东联盟 | jd.union.open.goods.search | CPS推广、选品、导购 | 含佣金比例、优惠券、推广链接 | 联盟账号+应用授权 |
| 宙斯开放平台 | jingdong.ware.read.searchWare4Valid | 商家自营、店铺管理 | 库存、SKU管理、上下架状态 | 商家资质+店铺授权 |
本文重点讲解京东联盟搜索接口,因其更适合开发者进行商品数据挖掘和推广业务。
二、接入准备
1. 注册与认证流程
访问 京东联盟开放平台 完成以下步骤:
- 注册开发者账号:完成实名认证(个人/企业)
- 创建应用:在"我的应用"中创建新应用,获取 App Key 和 App Secret
- 申请权限:申请
jd.union.open.goods.search接口权限 - 绑定推广位:创建推广位获取 PID(用于跟踪佣金)
- 获取Access Token:通过OAuth2.0流程获取访问令牌
2. 权限分级体系
京东联盟接口采用分级权限,不同等级对应不同的调用频次和数据深度:
| 权限等级 | 认证要求 | QPS限制 | 数据范围 |
|---|---|---|---|
| 基础权限 | 个人实名认证 | 5 | 基础商品信息(标题、价格、图片) |
| 进阶权限 | 企业认证 | 20 | 增加促销信息、评价摘要、店铺评分 |
| 高级权限 | 联盟合作伙伴 | 50 | 搜索热度、趋势数据、用户画像标签 |
三、接口技术规范
1. 基础信息
- 请求地址:
https://api.jd.com/routerjson或https://router.jd.com/api - 请求方式:GET/POST(推荐POST)
- 数据格式:JSON
- 签名算法:MD5(参数排序后拼接加密)
2. 核心请求参数
公共参数(必选)
| 参数名 | 类型 | 说明 | 示例值 |
|---|---|---|---|
method | String | 接口方法名 | jd.union.open.goods.search |
app_key | String | 应用唯一标识 | 1234567890abcdef |
access_token | String | 访问令牌 | xxxxxxxx-xxxx-xxxx |
timestamp | String | 时间戳(yyyy-MM-dd HH:mm:ss) | 2025-04-03 17:30:00 |
v | String | API版本号 | 1.0 或 2.0 |
format | String | 响应格式 | json |
sign_method | String | 签名算法 | md5 |
sign | String | 签名(需生成) | E4F2G3H4I5J6... |
业务参数(搜索核心)
| 参数名 | 类型 | 必选 | 说明 | 示例值 |
|---|---|---|---|---|
keyword | String | 是 | 搜索关键词,支持多词(空格分隔) | 无线蓝牙耳机 主动降噪 |
pageIndex | Integer | 否 | 页码,默认1,最大50 | 1 |
pageSize | Integer | 否 | 每页条数,默认20,最大50 | 30 |
priceFrom | Double | 否 | 价格区间最小值 | 100.00 |
priceTo | Double | 否 | 价格区间最大值 | 500.00 |
sortName | String | 否 | 排序字段:price/sales/commission | sales |
sort | String | 否 | 排序方式:asc/desc | desc |
cid3 | Integer | 否 | 三级分类ID,精准筛选 | 1234 |
hasCoupon | Integer | 否 | 是否有优惠券:0(无)/1(有) | 1 |
isSelf | Integer | 否 | 是否自营:0(否)/1(是) | 1 |
goodRate | Integer | 否 | 最低好评率(0-100) | 95 |
四、签名生成算法(核心难点)
京东API要求对所有请求参数进行签名验证,这是新手最容易出错的环节。
签名生成步骤:
- 参数排序:将所有请求参数(除sign外)按参数名字典序升序排列
- 字符串拼接:按
key1value1key2value2...格式拼接 - 首尾加Secret:在字符串首尾各拼接一次
App Secret - MD5加密:对完整字符串进行MD5加密,结果转大写
Python签名实现代码:
Python
import hashlibimport urllib.parsedef generate_sign(params: dict, app_secret: str) -> str:
"""
生成京东API签名
:param params: 请求参数字典(不含sign)
:param app_secret: 应用密钥
:return: 大写MD5签名
"""
# 1. 过滤空值并排序
sorted_params = sorted(
[(k, v) for k, v in params.items() if v is not None and v != ""],
key=lambda x: x[0]
)
# 2. 拼接字符串(注意:值需要URL编码)
sign_str = app_secret for key, value in sorted_params:
# 对值进行URL编码,确保特殊字符正确处理
encoded_value = urllib.parse.quote(str(value), safe='')
sign_str += f"{key}{encoded_value}"
sign_str += app_secret
# 3. MD5加密并转大写
sign = hashlib.md5(sign_str.encode('utf-8')).hexdigest().upper()
return sign# 使用示例params = {
"method": "jd.union.open.goods.search",
"app_key": "your_app_key",
"access_token": "your_access_token",
"timestamp": "2025-04-03 17:30:00",
"v": "1.0",
"format": "json",
"360buy_param_json": '{"keyword":"手机","pageIndex":1}' # 业务参数需JSON序列化}sign = generate_sign(params, "your_app_secret")print(f"生成的签名: {sign}")五、完整实战代码
以下是一个生产级的京东商品搜索类,包含重试机制、数据清洗和异常处理:
Python
import requestsimport timeimport jsonimport hashlibimport urllib.parseimport loggingfrom typing import Dict, List, Optional, Tuplefrom requests.adapters import HTTPAdapterfrom urllib3.util.retry import Retry# 配置日志logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s')logger = logging.getLogger(__name__)class JDUnionSearchAPI:
"""
京东联盟商品搜索API客户端
"""
def __init__(self, app_key: str, app_secret: str, access_token: str):
self.app_key = app_key
self.app_secret = app_secret
self.access_token = access_token
self.api_url = "https://api.jd.com/routerjson"
# 配置会话和重试机制(应对限流和网络波动)
self.session = requests.Session()
retry = Retry(
total=3,
backoff_factor=0.5,
status_forcelist=[429, 500, 502, 503]
)
self.session.mount('https://', HTTPAdapter(max_retries=retry))
# 核心返回字段配置(按需调整)
self.core_fields = (
"skuId,spuId,productName,price,lowestPrice,"
"commissionRate,commission,couponInfo,"
"goodComments,totalComments,score,"
"shopId,shopName,isSelf,volume,"
"imgUrl,materialUrl,hotScore"
)
# 排序方式映射
self.sort_mapping = {
"price_asc": ("price", "asc"),
"price_desc": ("price", "desc"),
"sales_desc": ("volume", "desc"),
"commission_desc": ("commissionRate", "desc"),
"rating_desc": ("score", "desc")
}
def _generate_sign(self, params: dict) -> str:
"""生成请求签名"""
sorted_params = sorted(
[(k, v) for k, v in params.items() if v is not None],
key=lambda x: x[0]
)
sign_str = self.app_secret for key, value in sorted_params:
encoded_value = urllib.parse.quote(str(value), safe='')
sign_str += f"{key}{encoded_value}"
sign_str += self.app_secret
return hashlib.md5(sign_str.encode('utf-8')).hexdigest().upper()
def search_goods(
self,
keyword: str,
page: int = 1,
page_size: int = 20,
price_range: Optional[Tuple[float, float]] = None,
sort: Optional[str] = "sales_desc",
has_coupon: bool = False,
is_self: Optional[bool] = None,
min_good_rate: Optional[int] = None
) -> Dict:
"""
搜索商品
:param keyword: 搜索关键词(必填)
:param page: 页码,默认1
:param page_size: 每页数量(1-50)
:param price_range: 价格范围元组 (min, max)
:param sort: 排序方式(price_asc/price_desc/sales_desc/commission_desc)
:param has_coupon: 是否只返回有优惠券的商品
:param is_self: 是否只返回自营商品(None表示不筛选)
:param min_good_rate: 最低好评率(0-100)
:return: 包含商品列表和总数的字典
"""
# 构建业务参数(需JSON序列化)
search_params = {
"keyword": keyword,
"pageIndex": page,
"pageSize": min(max(page_size, 1), 50), # 限制1-50
"fields": self.core_fields }
# 价格范围筛选
if price_range and len(price_range) == 2:
search_params["priceFrom"] = price_range[0]
search_params["priceTo"] = price_range[1]
# 排序设置
if sort in self.sort_mapping:
search_params["sortName"], search_params["sort"] = self.sort_mapping[sort]
# 优惠券筛选
if has_coupon:
search_params["hasCoupon"] = 1
# 自营筛选
if is_self is not None:
search_params["isSelf"] = 1 if is_self else 0
# 好评率筛选
if min_good_rate and 0 <= min_good_rate <= 100:
search_params["goodRate"] = min_good_rate
# 构建完整请求参数
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
params = {
"method": "jd.union.open.goods.search",
"app_key": self.app_key,
"access_token": self.access_token,
"timestamp": timestamp,
"format": "json",
"v": "1.0",
"sign_method": "md5",
"360buy_param_json": json.dumps(search_params, ensure_ascii=False)
}
# 生成签名
params["sign"] = self._generate_sign(params)
try:
# 发送请求
response = self.session.get(
self.api_url,
params=params,
timeout=(10, 30)
)
response.raise_for_status()
result = response.json()
# 处理错误响应
if "error_response" in result:
error = result["error_response"]
logger.error(f"接口错误: {error.get('msg')} (代码: {error.get('code')})")
return {"success": False, "error": error.get('msg')}
# 解析成功响应
data = result.get("jd_union_open_goods_search_response", {})
search_result = data.get("result", {})
goods_list = search_result.get("data", [])
total_count = search_result.get("totalCount", 0)
# 数据清洗和格式化
cleaned_items = self._clean_goods_data(goods_list)
# 控制请求频率(避免触发QPS限制)
time.sleep(1.5)
return {
"success": True,
"total": total_count,
"page": page,
"items": cleaned_items }
except requests.exceptions.RequestException as e:
logger.error(f"网络请求异常: {str(e)}")
return {"success": False, "error": f"网络错误: {str(e)}"}
except json.JSONDecodeError:
logger.error("JSON解析失败")
return {"success": False, "error": "响应数据解析失败"}
except Exception as e:
logger.error(f"未知异常: {str(e)}")
return {"success": False, "error": str(e)}
def _clean_goods_data(self, goods_list: List[Dict]) -> List[Dict]:
"""清洗和格式化商品数据"""
cleaned = []
for item in goods_list:
try:
cleaned_item = {
"sku_id": item.get("skuId"),
"spu_id": item.get("spuId"),
"title": item.get("productName", ""),
"price": float(item.get("price", 0)),
"lowest_price": float(item.get("lowestPrice", 0)),
"commission_rate": f"{item.get('commissionRate', 0)}%",
"commission_amount": item.get("commission"),
"has_coupon": bool(item.get("couponInfo")),
"coupon_info": item.get("couponInfo"),
"shop_name": item.get("shopName"),
"is_self": item.get("isSelf") == 1,
"sales_volume": item.get("volume"),
"good_comments": item.get("goodComments"),
"total_comments": item.get("totalComments"),
"good_rate": item.get("score"),
"image_url": item.get("imgUrl"),
"product_url": f"https://item.jd.com/{item.get('skuId')}.html",
"material_url": item.get("materialUrl") # 推广链接
}
cleaned.append(cleaned_item)
except Exception as e:
logger.warning(f"数据清洗异常: {e}")
continue
return cleaned# ==================== 使用示例 ====================if __name__ == "__main__":
# 初始化API客户端(替换为你的真实凭证)
jd_api = JDUnionSearchAPI(
app_key="your_app_key_here",
app_secret="your_app_secret_here",
access_token="your_access_token_here"
)
# 场景1:搜索"保温杯",价格10-50元,有优惠券,按销量排序
result = jd_api.search_goods(
keyword="保温杯",
page=1,
page_size=20,
price_range=(10, 50),
has_coupon=True,
sort="sales_desc"
)
if result["success"]:
print(f"✅ 搜索成功,共找到 {result['total']} 件商品")
for item in result["items"][:5]: # 只展示前5条
print(f"\n【{item['title'][:20]}...】")
print(f" 价格: ¥{item['lowest_price']} (原价¥{item['price']})")
print(f" 佣金: {item['commission_rate']}")
print(f" 销量: {item['sales_volume']} | 好评率: {item['good_rate']}")
print(f" 链接: {item['product_url']}")
else:
print(f"❌ 搜索失败: {result['error']}")六、响应数据解析
成功调用后,接口返回的JSON结构如下:
JSON
{
"jd_union_open_goods_search_response": {
"result": {
"totalCount": 1580,
"data": [
{
"skuId": "100012043978",
"productName": "富光保温杯 304不锈钢 500ml",
"price": 59.90,
"lowestPrice": 39.90,
"commissionRate": 15.00,
"commission": 5.99,
"couponInfo": {
"couponAmount": 10,
"couponCondition": "满30可用"
},
"volume": 50000,
"isSelf": 1,
"shopName": "富光京东自营旗舰店",
"score": 98,
"imgUrl": "https://img10.360buyimg.com/...",
"materialUrl": "https://union-click.jd.com/..." // 推广链接
}
]
}
}}七、常见问题与解决方案
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
sign invalid 签名无效 | 参数排序错误、未URL编码、时间戳格式不对 | 严格按照字典序排序,使用urllib.parse.quote编码值 |
access token expired | Access Token过期 | 重新获取Access Token,注意有效期管理 |
permission denied | 未申请接口权限或权限不足 | 在开放平台申请jd.union.open.goods.search权限 |
QPS limit exceeded | 请求频率超限 | 降低请求频率,基础权限QPS=5,建议加1-2秒延迟 |
| 返回空数据 | 关键词无结果或筛选条件过严 | 尝试更宽泛的关键词,逐步放宽筛选条件 |
parameter error | 业务参数格式错误 | 确保360buy_param_json是合法JSON字符串 |
八、最佳实践建议
- 关键词优化:使用具体品类词(如"无线蓝牙耳机")而非泛词(如"耳机"),搜索结果更精准
- 分页策略:每组查询条件最多返回10,000条结果,如需更多数据需调整关键词或筛选条件
- 数据缓存:对商品信息进行本地缓存(建议1-6小时),减少API调用次数
- 异常重试:网络波动时自动重试,但需避免无限重试导致封禁
- 合规使用:遵守京东开放平台规则,不得用于爬虫、比价等违规场景
九、扩展应用场景
基于该接口可构建以下应用:
- 智能选品系统:结合佣金比例、销量、评价等多维度筛选高潜力商品
- 竞品监控:定时搜索竞品关键词,追踪价格、促销变化
- 优惠券聚合:筛选高佣金+高优惠券商品,构建导购平台
- 价格监控:追踪特定商品价格波动,触发降价提醒