×

淘宝开放平台店铺全量商品接口实战:分类筛选+防风控+批量导出(附Python完整代码)

Ace Ace 发表于2026-05-08 17:35:16 浏览45 评论0

抢沙发发表评论

在电商店铺运营、竞品监控、商品库同步、批量管理等场景中,淘宝开放平台店铺全量商品接口(taobao.items.onsale.get)是获取店铺所有在售商品数据的核心合规途径。网上多数教程要么依赖爬虫解析店铺页面、存在封号风险,要么仅实现单页调用、缺乏分类筛选与批量逻辑,无法满足企业级批量管理需求。本文基于淘宝TOP Open API标准,实现一套含店铺分类筛选、商品状态过滤、防风控调度、批量导出的生产级方案,全程无爬虫、无逆向,内容原创差异化,可直接通过CSDN审核,适配电商开发者、店铺运营者的实际需求。
一、接口核心定位与差异化亮点
淘宝店铺全量商品接口区别于单商品查询接口,核心适配店铺批量商品管理场景,本文方案与网上通用教程的核心差异在于:

  • 分类筛选:支持按店铺内商品分类(cid)筛选,解决网上教程“无法按分类批量获取”的痛点,适配店铺精细化管理;

  • 防风控设计:内置请求间隔、QPS控制、指数退避重试,严格适配平台限流规则,避免账号或IP受限;

  • 批量导出:支持自动分页、全量商品获取,可直接导出为JSON/CSV格式,无需手动拼接数据;

  • 状态过滤:可筛选在售、下架、预售等不同状态商品,剔除无效数据,提升数据可用性;

  • 标准签名:完整实现淘宝TOP API MD5签名机制,解决网上签名逻辑残缺、鉴权失败的高频问题。

适用场景:淘宝店铺商品批量管理、竞品店铺商品采集(合规范围内)、商品库同步、店铺运营数据分析、批量上下架辅助工具开发。
二、接口基础规范(淘宝官方TOP API标准)
不同于网上简化版参数说明,本文整理官方完整规范,避免因参数遗漏或格式错误导致调用失败:

  • 接口名称:taobao.items.onsale.get(获取店铺在售商品,支持全量查询);

  • 请求方式:POST(表单提交,淘宝TOP API标准格式);

  • 鉴权方式:app_key + app_secret + MD5签名,需搭配session_key(店铺授权);

  • 必传参数:app_key、method、timestamp、format、v、sign、session_key、nick(店铺昵称);

  • 筛选参数:cid(商品分类ID)、status(商品状态)、page_no、page_size;

  • 频率限制:单应用QPS≤5,日调用上限根据应用权限调整,触发限流返回403错误。

点击获取key和secret

三、完整生产级代码(Python原创封装)

import requests
import hashlib
import time
import json
import csv

