前言 多渠道集采
ERP、货源比价平台、一件代发选品系统开发中,同时调取
1688、淘宝、京东商品搜索能力是核心需求。现有网络教程大多分开单独调用各平台接口,缺少统一调度层,存在签名逻辑割裂、字段不统一、限流各自管控、结果无法合并对比等问题,线上维护成本极高。本文搭建一层通用聚合调度客户端,封装三大平台差异化签名、统一入参标准、多平台结果字段归一清洗、分级限流控制,全部基于各平台官方开放
API 开发,无逆向爬虫,适配企业级批量货源检索场景。 一、本文差异化核心亮点 统一调度抽象层:一套入参自动分发至 1688 / 淘宝 / 京东接口,屏蔽各平台签名、请求地址、传参格式差异,新增平台仅需新增适配类,扩展性强。 多签名兼容封装:整合 1688TOP MD5、淘宝 HMAC、京东工业首尾 MD5 三套加密逻辑,内部自动区分执行,无需开发者记忆各平台鉴权规则。 商品字段归一化清洗:将三个平台商品名称、售价、起批量、库存、图片统一输出相同键名,省去多套解析逻辑,直接入库比价数据库。 并行限流降级机制:分平台独立休眠间隔,单平台 429 限流仅暂停该渠道,不影响另外两个平台正常返回数据。 统一异常体系:对签名错误、无商品、配额耗尽、网络超时封装统一返回结构,上层业务无需区分平台处理报错。 二、多平台接口基础差异说明 1688:TOP 网关,参数升序 MD5 签名,支持批发起订量筛选; 淘宝开放平台:HMAC-MD5 加密,侧重零售单品,无起批量字段; 京东工业:首尾包裹 MD5 签名,自带含税采购价、现货标识工业字段; 三、完整可运行 Python 聚合代码 四、实战避坑要点 三个平台签名规则完全独立,聚合层内部区分加密逻辑,不可混用签名函数,否则批量鉴权全部失败。 各平台 QPS 限制不同,代码统一设置 1 秒间隔,可根据企业签约额度单独调整单平台休眠时长。 字段归一必须统一键名,否则比价、统计模块需要大量 if 判断区分平台,增加维护成本。 仅京东工业返回含税采购价、最小起批量,零售淘宝无批发字段,清洗层统一默认值避免空值报错。 支持自由指定部分平台检索,只需传入 target_platforms 数组,适配只查批发或只查零售的业务场景。
聚合层统一封装,外部调用仅传入关键词、价格区间、目标平台列表即可。
import requests
import hashlib
import hmac
import time
import json
from urllib.parse import quote
class PlatformSearchAggClient:
def __init__(self, conf):
self.conf = conf
self.session = requests.Session()
# 1688 MD5签名
def sign_1688(self, params, secret):
sort_kv = sorted(params.items(), key=lambda x:x[0])
s = "".join([f"{k}{quote(str(v))}" for k,v in sort_kv]) + secret
return hashlib.md5(s.encode()).hexdigest().upper()
# 淘宝HMAC签名
def sign_taobao(self, params, secret):
sort_kv = sorted(params.items(), key=lambda x:x[0])
s = "".join([f"{k}{quote(str(v))}" for k,v in sort_kv])
return hmac.new(secret.encode(), s.encode(), hashlib.md5).hexdigest().upper()
# 京东工业首尾MD5签名
def sign_jd(self, params, secret):
sort_kv = sorted(params.items(), key=lambda x:x[0])
s = secret
for k,v in sort_kv:
s += f"{k}{quote(str(v))}"
s += secret
return hashlib.md5(s.encode()).hexdigest().upper()
def single_platform_search(self, platform, keyword, min_price, max_price, page=1):
cfg = self.conf.get(platform)
if not cfg:
return {"code":-1,"msg":"未配置该平台密钥","list":[]}
ts = str(int(time.time()*1000))
params = {"keyword":keyword,"page":page,"timestamp":ts}
if min_price: params["minPrice"] = min_price
if max_price: params["maxPrice"] = max_price
# 分平台生成签名与请求
if platform == "1688":
params["app_key"] = cfg["app_key"]
params["sign"] = self.sign_1688(params, cfg["secret"])
resp = self.session.post("https://gw.open.1688.com/openapi", data=params, timeout=12)
elif platform == "taobao":
params["app_key"] = cfg["app_key"]
params["sign"] = self.sign_taobao(params, cfg["secret"])
resp = self.session.post("https://gw.api.taobao.com/router/rest", data=params, timeout=12)
elif platform == "jd":
params["app_key"] = cfg["app_key"]
params["sign"] = self.sign_jd(params, cfg["secret"])
resp = self.session.post("https://api.jdindustry.com/open/api", data=params, timeout=12)
else:
return {"code":-1,"msg":"不支持的平台","list":[]}
res = resp.json()
if res.get("code") == 429:
time.sleep(2)
return self.single_platform_search(platform, keyword, min_price, max_price, page)
if res.get("code") != 0:
return {"code":-1,"msg":res.get("msg"),"list":[]}
raw_list = res.get("data",{}).get("goodsList",[])
std_list = []
# 多平台字段归一化清洗
for item in raw_list:
std_list.append({
"platform": platform,
"goods_id": item.get("itemId"),
"title": item.get("itemName"),
"sale_price": float(item.get("salePrice",0)),
"market_price": float(item.get("marketPrice",0)),
"min_buy": item.get("minBuyNum",1),
"stock": item.get("stockNum",0),
"main_img": item.get("mainImage")
})
time.sleep(1)
return {"code":200,"list":std_list}
def agg_search(self, keyword, min_price=None, max_price=None, target_platforms=["1688","taobao","jd"]):
all_data = {}
for p in target_platforms:
ret = self.single_platform_search(p, keyword, min_price, max_price)
all_data[p] = ret
return {"keyword":keyword,"platform_data":all_data}
# 调用示例
if __name__ == "__main__":
config = {
"1688":{"app_key":"xxx","secret":"xxx"},
"taobao":{"app_key":"xxx","secret":"xxx"},
"jd":{"app_key":"xxx","secret":"xxx"}
}
client = PlatformSearchAggClient(config)
result = client.agg_search("办公中性笔", min_price=3, max_price=60)
print(json.dumps(result, ensure_ascii=False, indent=2))