在电商开发这行闯荡多年,对接过不少平台的 API,微店商品详情 API 的对接经历,至今想起来还历历在目。看似简单的接口调用,却藏着不少特有的 “门道”,从授权方式到数据结构都和其他平台不太一样。今天就把这些年踩过的坑、攒下的实战代码掏出来,给同样在微店开发路上摸索的朋友指个路。
第一次对接微店 API 时,光授权环节就卡了我两天。微店采用的是 OAuth2.0 授权,和淘宝那种 AppKey+Secret 的方式完全不同,需要先引导用户授权获取 access_token,而且这个 token 有效期只有 2 小时,还得处理刷新机制。当时对着文档反复调试,才搞明白完整的授权流程,最后封装出这个工具函数:
python数据实例 [获取测试]
{ "item": { "num_iid": "2749499386", "title": "Swarovski施华洛世奇LIFELONG BOW 流苏蝴蝶结浪漫妩媚女款项链饰品礼物5447082", "desc_short": "Swarovski施华洛世奇LIFELONG BOW 流苏蝴蝶结浪漫妩媚女款项链饰品礼物5447082", "price": 45, "total_price": 0, "suggestive_price": 0, "orginal_price": "45", "nick": "达薇妮珠宝", "num": "", "min_num": 0, "detail_url": "https://weidian.com/item.html?itemID=2749499386", "pic_url": "https://si.geilicdn.com/pcitem1288244399-3b300000016a3fd26d6f0a217216_800_800.jpg", "brand": "", "brandId": "", "rootCatId": "", "cid": "", "favcount": "", "fanscount": "", "crumbs": [], "created_time": "", "modified_time": "", "delist_time": "", "desc": "<img src="https://si.geilicdn.com/pcitem1288244399-23910000016a40086f230a217216_790_420.jpg"/><img src="https://si.geilicdn.com/pcitem1288244399-25490000016a40086f650a217252_790_402.jpg"/><img src="https://si.geilicdn.com/pcitem1288244399-064c0000016a400870600a21924b_790_503.jpg"/><img src="https://si.geilicdn.com/pcitem1288244399-61250000016a400870900a219248_790_308.jpg"/><img src="https://si.geilicdn.com/pcitem1288244399-0e370000016a400871960a21924a_790_600.jpg"/><img src="https://si.geilicdn.com/pcitem1288244399-25730000016a400874400a217252_790_600.jpg"/><img src="https://si.geilicdn.com/pcitem1288244399-61470000016a400874790a219248_790_600.jpg"/><img src="https://si.geilicdn.com/pcitem1288244399-26c00000016a400875df0a217205_790_600.jpg"/><img src="https://si.geilicdn.com/pcitem1288244399-34850000016a400876a90a20b7b9_790_834.jpg"/><img src="https://si.geilicdn.com/pcitem1288244399-23c00000016a4008754e0a217216_790_379.jpg"/><img src="https://si.geilicdn.com/pcitem1288244399-06800000016a400876500a21924b_790_524.jpg"/><img src="https://si.geilicdn.com/pcitem1288244399-25840000016a400875e90a217252_790_427.jpg"/>", "desc_img": [ "https://si.geilicdn.com/pcitem1288244399-23910000016a40086f230a217216_790_420.jpg", "https://si.geilicdn.com/pcitem1288244399-25490000016a40086f650a217252_790_402.jpg", "https://si.geilicdn.com/pcitem1288244399-064c0000016a400870600a21924b_790_503.jpg", "https://si.geilicdn.com/pcitem1288244399-61250000016a400870900a219248_790_308.jpg", "https://si.geilicdn.com/pcitem1288244399-0e370000016a400871960a21924a_790_600.jpg", "https://si.geilicdn.com/pcitem1288244399-25730000016a400874400a217252_790_600.jpg", "https://si.geilicdn.com/pcitem1288244399-61470000016a400874790a219248_790_600.jpg", "https://si.geilicdn.com/pcitem1288244399-26c00000016a400875df0a217205_790_600.jpg", "https://si.geilicdn.com/pcitem1288244399-34850000016a400876a90a20b7b9_790_834.jpg", "https://si.geilicdn.com/pcitem1288244399-23c00000016a4008754e0a217216_790_379.jpg", "https://si.geilicdn.com/pcitem1288244399-06800000016a400876500a21924b_790_524.jpg", "https://si.geilicdn.com/pcitem1288244399-25840000016a400875e90a217252_790_427.jpg"
python
运行
import requests import time def get_access_token(client_id, client_secret, redirect_uri, code): """获取微店access_token""" url = "https://openapi.weidian.com/token" params = { "grant_type": "authorization_code", "client_id": client_id, "client_secret": client_secret, "redirect_uri": redirect_uri, "code": code } response = requests.get(url, params=params) data = response.json() if data.get("errcode") == 0: # 存储token和过期时间 return { "access_token": data["access_token"], "expires_in": data["expires_in"], "refresh_token": data["refresh_token"], "expire_time": time.time() + data["expires_in"] } else: raise Exception(f"授权失败: {data.get('errmsg')}") def refresh_access_token(client_id, client_secret, refresh_token): """刷新access_token""" url = "https://openapi.weidian.com/token" params = { "grant_type": "refresh_token", "client_id": client_id, "client_secret": client_secret, "refresh_token": refresh_token } response = requests.get(url, params=params) data = response.json() if data.get("errcode") == 0: return { "access_token": data["access_token"], "expires_in": data["expires_in"], "refresh_token": data["refresh_token"], "expire_time": time.time() + data["expires_in"] } else: raise Exception(f"刷新token失败: {data.get('errmsg')}")
解决了授权问题,调用商品详情接口时又遇到新麻烦。微店的商品数据结构特别 “随性”,不同类型的商品返回字段差异很大。比如实物商品有 “weight” 重量字段,虚拟商品却没有;普通商品的价格在 “price” 字段,而限时折扣商品则藏在 “promotion” 对象里。有次做商品聚合平台,因为没处理这些差异,导致虚拟商品详情页直接报错。后来专门写了个兼容不同商品类型的解析函数:
python
运行
def parse_weidian_product(data): """解析微店商品数据,兼容不同商品类型""" product = data.get("product", {}) result = { "product_id": product.get("product_id"), "title": product.get("title"), "main_image": product.get("main_img"), "sales_count": product.get("sales_count", 0) } # 处理价格:优先取促销价 promotion = product.get("promotion", {}) if promotion.get("is_valid"): result["price"] = float(promotion.get("promotion_price", 0)) result["original_price"] = float(product.get("price", 0)) else: result["price"] = float(product.get("price", 0)) result["original_price"] = float(product.get("price", 0)) # 处理库存:实物商品和虚拟商品字段不同 if product.get("product_type") == 1: # 实物商品 result["stock"] = int(product.get("stock_num", 0)) elif product.get("product_type") == 2: # 虚拟商品 result["stock"] = int(product.get("virtual_stock_num", 0)) else: result["stock"] = 0 return result
微店 API 的调用频率限制也很有特点,它不是按分钟或小时限制,而是采用 “滑动窗口” 机制,比如 10 秒内最多调用 5 次。有次批量获取商品列表时没控制好节奏,连续收到429
错误,导致部分商品数据缺失。后来用滑动窗口算法实现了精准限流:
python
运行
import time from collections import deque class SlidingWindowLimiter: def __init__(self, max_calls, window_seconds): self.max_calls = max_calls self.window = window_seconds self.calls = deque() def allow_request(self): now = time.time() # 移除窗口外的请求记录 while self.calls and now - self.calls[0] > self.window: self.calls.popleft() # 判断是否允许新请求 if len(self.calls) < self.max_calls: self.calls.append(now) return True return False # 使用示例 limiter = SlidingWindowLimiter(max_calls=5, window_seconds=10) # 10秒内最多5次请求 if limiter.allow_request(): # 调用API response = requests.get(api_url) else: # 等待窗口刷新 sleep_time = self.window - (time.time() - self.calls[0]) time.sleep(sleep_time + 0.1)
最让我印象深刻的是一次接口突然变更。微店在某个版本更新中,把商品详情接口的 “category_id” 字段改成了 “cate_id”,而且没在更新日志里说明。那天早上一上班就收到大量报错,排查了半天才发现是字段变了。从那以后,每次解析数据都会加一层容错处理:
python
运行
def safe_get(data, keys, default=None): """安全获取嵌套字典中的字段""" for key in keys: if isinstance(data, dict) and key in data: data = data[key] else: return default return data # 示例:兼容新旧字段 category_id = safe_get(product, ["category_id"]) or safe_get(product, ["cate_id"], 0)
这些年和微店 API 打交道,最大的感受就是它的灵活性 —— 既支持个人店铺也支持企业店铺,数据结构会根据店铺类型动态调整。开发时一定要多考虑边界情况,尤其是不同商品类型、不同店铺等级的兼容性处理。