Cdiscount 是法国头部电商平台,其开放 API(需通过官方开发者平台授权)提供商品详情、库存、定价等核心数据,返回格式为 JSON。本文聚焦「合规调用商品详情 API + 解析核心字段 + 提取多规格 SKU」,给出完整 Python 实现方案,兼顾实用性与容错性。
一、核心前提(合规与授权)
- 授权准备 :
- 在 Cdiscount 开发者平台注册账号,创建应用,获取
Client ID和Client Secret; - 通过 OAuth 2.0 协议获取 Access Token(有效期通常 1 小时),所有 API 请求需携带 Token。
- 在 Cdiscount 开发者平台注册账号,创建应用,获取
- API 基础信息 :
- 商品详情 API 端点:
https://api.cdiscount.com/OpenApi/json/GetProduct; - 请求方式:POST(需传入商品 ID / 参考 SKU);
- 频率限制:默认 100 次 / 分钟,超量触发 429 错误。
- 商品详情 API 端点:
二、完整实现代码(含多规格 SKU 提取)
1. 环境依赖
bash
运行
pip install requests # 发送HTTP请求
pip install python-dotenv # 可选,存储敏感信息(避免硬编码)
2. 核心代码(含注释)
python
运行
import requests
import json
import time
from typing import Dict, List, Optional
import logging
# -------------------------- 基础配置与日志 --------------------------
# 日志配置(记录异常与关键流程)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[logging.FileHandler("cdiscount_api.log"), logging.StreamHandler()]
)
logger = logging.getLogger(__name__)
# 敏感信息(建议通过.env文件读取,避免硬编码)
CLIENT_ID = "你的Client ID"
CLIENT_SECRET = "你的Client Secret"
TOKEN_URL = "https://api.cdiscount.com/OpenApi/json/AccessToken" # Token获取地址
PRODUCT_API_URL = "https://api.cdiscount.com/OpenApi/json/GetProduct" # 商品详情API
# 超时与限流配置
TIMEOUT = 15 # 请求超时时间(秒)
RATE_LIMIT_DELAY = 0.6 # 调用间隔(避免超限,100次/分钟=0.6秒/次)
RETRY_TIMES = 3 # 异常重试次数
# -------------------------- 通用工具函数 --------------------------
def get_access_token() -> Optional[str]:
"""获取Cdiscount Access Token(OAuth 2.0授权)"""
payload = {
"parameters": {
"Login": CLIENT_ID,
"Password": CLIENT_SECRET
}
}
for retry in range(RETRY_TIMES):
try:
response = requests.post(
TOKEN_URL,
data=json.dumps(payload),
headers={"Content-Type": "application/json"},
timeout=TIMEOUT
)
response.raise_for_status() # 触发HTTP错误(如401、500)
token_data = response.json()
# 校验Token响应(Cdiscount返回格式特殊,需检查Success字段)
if token_data.get("Success") and token_data.get("Token"):
logger.info("Token获取成功,有效期1小时")
return token_data["Token"]
else:
error_msg = token_data.get("ErrorMessage", "未知错误")
logger.error(f"Token获取失败:{error_msg}")
return None
except Exception as e:
logger.error(f"Token获取异常(重试{retry+1}/{RETRY_TIMES}):{str(e)}")
if retry < RETRY_TIMES - 1:
time.sleep(2 ** retry) # 指数退避重试
return None
def safe_extract_field(data: Dict, field_path: str, default: any = None) -> any:
"""
安全提取JSON嵌套字段(处理字段缺失/类型不匹配)
:param data: 解析后的JSON字典
:param field_path: 字段路径,如"Product.SellerName"
: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 fetch_cdiscount_product(product_id: str, token: str) -> Optional[Dict]:
"""
调用Cdiscount商品详情API,解析核心字段(含多规格SKU)
:param product_id: Cdiscount商品ID(如"123456789")
:param token: 有效Access Token
:return: 解析后的商品数据字典
"""
# 构造API请求参数(Cdiscount要求固定格式)
payload = {
"Token": token,
"Parameters": {
"ProductIdList": [product_id], # 支持传入多个商品ID(最多20个)
"WithProductAttribute": True, # 启用属性(多规格依赖)
"WithProductB2BPrice": False, # 关闭B2B价格(仅需零售价格)
"WithProductEan": True, # 启用EAN码
"WithProductMedia": True, # 启用商品图片
"WithProductStock": True # 启用库存信息
}
}
# 发送API请求(带重试)
for retry in range(RETRY_TIMES):
time.sleep(RATE_LIMIT_DELAY) # 限流控制
try:
logger.info(f"开始调用商品详情API,商品ID:{product_id}")
response = requests.post(
PRODUCT_API_URL,
data=json.dumps(payload),
headers={"Content-Type": "application/json"},
timeout=TIMEOUT
)
response.raise_for_status()
raw_json = response.json()
# 校验API响应是否成功
if not raw_json.get("Success"):
error_msg = raw_json.get("ErrorMessage", "API响应失败")
logger.error(f"商品 {product_id} API响应失败:{error_msg}")
return None
# 提取商品列表(支持批量查询,此处取第一个)
products = raw_json.get("Products", [])
if not products:
logger.warning(f"商品 {product_id} 无返回数据")
return None
product = products[0]
# -------------------------- 解析核心字段 --------------------------
parsed_product = {
# 基础信息
"商品ID": safe_extract_field(product, "Id", "未知ID"),
"商品标题": safe_extract_field(product, "Name", "无标题"),
"卖家名称": safe_extract_field(product, "SellerName", "未知卖家"),
"品牌": safe_extract_field(product, "Brand", "未知品牌"),
"类目ID": safe_extract_field(product, "CategoryId", 0),
"类目名称": safe_extract_field(product, "CategoryName", "未知类目"),
"主图URL": safe_extract_field(product, "MainImageUrl", ""),
"平均评分": safe_extract_field(product, "ReviewAverageNote", 0.0),
"评论数": safe_extract_field(product, "ReviewCount", 0),
# 价格与库存(默认取第一个SKU的基础价格)
"基础售价": safe_extract_field(product, "Price", 0.0),
"基础库存": safe_extract_field(product, "Stock", 0),
"库存状态": "有货" if safe_extract_field(product, "Stock", 0) > 0 else "无货",
# 多规格SKU列表(核心提取)
"多规格SKU": []
}
# -------------------------- 提取多规格SKU --------------------------
# Cdiscount多规格存储在"Attributes"和"Skus"字段中
skus = safe_extract_field(product, "Skus", [])
attributes = safe_extract_field(product, "Attributes", [])
# 解析规格属性(如颜色、尺寸)
attr_map = {}
for attr in attributes:
attr_code = safe_extract_field(attr, "Code")
attr_name = safe_extract_field(attr, "Name")
if attr_code and attr_name:
attr_map[attr_code] = attr_name
# 解析每个SKU的详细信息
for sku in skus:
try:
sku_data = {
"SKU ID": safe_extract_field(sku, "Id", "未知SKU"),
"SKU参考码": safe_extract_field(sku, "SellerProductId", ""),
"售价": safe_extract_field(sku, "Price", 0.0),
"库存": safe_extract_field(sku, "Stock", 0),
"EAN码": safe_extract_field(sku, "Ean", ""),
"规格属性": {}
}
# 提取SKU对应的规格值(如颜色=红色、尺寸=M)
sku_attributes = safe_extract_field(sku, "Attributes", [])
for sku_attr in sku_attributes:
attr_code = safe_extract_field(sku_attr, "Code")
attr_value = safe_extract_field(sku_attr, "Value")
if attr_code in attr_map and attr_value:
sku_data["规格属性"][attr_map[attr_code]] = attr_value
parsed_product["多规格SKU"].append(sku_data)
except Exception as e:
logger.warning(f"SKU解析失败:{str(e)},跳过该SKU")
continue
logger.info(f"商品 {product_id} 解析完成,共提取 {len(parsed_product['多规格SKU'])} 个SKU")
return parsed_product
except requests.exceptions.Timeout:
logger.error(f"商品 {product_id} 请求超时(重试{retry+1}/{RETRY_TIMES})")
except requests.exceptions.ConnectionError:
logger.error(f"商品 {product_id} 连接失败(重试{retry+1}/{RETRY_TIMES})")
except Exception as e:
logger.error(f"商品 {product_id} 解析异常(重试{retry+1}/{RETRY_TIMES}):{str(e)}")
if retry < RETRY_TIMES - 1:
time.sleep(2 ** retry) # 重试延迟
logger.error(f"商品 {product_id} 调用/解析失败(重试{RETRY_TIMES}次后)")
return None
# -------------------------- 主函数(测试调用) --------------------------
if __name__ == "__main__":
# 1. 获取Token
access_token = get_access_token()
if not access_token:
logger.critical("Token获取失败,终止程序")
exit(1)
# 2. 调用商品详情API(替换为目标商品ID)
target_product_id = "123456789" # Cdiscount商品ID
product_data = fetch_cdiscount_product(target_product_id, access_token)
# 3. 打印解析结果
if product_data:
print("\n===== Cdiscount商品详情解析结果 =====")
print(f"商品ID:{product_data['商品ID']}")
print(f"标题:{product_data['商品标题']}")
print(f"品牌:{product_data['品牌']}")
print(f"基础售价:{product_data['基础售价']} €")
print(f"库存状态:{product_data['库存状态']}")
print(f"平均评分:{product_data['平均评分']}({product_data['评论数']}条评论)")
print("\n===== 多规格SKU详情 =====")
for idx, sku in enumerate(product_data["多规格SKU"], 1):
print(f"\nSKU {idx}:")
print(f" SKU ID:{sku['SKU ID']}")
print(f" 参考码:{sku['SKU参考码']}")
print(f" 售价:{sku['售价']} €")
print(f" 库存:{sku['库存']}")
print(f" 规格属性:{sku['规格属性']}")
三、关键解析逻辑说明
1. Cdiscount JSON 响应结构(核心部分)
Cdiscount 商品详情 API 返回的 JSON 结构层级较深,核心字段路径如下:
json
{
"Success": true,
"Products": [
{
"Id": "123456789", // 商品ID
"Name": "无线蓝牙耳机", // 商品标题
"Brand": "SoundCore", // 品牌
"Price": 79.99, // 基础价格(欧元)
"Stock": 156, // 基础库存
"MainImageUrl": "https://...", // 主图
"ReviewAverageNote": 4.7, // 平均评分
"ReviewCount": 328, // 评论数
"Attributes": [ // 规格属性定义(如颜色、尺寸)
{"Code": "COLOR", "Name": "Couleur"},
{"Code": "SIZE", "Name": "Taille"}
],
"Skus": [ // 多规格SKU列表
{
"Id": "SKU123",
"SellerProductId": "BT-BLK-M",
"Price": 79.99,
"Stock": 89,
"Ean": "3560070621564",
"Attributes": [
{"Code": "COLOR", "Value": "Noir"},
{"Code": "SIZE", "Value": "M"}
]
}
]
}
]
}
2. 多规格 SKU 提取核心逻辑
- 属性映射 :先解析
Attributes字段,将规格编码(如COLOR)映射为易读名称(如Couleur/ 颜色); - SKU 遍历 :循环
Skus数组,提取每个 SKU 的 ID、价格、库存、EAN 码; - 规格值匹配 :将 SKU 的
Attributes与全局属性映射匹配,得到「颜色 = 黑色、尺寸 = M」等易读规格; - 容错处理:单个 SKU 解析失败时跳过,不影响其他 SKU 提取。
四、避坑与优化建议
1. 常见问题解决
- Token 失效:Token 有效期 1 小时,需在代码中捕获 401 错误,自动重新获取 Token;
- 字段缺失 :部分商品无 SKU(单规格),
Skus字段为空,需默认返回空列表; - 价格单位:Cdiscount 默认返回欧元(€),需根据业务需求转换汇率;
- 编码问题:商品标题含法语特殊字符(如 é、è),需确保 JSON 解析时使用 UTF-8 编码。
2. 性能优化
- 批量查询:API 支持单次传入最多 20 个商品 ID,批量调用可减少请求次数;
- 数据缓存:将解析后的商品数据缓存到本地 / Redis,避免重复调用 API;
- 异步调用 :高并发场景下,使用
aiohttp替代requests实现异步请求。
3. 合规要求
- 数据用途:仅可用于授权范围内的业务(如店铺运营、价格监控),禁止转售数据;
- 频率控制:严格遵守 100 次 / 分钟的限流规则,超量会导致账号临时封禁;
- 隐私保护:不泄露卖家名称、SKU 参考码等敏感信息,解析日志需脱敏。
五、总结
Python 调用 Cdiscount 商品详情 API 的核心流程为:「获取 Token → 构造合规请求 → 解析 JSON 基础字段 → 提取多规格 SKU → 容错处理」。
本文方案覆盖了字段缺失、网络异常、SKU 解析失败等常见问题,解析逻辑适配 Cdiscount 的 JSON 结构特点,可直接用于商品数据采集、价格监控、库存管理等合法业务场景。重点关注 Token 有效期和限流规则,即可稳定采集数据。