×

义乌购关键词商品搜索接口实战:双签名兼容 + 批发多维度筛选 + 分页限流防护(Python 合规生产版)

Ace Ace 发表于2026-06-25 17:35:01 浏览10 评论0

抢沙发发表评论

前言

小商品 ERP 选品、跨境货源铺货、线下档口价格巡检场景下,关键词批量搜索是货源采集首要入口。网上现有义乌购接口教程普遍存在明显缺陷:仅实现单一 MD5 签名无法适配 2024 年后新密钥、缺失批发专属筛选条件、分页无自动闭环、无限流熔断机制、未解析线下实体商位与混批门槛字段,且大量使用第三方中转 API 而非平台原生网关。本文基于义乌购官方原生search/goods搜索接口,封装MD5/SHA1 双算法自适应签名、多维度批发筛选、全自动分页遍历、限流指数退避、时差校验,全程依托官方开放 API 开发,无爬虫逆向逻辑。

一、本文差异化核心亮点


  1. 双加密算法兼容:自动识别新旧 AppSecret,老密钥 MD5、2024-10 新密钥 SHA1,解决 90% 开发者签名 401 鉴权失败问题,市面教程极少完整实现兼容逻辑。

  2. 小商品批发专属筛选体系:内置价格区间、最小起批量、线下市场、销量排序筛选,贴合义乌源头批发业务,区别零售平台简易搜索代码。

  3. 全自动分页闭环:读取总商品数循环遍历所有页面,空页自动终止,无需外部维护页码循环,适配批量货源采集。

  4. 严苛限流防护机制:适配免费版 1 次 / 秒、企业版 10 次 / 秒双档位限速,捕获 429 超限自动延长休眠,规避 IP 封禁 8 小时风险。

  5. 批发字段结构化清洗:单独提取实体商位、混批最低件数、库存预警阈值等义乌独有字段,直接适配 ERP 入库逻辑。


二、接口基础规范


  • 原生接口地址:https://api.yiwugo.com/search/goods

  • 请求方式:GET 参数拼接

  • 公共必填参数:app_key、keyword、timestamp(秒级时间戳)、sign 签名

  • 签名规则:参数 ASCII 升序、过滤空值、中文 URL 编码,新旧密钥区分加密算法

  • 调用限制:个人开发者 QPS≤1,企业签约 QPS≤10,超限直接锁定 IP8 小时

  • 权限要求:义乌购开放平台企业实名认证,开通商品搜索读取权限

点击获取key和secret

三、完整可运行 Python 生产代码

python

运行

import requests
import hashlib
import time
import json
from urllib.parse import quote

class YiWuKeywordSearchClient:
    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/search/goods"
        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_raw_str = "&".join(sign_parts) + f"&secret={self.app_secret}"
        if self.is_new_key:
            sign_res = hashlib.sha1(sign_raw_str.encode("utf-8"))
        else:
            sign_res = hashlib.md5(sign_raw_str.encode("utf-8"))
        return sign_res.hexdigest().upper()

    def single_page_search(self, keyword, page=1, page_size=30, min_price=None, max_price=None, min_buy=None, sort="sales_desc"):
        """单页关键词搜索,支持批发多条件筛选"""
        timestamp = int(time.time())
        base_params = {
            "app_key": self.app_key,
            "keyword": keyword,
            "timestamp": timestamp,
            "page": page,
            "page_size": min(page_size, 50),
            "sort": sort
        }
        if min_price:
            base_params["min_price"] = min_price
        if max_price:
            base_params["max_price"] = max_price
        if min_buy:
            base_params["min_buy"] = min_buy
        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.single_page_search(keyword, page, page_size, min_price, max_price, min_buy, sort)
            if raw_data.get("code") != 200:
                return {"code": -1, "msg": raw_data.get("msg", "接口调用失败"), "list": []}
            data_body = raw_data.get("data", {})
            goods_raw = data_body.get("goods_list", [])
            goods_clean = []
            # 批发商品标准化清洗
            for item in goods_raw:
                goods_clean.append({
                    "goods_id": item.get("goods_id"),
                    "title": item.get("title"),
                    "market_shop": item.get("market_shop", ""),
                    "wholesale_price": item.get("price"),
                    "min_buy": item.get("min_buy"),
                    "stock": item.get("stock"),
                    "warning_stock": item.get("warning_stock", 0),
                    "sales_volume": item.get("sales"),
                    "main_img": item.get("main_image"),
                    "supplier_name": item.get("supplier_name")
                })
            # 免费版强制1秒间隔,规避风控
            time.sleep(1)
            return {
                "code": 200,
                "total": data_body.get("total", 0),
                "total_page": data_body.get("total_page", 0),
                "current_page": page,
                "goods_list": goods_clean
            }
        except Exception as e:
            return {"code": -2, "msg": f"网络异常:{str(e)}", "list": []}

    def get_all_keyword_goods(self, keyword, min_price=None, max_price=None, min_buy=None, sort="sales_desc"):
        """自动循环拉取关键词全部货源,无需手动分页"""
        all_goods = []
        current_page = 1
        while True:
            page_result = self.single_page_search(keyword, current_page, 30, min_price, max_price, min_buy, sort)
            if page_result["code"] != 200 or len(page_result["goods_list"]) == 0:
                break
            all_goods.extend(page_result["goods_list"])
            if current_page >= page_result["total_page"]:
                break
            current_page += 1
        return {"keyword": keyword, "total_matched": len(all_goods), "goods_all": all_goods}

# 调用示例
if __name__ == "__main__":
    client = YiWuKeywordSearchClient(
        app_key="开发者后台申请的AppKey",
        app_secret="开发者后台密钥AppSecret",
        is_new_key=True
    )
    # 搜索饰品,价格5-50元,起批≥10件,按销量排序
    result = client.get_all_keyword_goods(keyword="饰品", min_price=5, max_price=50, min_buy=10)
    print(json.dumps(result, ensure_ascii=False, indent=2))

四、实战原创避坑要点


  1. 时间戳必须秒级,传入毫秒值会触发服务器时差拦截,直接返回签名无效,多数简易教程未做限制。

  2. 新老密钥加密算法不可混用,2024 年 10 月后创建应用强制 SHA1,老应用使用 MD5,代码自动区分适配。

  3. market_shop线下商位为义乌独有字段,用于线下实地看样,零售电商平台无该返回值。

  4. warning_stock库存预警字段不可忽略,库存低于预警值商家不再接单,入库时需增加过滤判断。

  5. 免费版调用间隔最低 1 秒,无缓冲重试机制,高频采集会直接锁定 IP8 小时无法访问接口。

群贤毕至

访客