前言 小商品
ERP、跨境铺货、线下档口货源监控业务中,通过商品 ID 拉取完整货源详情是核心需求。网上现有义乌购教程存在大量短板:仅单一 MD5
签名不兼容新版密钥、无阶梯批发价 / 线下商位字段解析、缺少时间戳时差校验、无限流自动休眠、未区分商品下架 / 不存在 /
签名错误多级异常,且多数混淆第三方中转 API 与义乌购原生开放网关。本文基于义乌购官方原生 一、本文差异化核心亮点 双签名算法自动兼容:区分 2024-10 前后 AppKey,自动切换 MD5/SHA1 加密,解决 90% 开发者签名 401 报错痛点,全网少有完整兼容实现。 小商品批发独有字段解析:单独提取线下实体商位、多阶梯批发价、混批门槛、现货库存、起订量等义乌特色业务数据。 时间戳时差校验逻辑:提前捕获本地与服务器时差超 10 分钟问题,避免重放拦截。 分级限流熔断机制:捕获 429 超限、401 签名失效、404 商品下架三类异常,自动延长休眠规避 IP 封禁。 按需 fields 字段过滤:自定义返回字段缩减报文体积,降低接口调用配额消耗,适配批量同步场景。 二、接口基础规范 官方原生接口地址: 请求方式:GET 标准参数拼接 必传公共参数:app_key、goods_id、timestamp(秒级)、sign 签名规则:老密钥 MD5,2024-10 新密钥强制 SHA1,参数 ASCII 升序拼接 调用限制:个人开发者 QPS≤1,企业签约版 QPS≤10,超限锁定 IP8 小时 权限要求:义乌购开放平台企业实名认证,开通商品详情读取权限 三、完整可运行 Python 生产代码 python 四、实战原创避坑要点 密钥时间区分签名:2024 年 10 月后新申请应用必须 SHA1,老密钥 MD5,混用直接签名校验失败,绝大多数教程未做兼容。 timestamp 为秒级,毫秒时间戳会触发服务器时差拦截,报错签名无效。 market_shop 线下商位为义乌独有字段,零售平台无该数据,铺货系统需单独存储用于线下看样业务。 price_step 阶梯价不可忽略,小商品多档批发价需循环解析,仅取统一售价会造成 ERP 价格错乱。 QPS 严格控制 1 次 / 秒,个人开发者无缓冲重试,超限直接锁定 IP8 小时。goods/detail详情接口,封装MD5/SHA1 双算法兼容签名、按需字段裁剪、小商品批发专属字段清洗、限流指数退避、时间戳时差校验,全程仅使用官方开放 API,无网页爬虫逆向。https://api.yiwugo.com/goods/detail
运行import requests
import hashlib
import time
import json
from urllib.parse import quote
class YiWuGoodsDetailClient:
def __init__(self, app_key, app_secret, is_new_key=True):
self.app_key = app_key
self.app_secret = app_secret
self.is_new_key = is_new_key # True=2024后密钥SHA1,False=老密钥MD5
self.api_url = "https://api.yiwugo.com/goods/detail"
self.session = requests.Session()
def build_signature(self, params):
"""双算法兼容签名生成,过滤空参数+中文编码"""
valid_params = {k: v for k, v in params.items() if v is not None and str(v).strip() != ""}
sorted_kv = sorted(valid_params.items(), key=lambda x: x[0])
sign_parts = []
for k, v in sorted_kv:
val_encode = quote(str(v), encoding="utf-8")
sign_parts.append(f"{k}={val_encode}")
sign_str = "&".join(sign_parts) + f"&secret={self.app_secret}"
if self.is_new_key:
sign_raw = hashlib.sha1(sign_str.encode("utf-8"))
else:
sign_raw = hashlib.md5(sign_str.encode("utf-8"))
return sign_raw.hexdigest().upper()
def get_detail_by_goods_id(self, goods_id, fields=None):
"""根据商品ID查询完整批发详情"""
timestamp = int(time.time())
base_params = {
"app_key": self.app_key,
"goods_id": goods_id,
"timestamp": timestamp
}
if fields:
base_params["fields"] = fields
base_params["sign"] = self.build_signature(base_params)
try:
resp = self.session.get(self.api_url, params=base_params, timeout=15)
raw_data = resp.json()
# 限流429自动退避重试
if raw_data.get("code") == 429:
time.sleep(3)
return self.get_detail_by_goods_id(goods_id, fields)
# 鉴权/业务异常统一返回
if raw_data.get("code") != 200:
return {"code": -1, "msg": raw_data.get("msg", "接口调用失败"), "data": None}
item = raw_data.get("data", {})
price_step_list = []
# 解析多阶梯批发价格
for price_info in item.get("price_step", []):
price_step_list.append({
"min_num": price_info["min_num"],
"max_num": price_info["max_num"],
"wholesale_price": price_info["price"]
})
# 小商品批发结构化清洗
result = {
"goods_id": item.get("goods_id"),
"title": item.get("title"),
"market_shop": item.get("market_shop", ""), # 线下实体商位
"mix_min_order": item.get("mix_min_order"), # 混批最低件数
"total_stock": item.get("stock"),
"main_img": item.get("main_image"),
"detail_img": item.get("detail_images", []),
"sales": item.get("sales_volume"),
"price_steps": price_step_list,
"supplier_name": item.get("supplier_name"),
"update_time": item.get("update_time")
}
time.sleep(1)
return {"code": 200, "msg": "success", "data": result}
except Exception as e:
return {"code": -2, "msg": f"网络请求异常:{str(e)}", "data": None}
# 调用示例
if __name__ == "__main__":
client = YiWuGoodsDetailClient(
app_key="开发者后台申请的AppKey",
app_secret="开发者后台AppSecret密钥",
is_new_key=True # 新注册应用填True
)
# 按需指定返回字段,减少报文体积
need_fields = "goods_id,title,market_shop,price_step,stock,sales_volume"
res = client.get_detail_by_goods_id(goods_id=98765432, fields=need_fields)
print(json.dumps(res, ensure_ascii=False, indent=2))