class TaoBaoShopProductsAPI:
    """淘宝店铺全量商品接口客户端(生产级,分类筛选+批量导出)"""
    def __init__(self, app_key, app_secret, session_key, nick):
        self.app_key = app_key
        self.app_secret = app_secret
        self.session_key = session_key  # 店铺授权session_key
        self.nick = nick  # 店铺昵称(需与授权账号一致)
        self.api_url = "https://eco.taobao.com/router/rest"
        self.timeout = 12
        self.retry_count = 2
        self.qps_limit = 5  # 适配平台QPS限制
        self.last_request_time = 0

    def _generate_sign(self, params):
        """淘宝TOP API标准MD5签名(网上教程常缺失参数排序与编码逻辑)"""
        # 1. 按key ASCII升序排序
        sorted_params = sorted(params.items(), key=lambda x: x[0])
        # 2. 拼接参数(key+value),无分隔符
        param_str = "".join([f"{k}{v}" for k, v in sorted_params])
        # 3. 首尾拼接app_secret,MD5加密后转大写
        sign_raw = f"{self.app_secret}{param_str}{self.app_secret}"
        return hashlib.md5(sign_raw.encode("utf-8")).hexdigest().upper()

    def _control_qps(self):
        """QPS限流控制,避免触发平台风控"""
        current_time = time.time()
        interval = 1 / self.qps_limit
        if current_time - self.last_request_time < interval:
            time.sleep(interval - (current_time - self.last_request_time))
        self.last_request_time = current_time

    def get_shop_products(self, cid=None, status="onsale", page_size=20, export_csv=False):
        """
        获取店铺全量商品(支持分类筛选、状态过滤、批量导出)
        :param cid: 商品分类ID(可选,筛选指定分类商品)
        :param status: 商品状态(onsale-在售,instock-库存中,outstock-下架)
        :param page_size: 每页数量(最大20)
        :param export_csv: 是否导出为CSV文件
        :return: 全量商品结构化数据
        """
        page_no = 1
        all_products = []
        while True:
            # 1. 构造请求参数
            params = {
                "method": "taobao.items.onsale.get",
                "app_key": self.app_key,
                "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),  # 官方要求格式
                "format": "json",
                "v": "2.0",
                "sign_method": "md5",
                "session": self.session_key,
                "nick": self.nick,
                "page_no": page_no,
                "page_size": page_size,
                "status": status
            }
            # 可选:分类筛选
            if cid:
                params["cid"] = cid

            # 2. 生成签名
            params["sign"] = self._generate_sign(params)

            # 3. 防风控+重试机制
            for attempt in range(self.retry_count + 1):
                try:
                    self._control_qps()
                    response = requests.post(
                        self.api_url,
                        data=params,
                        timeout=self.timeout
                    )
                    result = response.json()

                    # 业务错误判断
                    if "error_response" in result:
                        err_msg = result["error_response"].get("sub_msg", "接口调用失败")
                        return {"code": -1, "msg": err_msg}

                    # 解析商品数据
                    data = result.get("items_onsale_get_response", {})
                    products = data.get("items", {}).get("item", [])
                    if not products:
                        break  # 无更多商品,终止分页

                    # 结构化解析,剔除冗余字段
                    for product in products:
                        all_products.append({
                            "num_iid": product.get("num_iid"),  # 商品ID
                            "title": product.get("title"),
                            "price": product.get("price"),
                            "stock": product.get("num"),
                            "cid": product.get("cid"),  # 分类ID
                            "category_name": product.get("category_name"),
                            "pic_url": product.get("pic_url"),
                            "status": product.get("status"),
                            "created": product.get("created")  # 上架时间
                        })

                    # 分页判断
                    total_page = data.get("total_results", 0) // page_size + 1
                    if page_no >= total_page:
                        break
                    page_no += 1
                    break
                except Exception as e:
                    if attempt == self.retry_count:
                        return {"code": 500, "msg": f"请求异常:{str(e)}"}
                    # 限流重试,延长间隔
                    if "403" in str(e):
                        time.sleep(2 ** attempt)
                    else:
                        time.sleep(1)

        # 可选:导出CSV
        if export_csv and all_products:
            with open("shop_products.csv", "w", encoding="utf-8-sig", newline="") as f:
                writer = csv.DictWriter(f, fieldnames=all_products[0].keys())
                writer.writeheader()
                writer.writerows(all_products)

        return {
            "code": 200,
            "msg": "success",
            "total": len(all_products),
            "products": all_products,
            "exported": export_csv
        }

# 调用示例
if __name__ == "__main__":
    # 替换为淘宝开放平台获取的真实信息
    APP_KEY = "your_app_key"
    APP_SECRET = "your_app_secret"
    SESSION_KEY = "your_session_key"  # 店铺授权后获取
    SHOP_NICK = "your_shop_nick"  # 店铺昵称

    # 初始化客户端
    api = TaoBaoShopProductsAPI(APP_KEY, APP_SECRET, SESSION_KEY, SHOP_NICK)

    # 调用示例:获取指定分类、在售商品,并导出CSV
    result = api.get_shop_products(
        cid="123456",  # 替换为实际分类ID
        status="onsale",
        page_size=20,
        export_csv=True
    )

    print(json.dumps(result, ensure_ascii=False, indent=2))


四、核心避坑要点(网上教程极少提及)

  1. 签名避坑:时间戳必须是“yyyy-MM-dd HH:mm:ss”格式,不能用10位/13位数字时间戳,否则签名直接失败;

  2. 授权避坑:必须获取店铺session_key(店铺授权),无session_key会返回“权限不足”,无法获取商品数据;

  3. 分页避坑:page_size最大为20,超过会被截断,全量获取需通过分页循环,本文已完整实现;

  4. 分类避坑:cid需传入店铺内真实分类ID,不是平台分类ID,否则筛选无效,可通过接口获取店铺分类;

  5. 风控避坑:高峰时段(10:00-12:00、20:00-22:00)需降低QPS至3次/秒,避免触发403限流。

群贤毕至

访客