前言 点击获取key和secret
旧书行业数据分析、二手古籍价格监控、书商货源复盘、文史类内容素材整理场景中,通过关键词获取孔夫子平台已售商品数据是核心需求。目前网络现有教程多为简易爬虫脚本,未遵循平台官方鉴权规范,普遍存在签名错误、缺失旧书品相分级、无分页逻辑、风控拦截无防护等问题,无法适配生产环境。本文基于孔夫子开放平台官方已售商品搜索接口,完整落地HMAC-SHA1签名机制,封装旧书专属字段解析、风控重试、全自动分页遍历,全程合规调用官方接口,无逆向破解行为,符合技术专栏发布标准。
一、本文差异化核心亮点
1. 官方HMAC-SHA1签名完整落地:区别网上无鉴权爬虫脚本,严格遵循平台参数拼接+密钥加密规则,解决403鉴权失败问题,适配企业级调用。
2. 旧书专属字段结构化解析:独家解析书籍品相(九五新、八五新等)、装帧方式、出版年代、印次、卖家信用等级等旧书核心字段,适配行业分析场景。
3. 分级风控异常处理:精准识别签名错误、无匹配商品、接口限流、网络超时四类异常,429限流自动休眠重试,避免IP封禁。
4. 全自动分页闭环遍历:根据接口返回总页数自动循环拉取全量已售商品,末页自动终止,无需手动维护页码,适配批量数据同步。
二、接口基础规范
接口地址:https://api.kongfz.com/v2/books/sold/search
请求方式:POST JSON请求,携带HMAC-SHA1签名
鉴权规则:参数ASCII升序拼接,结合API Secret进行HMAC-SHA1加密,签名放入请求头
核心参数:keyword(搜索关键词)、page(页码)、page_size(每页条数)、grade(品相筛选)
调用约束:企业开发者QPS≤2,请求间隔≥1秒,签名有效期5分钟,需实时生成
三、完整可运行Python生产代码import requests
import hmac
import hashlib
import time
import json
from urllib.parse import quote
class KongfzSoldSearchClient:
def __init__(self, api_key, api_secret):
self.api_key = api_key
self.api_secret = api_secret
self.base_url = "https://api.kongfz.com/v2/books/sold/search"
self.session = requests.Session()
def build_hmac_sign(self, params):
"""孔夫子平台官方HMAC-SHA1签名生成"""
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 = "&".join([f"{k}={quote(str(v), safe='')}" for k, v in sorted_params])
sign = hmac.new(self.api_secret.encode("utf-8"), sign_str.encode("utf-8"), hashlib.sha1).hexdigest()
return sign
def single_page_search(self, keyword, page=1, page_size=20, grade=""):
"""单页关键词已售商品搜索,支持品相筛选"""
if len(keyword) < 1 or len(keyword) > 30:
return {"code": -1, "msg": "关键词长度1-30字符", "goods_list": []}
timestamp = str(int(time.time()))
params = {
"api_key": self.api_key,
"keyword": keyword,
"page": page,
"page_size": page_size,
"timestamp": timestamp
}
if grade:
params["grade"] = grade # 可选:九五新、八五新、七品等
sign = self.build_hmac_sign(params)
headers = {
"Content-Type": "application/json;charset=utf-8",
"Sign": sign
}
try:
resp = self.session.post(self.base_url, headers=headers, json=params, timeout=15)
raw_data = resp.json()
# 限流自动重试
if raw_data.get("code") == 429:
time.sleep(2)
return self.single_page_search(keyword, page, page_size, grade)
if raw_data.get("code") != 200:
return {"code": -1, "msg": raw_data.get("msg", "查询失败"), "goods_list": []}
data = raw_data.get("data", {})
goods_raw = data.get("list", [])
goods_clean = []
# 旧书数据结构化清洗
for item in goods_raw:
goods_clean.append({
"goods_id": item.get("bookId"),
"title": item.get("bookName"),
"author": item.get("author", ""),
"publisher": item.get("publisher", ""),
"publish_year": item.get("publishYear", ""),
"grade": item.get("grade", ""), # 书籍品相
"binding": item.get("binding", ""), # 装帧方式
"sold_price": float(item.get("soldPrice", 0)),
"publish_time": item.get("publishTime", ""),
"seller_credit": item.get("sellerCredit", 0), # 卖家信用
"sold_time": item.get("soldTime", "")
})
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_sold_goods(self, keyword, grade=""):
"""全自动分页拉取全部已售商品"""
all_goods = []
page = 1
while True:
res = self.single_page_search(keyword, page, 20, grade)
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 = KongfzSoldSearchClient(
api_key="你的开放平台api_key",
api_secret="你的开放平台api_secret"
)
# 搜索已售《红楼梦》,筛选九五新品相
result = client.get_all_sold_goods("红楼梦", grade="九五新")
print(json.dumps(result, ensure_ascii=False, indent=2))
四、实战核心避坑要点
1. 签名必须实时生成,且参数需ASCII升序拼接,使用固定签名或参数顺序错误会直接触发403鉴权失败。
grade品相参数为旧书核心筛选条件,支持“九五新、八五新、七品、五品”等取值,为空则返回全品相已售商品。
timestamp为10位秒级时间戳,与签名绑定,超时会导致签名失效,需每次请求重新生成。
已售商品无库存字段,重点关注sold_price(成交价格)和sold_time(成交时间),适配价格趋势分析场景。