×

当当开放平台关键词商品搜索接口实战:专属MD5签名+图书字段解析+风控分页查询(Python合规生产版)

Ace Ace 发表于2026-07-15 17:51:14 浏览7 评论0

抢沙发发表评论

前言
图书电商ERP、线上书店货源同步、图书比价监控、文教类内容素材筛选系统开发中,通过关键词获取当当平台商品列表是核心需求。目前网络现有教程多为简易爬虫脚本,未遵循平台官方鉴权规范,普遍存在签名错误、缺失图书专属字段、无分页闭环、风控拦截无防护等问题,无法适配生产环境。本文基于当当开放平台官方商品搜索接口,完整落地平台专属MD5签名机制,封装图书核心字段解析、风控重试、全自动分页遍历,全程合规调用官方接口,无逆向破解行为,符合技术专栏发布标准。


一、本文差异化核心亮点
1. 当当专属MD5签名完整落地:区别网上无鉴权爬虫脚本,严格遵循平台“参数拼接+密钥加密+版本校验”规则,解决401鉴权失败问题,适配企业级调用。
2. 图书专属字段结构化解析:独家解析图书开本、ISBN编码、作者、出版社、出版时间、印次等核心字段,适配图书电商分析场景,区别普通电商接口。
3. 分级风控异常处理:精准识别签名错误、无匹配商品、接口限流、网络超时四类异常,429限流自动休眠重试,避免IP封禁和权限冻结。
4.全自动分页闭环遍历:根据接口返回总页数自动循环拉取全量商品,末页自动终止,无需手动维护页码,适配批量货源同步场景。


二、接口基础规范
接口地址:https://api.dangdang.com/v2/searchItems.php
请求方式:POST 表单请求,携带专属MD5签名
鉴权规则:参数ASCII升序拼接,结合AppSecret进行MD5加密,签名与AppKey、版本号共同校验
核心参数:keyword(搜索关键词)、page(页码)、page_size(每页条数)、sort(排序方式)
调用约束:企业开发者QPS≤2,请求间隔≥1秒,签名需实时生成,版本号固定为v2.0


点击获取key和secret
三、完整可运行Python生产代码

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

class DangdangSearchClient:
    def __init__(self, app_key, app_secret):
        self.app_key = app_key
        self.app_secret = app_secret
        self.base_url = "https://api.dangdang.com/v2/searchItems.php"
        self.version = "v2.0"
        self.session = requests.Session()

    def build_dangdang_sign(self, params):
        """当当开放平台专属MD5签名生成"""
        valid_params = {k: v for k, v in params.items() if v}
        sorted_params = sorted(valid_params.items(), key=lambda x: x[0])
        sign_str = self.app_secret
        for k, v in sorted_params:
            sign_str += f"{k}{quote(str(v), safe='')}"
        sign_str += self.app_secret
        return hashlib.md5(sign_str.encode("utf-8")).hexdigest().upper()

    def single_page_search(self, keyword, page=1, page_size=20, sort="sort_default"):
        """单页关键词商品搜索,支持排序筛选"""
        if len(keyword) < 1 or len(keyword) > 30:
            return {"code": -1, "msg": "关键词长度1-30字符", "goods_list": []}
        timestamp = str(int(time.time()))
        params = {
            "app_key": self.app_key,
            "version": self.version,
            "keyword": keyword,
            "page": page,
            "page_size": page_size,
            "sort": sort,
            "timestamp": timestamp
        }
        params["sign"] = self.build_dangdang_sign(params)
        headers = {"Content-Type": "application/x-www-form-urlencoded;charset=utf-8"}
        try:
            resp = self.session.post(self.base_url, data=params, headers=headers, timeout=15)
            raw_data = resp.json()
            # 限流自动重试
            if raw_data.get("code") == 429:
                time.sleep(2)
                return self.single_page_search(keyword, page, page_size, sort)
            if raw_data.get("code") != 0:
                return {"code": -1, "msg": raw_data.get("msg", "查询失败"), "goods_list": []}
            data = raw_data.get("data", {})
            goods_raw = data.get("items", [])
            goods_clean = []
            # 图书数据结构化清洗
            for item in goods_raw:
                goods_clean.append({
                    "goods_id": item.get("itemId"),
                    "title": item.get("itemName"),
                    "author": item.get("author", ""),
                    "publisher": item.get("publisher", ""),
                    "isbn": item.get("isbn", ""),
                    "publish_time": item.get("publishTime", ""),
                    "format": item.get("format", ""),  # 图书开本
                    "price": float(item.get("price", 0)),
                    "market_price": float(item.get("marketPrice", 0)),
                    "stock": item.get("stock", 0),
                    "sales": item.get("sales", 0)
                })
            time.sleep(1)
            return {
                "code": 200,
                "total": data.get("total", 0),
                "total_page": data.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_goods(self, keyword, sort="sort_default"):
        """全自动分页拉取全部商品"""
        all_goods = []
        page = 1
        while True:
            res = self.single_page_search(keyword, page, 20, sort)
            if res["code"] != 200 or not res["goods_list"]:
                break
            all_goods.extend(res["goods_list"])
            if page >= res["total_page"]:
                break
            page += 1
        return {"keyword": keyword, "total": len(all_goods), "data": all_goods}

# 调用示例
if __name__ == "__main__":
    client = DangdangSearchClient(
        app_key="你的开放平台app_key",
        app_secret="你的开放平台app_secret"
    )
    # 搜索Python相关图书,按销量排序
    result = client.get_all_goods("Python编程", sort="sort_sales")
    print(json.dumps(result, ensure_ascii=False, indent=2))


四、实战核心避坑要点
1. 签名必须实时生成,且参数需ASCII升序拼接,使用固定签名或参数顺序错误会直接触发401鉴权失败,这是当当接口最易踩坑点。
sort排序参数需使用平台指定值,支持sort_default(综合)、sort_sales(销量)、sort_price(价格),自定义值会返回参数异常。
ISBN、开本等图书专属字段仅图书品类返回,其他品类无相关数据,开发时需做非空判断。
严格控制请求间隔≥1秒,高频并发会触发临时限流,严重时会冻结应用调用权限。

群贤毕至

访客