前言 办公耗材经销商管理、政企集采比价、企业采购
ERP、货源价格监控系统开发场景中,关键词批量检索得力全品类商品是核心前置能力。目前网络流通的电商对接教程无法适配得力云独有的 Header
分层鉴权体系,普遍存在多处短板:仅简化实现参数签名、缺失办公行业集采专属筛选条件、无全自动分页闭环逻辑、缺少阶梯批发价、装箱规格解析、无限流指数退避机制,且大量混淆第三方爬虫接口与得力官方开放网关。本文基于得力
E + 稳定搜索指令 一、本文差异化核心亮点 Header 分层完整 HMAC-SHA256 签名落地 办公集采专属多维度筛选 全自动分页闭环逻辑 分级限流熔断与异常捕获 货源数据标准化清洗 二、接口基础规范 生产网关地址: 业务指令: 请求方式:POST JSON 报文,鉴权信息全部存放 HTTP Header 鉴权 Header:App-Key、App-Timestamp(13 位毫秒)、Api-Module、Api-Cmd、Sign 签名 签名规则:POST + 接口路径 + 模块 + 指令 + 时间戳 + 压缩无空格业务报文,拼接后 HMAC-SHA256 加密 调用限制:普通经销商 QPS≤1,企业签约版 QPS≤5,超限返回 429 错误码 权限要求:得力开放平台完成企业实名认证,创建应用并开通商品搜索读取接口权限 三、完整可运行 Python 生产代码 python 四、实战原创避坑要点 时间戳强制 13 位毫秒数值,秒级时间戳会直接判定签名不匹配,多数简易教程未做强制约束。 业务报文必须压缩无空格 JSON,格式化换行、空格会改变签名源字符串,鉴权永久失败。 Api-Module、Api-Cmd 必须放入 Header 参与加密,遗漏任一头部字段都会校验不通过。 onlyGovPurchase 政企采购标签仅企业签约账号可生效,普通经销商应用筛选无效果。 pageSize 上限 50,超过阈值返回参数非法,批量采集建议 30 条每页,平衡同步速度与风控限制。goods.search.keyword,完整落地平台请求头 + 报文联合 HMAC-SHA256 签名、多维度货源筛选、自动分页循环、429 限流重试、参数前置校验,全程依托官方合规开放 API 开发,无网页逆向抓包逻辑,内容符合公开技术专栏发布标准。
区别通用电商仅业务参数签名,得力要求请求方法、网关路径、模块指令、毫秒时间戳、完整 JSON 报文共同参与加密,全网同类教程极少完整复现该规则,解决 90% 开发者 401 鉴权失败问题。
内置价格区间、最小起订量、分类编码、政企采购标签、库存筛选,适配文具、设备批量采购业务,零售类平台无对应筛选维度。
自动读取总商品数量循环遍历全部页面,空页、末页自动终止,无需外部手动维护页码循环,适配大批量货源同步场景。
精准区分签名错误、关键词非法、接口限流、商品无结果、网络超时五类异常,429 超限自动延长休眠,避免应用调用权限临时锁定。
统一提取阶梯批发价、装箱数量、最小起订量、政企采购标识,输出结构化数据可直接入库 ERP,无需二次数据加工。https://api.delicloud.com/v2/opengoods.search.keyword(关键词搜索稳定版)
运行import requests
import hmac
import hashlib
import time
import json
from typing import Dict, Optional
class DeliKeywordSearchClient:
def __init__(self, app_key: str, app_secret: str):
self.app_key = app_key
self.app_secret = app_secret
self.base_url = "https://api.delicloud.com/v2/open"
self.api_module = "goods"
self.api_cmd = "goods.search.keyword"
self.session = requests.Session()
def build_hmac_sign(self, body: Dict, timestamp: str) -> str:
"""得力官方Header+报文联合HMAC-SHA256标准签名"""
body_json = json.dumps(body, separators=(",", ":"), ensure_ascii=False)
sign_source = f"POST/v2/open{self.api_module}{self.api_cmd}{timestamp}{body_json}"
sign_obj = hmac.new(self.app_secret.encode("utf-8"), sign_source.encode("utf-8"), hashlib.sha256)
return sign_obj.hexdigest().lower()
def single_page_query(self, keyword: str, page: int = 1, page_size: int = 30,
min_price: Optional[int] = None, max_price: Optional[int] = None,
min_buy: Optional[int] = None, cat_code: str = "", only_gov: int = 0) -> Dict:
"""单页关键词搜索,支持集采多条件筛选"""
# 前置参数合法性校验
if len(keyword.strip()) < 1 or len(keyword) > 30:
return {"code": -1, "msg": "关键词长度1-30字符", "goods_list": []}
if page < 1 or not (10 <= page_size <= 50):
return {"code": -1, "msg": "页码≥1,每页条数10-50", "goods_list": []}
timestamp = str(int(time.time() * 1000))
body_params = {
"keyword": keyword,
"pageIndex": page,
"pageSize": page_size,
"onlyGovPurchase": only_gov
}
if min_price:
body_params["minPrice"] = min_price
if max_price:
body_params["maxPrice"] = max_price
if min_buy:
body_params["minBuyNum"] = min_buy
if cat_code:
body_params["categoryCode"] = cat_code
sign = self.build_hmac_sign(body_params, timestamp)
headers = {
"App-Key": self.app_key,
"App-Timestamp": timestamp,
"Api-Module": self.api_module,
"Api-Cmd": self.api_cmd,
"Sign": sign,
"Content-Type": "application/json;charset=utf-8"
}
try:
resp = self.session.post(self.base_url, headers=headers, json=body_params, timeout=15)
raw_data = resp.json()
# 限流429自动退避重试
if raw_data.get("code") == 429:
time.sleep(3)
return self.single_page_query(keyword, page, page_size, min_price, max_price, min_buy, cat_code, only_gov)
if raw_data.get("code") != 0:
return {"code": -1, "msg": raw_data.get("msg", "接口业务异常"), "goods_list": []}
data_body = raw_data.get("data", {})
goods_raw = data_body.get("goodsList", [])
goods_clean = []
# 办公商品结构化清洗
for item in goods_raw:
goods_clean.append({
"item_code": item.get("itemCode"),
"title": item.get("itemName"),
"category_name": item.get("categoryName"),
"min_buy": item.get("minBuyNum"),
"retail_price": float(item.get("salePrice", 0)),
"market_price": float(item.get("marketPrice", 0)),
"box_spec": item.get("boxNum", 0),
"total_stock": item.get("totalStock"),
"sales_volume": item.get("saleCount"),
"main_img": item.get("mainImage"),
"support_gov": bool(int(item.get("onlyGovPurchase", 0)))
})
time.sleep(1)
return {
"code": 200,
"total": data_body.get("total", 0),
"total_page": data_body.get("totalPage", 0),
"current_page": page,
"goods_list": goods_clean
}
except Exception as e:
return {"code": -2, "msg": f"网络请求异常:{str(e)}", "goods_list": []}
def get_all_search_goods(self, keyword: str, min_price=None, max_price=None, min_buy=None, cat_code="", only_gov=0):
"""自动循环拉取关键词全部货源数据"""
all_goods = []
curr_page = 1
while True:
page_res = self.single_page_query(keyword, curr_page, 30, min_price, max_price, min_buy, cat_code, only_gov)
if page_res["code"] != 200 or len(page_res["goods_list"]) == 0:
break
all_goods.extend(page_res["goods_list"])
if curr_page >= page_res["total_page"]:
break
curr_page += 1
return {"keyword": keyword, "total_matched": len(all_goods), "goods_all": all_goods}
# 调用示例
if __name__ == "__main__":
client = DeliKeywordSearchClient(
app_key="开发者后台App-Key",
app_secret="开发者后台App-Secret密钥"
)
# 搜索中性笔,价格5-50,起批≥10,仅展示政企采购商品
result = client.get_all_search_goods(keyword="中性笔", min_price=5, max_price=50, min_buy=10, only_gov=1)
print(json.dumps(result, ensure_ascii=False, indent=2))