Python 实现苏宁商品详情 API 调用:请求构造、响应解析与异常处理
·
Python 实现苏宁商品详情 API 调用:请求构造、响应解析与异常处理
苏宁开放平台(苏宁开发者中心)的商品详情 API 是获取商品基础信息、价格、库存、规格等核心数据的核心接口,其调用需遵循「签名验证 + 固定请求格式」规范,且需处理网络异常、签名错误、字段缺失等问题。本文基于 Python 实现全流程调用方案,覆盖请求构造、响应解析、异常处理全链路,确保调用稳定合规。
一、核心前提(苏宁开放平台准备)
1. 授权与密钥获取
- 注册苏宁开放平台开发者账号,创建应用并审核通过;
- 获取核心参数:
appKey(应用密钥)、appSecret(应用秘钥)、accessToken(若需用户授权,公共接口可省略); - 确认商品详情 API 端点:苏宁商品详情 API(公共版)端点为
https://open.suning.com/api/http/sopRequest(需按接口文档确认最新地址)。
2. 核心规则
- 请求方式:POST/GET(苏宁 API 多为 POST,需携带签名);
- 签名规则:基于
appKey+appSecret+ 请求参数 + 时间戳生成 MD5/SHA256 签名(苏宁默认 MD5); - 响应格式:JSON,包含
code(状态码)、msg(提示信息)、body(核心数据); - 限流规则:默认 100 次 / 分钟,超限返回 429 错误。
二、完整实现代码
1. 环境依赖
bash
运行
pip install requests # HTTP请求
pip install hashlib # 签名生成(Python内置,无需额外安装)
pip install time # 时间戳生成(内置)
2. 核心代码(含请求构造、解析、异常处理)
python
运行
import requests
import hashlib
import time
import json
from typing import Dict, Optional, Any
import logging
# -------------------------- 基础配置与日志 --------------------------
# 日志配置(记录调用日志与异常)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[logging.FileHandler("suning_api.log"), logging.StreamHandler()]
)
logger = logging.getLogger(__name__)
# 苏宁API核心参数(替换为你的真实信息)
APP_KEY = "你的appKey"
APP_SECRET = "你的appSecret"
API_URL = "https://open.suning.com/api/http/sopRequest" # 商品详情API端点
TIMEOUT = 15 # 请求超时时间(秒)
RETRY_TIMES = 3 # 异常重试次数
RATE_LIMIT_DELAY = 0.6 # 限流延迟(100次/分钟=0.6秒/次)
# -------------------------- 签名生成工具函数 --------------------------
def generate_sign(params: Dict, app_secret: str) -> str:
"""
生成苏宁API签名(按苏宁规则:参数排序+拼接秘钥+MD5加密)
:param params: 请求参数(不含sign)
:param app_secret: 应用秘钥appSecret
:return: 签名字符串
"""
# 1. 按参数名ASCII升序排序
sorted_params = sorted(params.items(), key=lambda x: x[0])
# 2. 拼接参数为 "key1=value1key2=value2" 格式
param_str = "".join([f"{k}{v}" for k, v in sorted_params])
# 3. 首尾拼接appSecret
sign_str = f"{app_secret}{param_str}{app_secret}"
# 4. MD5加密并转大写
md5 = hashlib.md5()
md5.update(sign_str.encode("utf-8"))
sign = md5.hexdigest().upper()
return sign
# -------------------------- 安全字段提取函数 --------------------------
def safe_extract_field(data: Dict, field_path: str, default: Any = None) -> Any:
"""
安全提取嵌套JSON字段(处理字段缺失/类型不匹配)
:param data: 解析后的JSON字典
:param field_path: 字段路径,如"body.productInfo.productName"
:param default: 字段缺失时的默认值
:return: 提取的字段值
"""
keys = field_path.split(".")
value = data
for key in keys:
if isinstance(value, dict) and key in value:
value = value[key]
else:
logger.debug(f"字段 {field_path} 缺失,返回默认值:{default}")
return default
return value
# -------------------------- 苏宁API请求构造与调用 --------------------------
def build_suning_request_params(product_id: str) -> Dict:
"""
构造苏宁商品详情API请求参数(含签名)
:param product_id: 苏宁商品ID(如"123456789")
:return: 完整请求参数(含签名)
"""
# 基础参数(苏宁API必传)
params = {
"appKey": APP_KEY,
"timestamp": str(int(time.time() * 1000)), # 毫秒级时间戳
"format": "json", # 响应格式
"version": "v1.0", # API版本
"method": "suning.custom.product.get", # 商品详情API方法名(需确认最新方法名)
"productCode": product_id, # 商品ID参数(按接口文档调整参数名)
"signMethod": "md5" # 签名方式
}
# 生成签名并添加到参数
params["sign"] = generate_sign(params, APP_SECRET)
return params
def call_suning_product_api(product_id: str) -> Optional[Dict]:
"""
调用苏宁商品详情API,处理重试、限流、异常
:param product_id: 苏宁商品ID
:return: 解析后的商品数据字典(失败返回None)
"""
# 限流延迟(避免超限)
time.sleep(RATE_LIMIT_DELAY)
for retry in range(RETRY_TIMES):
try:
# 1. 构造请求参数(含签名)
request_params = build_suning_request_params(product_id)
logger.info(f"开始调用苏宁API,商品ID:{product_id},重试次数:{retry+1}")
# 2. 发送POST请求(苏宁API多为POST,部分为GET,需按文档调整)
response = requests.post(
API_URL,
data=request_params,
timeout=TIMEOUT,
headers={"Content-Type": "application/x-www-form-urlencoded"}
)
# 触发HTTP错误(如404、500、429)
response.raise_for_status()
# 3. 解析响应JSON
response_json = response.json()
logger.debug(f"API响应:{json.dumps(response_json, ensure_ascii=False)[:500]}...")
# 4. 校验业务状态码
code = safe_extract_field(response_json, "code", "")
msg = safe_extract_field(response_json, "msg", "未知错误")
if code == "0000": # 苏宁API成功状态码(以文档为准)
logger.info(f"商品 {product_id} API调用成功")
return response_json
else:
logger.error(f"商品 {product_id} 业务错误:{code} - {msg}")
return None
except requests.exceptions.Timeout:
logger.error(f"商品 {product_id} 请求超时(重试{retry+1}/{RETRY_TIMES})")
if retry < RETRY_TIMES - 1:
time.sleep(2 ** retry) # 指数退避重试
continue
except requests.exceptions.ConnectionError:
logger.error(f"商品 {product_id} 连接失败(重试{retry+1}/{RETRY_TIMES})")
if retry < RETRY_TIMES - 1:
time.sleep(2 ** retry)
continue
except requests.exceptions.HTTPError as e:
# 处理特定HTTP状态码
if response.status_code == 429:
logger.warning(f"商品 {product_id} 限流(429),休眠60秒重试")
time.sleep(60)
continue
elif response.status_code == 401:
logger.error(f"商品 {product_id} 签名错误/权限不足(401):{e}")
return None # 签名错误无需重试
elif response.status_code == 404:
logger.error(f"商品 {product_id} 不存在(404)")
return None
else:
logger.error(f"商品 {product_id} HTTP错误:{e}")
if retry < RETRY_TIMES - 1:
time.sleep(2 ** retry)
continue
except json.JSONDecodeError:
logger.error(f"商品 {product_id} 响应JSON解析失败:{response.text[:200]}...")
return None
except Exception as e:
logger.error(f"商品 {product_id} 未知异常(重试{retry+1}/{RETRY_TIMES}):{str(e)}", exc_info=True)
if retry < RETRY_TIMES - 1:
time.sleep(2 ** retry)
continue
logger.error(f"商品 {product_id} 调用失败(重试{RETRY_TIMES}次后)")
return None
# -------------------------- 商品数据解析(核心字段提取) --------------------------
def parse_suning_product_data(raw_data: Dict) -> Dict:
"""
解析苏宁商品详情API响应,提取核心字段
:param raw_data: API返回的原始JSON字典
:return: 结构化的商品数据
"""
parsed_data = {
# 基础信息
"商品ID": safe_extract_field(raw_data, "body.productInfo.productCode", "未知ID"),
"商品名称": safe_extract_field(raw_data, "body.productInfo.productName", "无标题"),
"品牌": safe_extract_field(raw_data, "body.productInfo.brandName", "未知品牌"),
"类目ID": safe_extract_field(raw_data, "body.productInfo.categoryCode", 0),
"类目名称": safe_extract_field(raw_data, "body.productInfo.categoryName", "未知类目"),
# 价格信息
"售价(元)": safe_extract_field(raw_data, "body.priceInfo.salePrice", 0.0),
"原价(元)": safe_extract_field(raw_data, "body.priceInfo.originalPrice", 0.0),
"会员价(元)": safe_extract_field(raw_data, "body.priceInfo.memberPrice", 0.0),
# 库存信息
"库存数量": safe_extract_field(raw_data, "body.stockInfo.stockCount", 0),
"库存状态": safe_extract_field(raw_data, "body.stockInfo.stockStatus", "未知"),
# 规格信息
"主图URL": safe_extract_field(raw_data, "body.productInfo.mainImageUrl", ""),
"规格列表": safe_extract_field(raw_data, "body.skuInfo.skuList", [])
}
# 解析规格列表(格式化SKU信息)
sku_list = []
for sku in parsed_data["规格列表"]:
if isinstance(sku, dict):
sku_info = {
"SKU ID": safe_extract_field(sku, "skuCode", ""),
"规格属性": safe_extract_field(sku, "skuAttr", ""),
"SKU售价": safe_extract_field(sku, "salePrice", 0.0),
"SKU库存": safe_extract_field(sku, "stockCount", 0)
}
sku_list.append(sku_info)
parsed_data["规格列表"] = sku_list
logger.info(f"商品 {parsed_data['商品ID']} 解析完成")
return parsed_data
# -------------------------- 主函数(测试调用) --------------------------
if __name__ == "__main__":
# 目标商品ID(替换为真实苏宁商品ID)
target_product_id = "123456789"
# 1. 调用API获取原始数据
raw_api_data = call_suning_product_api(target_product_id)
if not raw_api_data:
logger.critical("API调用失败,终止解析")
exit(1)
# 2. 解析核心字段
product_data = parse_suning_product_data(raw_api_data)
# 3. 打印解析结果
print("\n===== 苏宁商品详情解析结果 =====")
print(f"商品ID:{product_data['商品ID']}")
print(f"商品名称:{product_data['商品名称']}")
print(f"品牌:{product_data['品牌']}")
print(f"售价:{product_data['售价(元)']} 元(原价:{product_data['原价(元)']} 元)")
print(f"库存:{product_data['库存数量']} 件(状态:{product_data['库存状态']})")
print(f"主图URL:{product_data['主图URL']}")
print("\n===== 规格SKU列表 =====")
for idx, sku in enumerate(product_data["规格列表"], 1):
print(f"\nSKU {idx}:")
print(f" SKU ID:{sku['SKU ID']}")
print(f" 规格属性:{sku['规格属性']}")
print(f" 售价:{sku['SKU售价']} 元")
print(f" 库存:{sku['SKU库存']} 件")
三、核心流程拆解
1. 请求构造(签名是核心)
苏宁 API 的核心风控是签名验证,步骤如下:
- 构造基础参数(
appKey、时间戳、API 方法名、商品 ID 等); - 按参数名 ASCII 升序排序(如
appKey→format→method); - 拼接参数为
key1=value1key2=value2格式; - 首尾拼接
appSecret,生成 MD5 并转大写,得到sign; - 将
sign加入参数,作为最终请求参数。
注意:参数名 / 值不可遗漏,时间戳需为毫秒级,否则签名验证失败(返回 401 错误)。
2. 响应解析(结构化提取核心字段)
苏宁商品详情 API 响应结构示例:
json
{
"code": "0000",
"msg": "success",
"body": {
"productInfo": {
"productCode": "123456789",
"productName": "苏宁自营 小米14 5G手机",
"brandName": "小米",
"categoryCode": "10001",
"mainImageUrl": "https://img10.suning.cn/..."
},
"priceInfo": {
"salePrice": 3999.0,
"originalPrice": 4299.0,
"memberPrice": 3949.0
},
"stockInfo": {
"stockCount": 500,
"stockStatus": "有货"
},
"skuInfo": {
"skuList": [
{"skuCode": "SKU123", "skuAttr": "黑色+12GB+256GB", "salePrice": 3999.0, "stockCount": 200}
]
}
}
}
通过safe_extract_field函数逐层提取字段,避免因字段缺失导致KeyError中断程序。
3. 异常处理(全链路容错)
| 异常类型 | 处理策略 |
|---|---|
| 签名错误(401) | 校验appKey/appSecret是否正确,参数排序 / 拼接是否符合规则,无需重试 |
| 限流(429) | 休眠 60 秒后重试,或降低请求频率(如 0.8 秒 / 次) |
| 超时 / 连接失败 | 指数退避重试(1s→2s→4s),最多 3 次 |
| 商品不存在(404) | 记录无效商品 ID,直接返回 None |
| JSON 解析失败 | 记录原始响应片段,返回 None |
| 业务错误(code≠0000) | 打印错误码 + 提示信息,返回 None |
四、避坑指南与优化建议
1. 常见坑点解决
- 签名验证失败:
- 检查参数名是否与文档一致(如
productCodevsproductId); - 时间戳需为毫秒级(
int(time.time()*1000)),而非秒级; - 拼接参数时不可加多余符号(如 &、=),严格按
key=value拼接。
- 检查参数名是否与文档一致(如
- 字段缺失:部分商品无会员价 / 规格,需设置默认值(如 0.0、空列表);
- 限流触发:严格遵守 100 次 / 分钟规则,批量调用时添加
RATE_LIMIT_DELAY; - 响应编码:苏宁 API 响应为 UTF-8,无需额外转码,避免乱码。
2. 性能 / 稳定性优化
- 批量调用优化:将商品 ID 按批次拆分,每批调用间隔 0.6 秒,避免超限;
- Token 缓存(如需):若 API 需
accessToken,缓存 Token(有效期 2 小时),避免重复获取; - 断点续采:记录已调用的商品 ID,程序中断后从断点继续;
- 日志脱敏:日志中隐藏
appSecret/accessToken,仅保留商品 ID、错误码等非敏感信息; - 异步调用(进阶):使用
aiohttp替代requests,实现异步批量调用,提升采集效率。
3. 合规要求
- 仅采集自有业务所需数据,禁止滥用 / 转售;
- 遵守苏宁开放平台的《开发者协议》,不高频恶意调用;
- 数据留存不超过业务必需周期,定期清理过期数据。
五、总结
Python 调用苏宁商品详情 API 的核心是「合规构造请求(签名)+ 结构化解析响应 + 全链路异常容错」:
- 签名是基础:严格按苏宁规则生成签名,避免 401 错误;
- 解析是核心:通过安全字段提取函数,处理字段缺失 / 类型不匹配;
- 异常是保障:针对不同异常场景(限流、超时、业务错误)制定差异化处理策略,确保调用稳定。
该方案可直接适配单商品调用、批量采集等场景,兼顾合规性与稳定性,适合电商数据监控、库存同步等业务需求。
更多推荐
所有评论(0)