唯品会商品数据API调用教程:签名、分页、去重一篇文章讲透

简介: 本文详解唯品会商品API调用三大核心难点:**复合商品ID(店铺ID-商品ID)识别、单页上限120条+排序抖动下的稳健分页、多链接同款的三层去重(num_iid→sku_id→标题语义相似)**,附可运行Python签名实现与完整命令行工具,助你一次打通数据链路。

唯品会商品数据API调用教程:签名、分页、去重一篇文章讲透

唯品会的数据有三处和淘宝、1688 不一样,踩过一次就忘不掉:

  1. 商品 ID 是复合键:1710613157-6918711233889249157,前半是店铺/品牌 ID,后半才是商品 ID,少一段就查不到;
  2. 搜索单页最多 120 条,翻页期间排序还会抖,同一件商品会在第 2 页、第 3 页反复出现;
  3. 同一个款会有多个链接(不同店铺、不同活动),按标题判重几乎必错。

所以这篇文章不铺概念,就讲三件事:签名怎么拼、分页怎么翻、去重怎么做。每段都给可运行的 Python。


一、先选路径:官方 VOP、聚合接口、还是页面接口

路径 地址 鉴权 适合谁
官方开放平台 VOP https://vop.vipapis.com appKey + appSecret + hmac-md5 签名 + 授权 商家/服务商,要订单、库存、发货
聚合接口(本文主线) https://api-gw.onebound.cn/vip/ key + secret(免签名) 选品、比价、价格监控、竞品分析
页面接口(mapi) mapi.vip.com/... 页面端 sign(JS 生成) 不建议:随时变更,且合规风险高

两条路线并不冲突:读数据用聚合接口最省事,一旦要接自己的商家后台,就得回到 VOP 走签名。所以下面两套都给你——签名那节讲官方,分页和去重两节讲聚合接口。


二、签名:官方 hmac-md5,90% 的教程都拼错

2.1 聚合接口为什么“免签名”

万邦的 vip.item_get / vip.item_search 只需要把 key、secret 拼在 URL 上,服务端据此鉴权,不需要额外算 sign:

https://api-gw.onebound.cn/vip/item_get/?key=xxx&secret=yyy&num_iid=1710613157-6918711233889249157

省事,但也意味着别把 key 写进前端代码——它相当于账号密码。

2.2 官方 VOP 的签名规则

VOP 的系统级参数一共 7 个,除 appSecret 外全部参与签名:

参数 说明
service 服务名,如 vipapis.address.AddressService
method 方法名,如 getFullAddress
version 版本号,如 1.0.0
timestamp 10 位秒级时间戳,与服务器偏差不能超过 10 分钟
format json / xml
appKey 应用标识
sign 算出来的签名,32 位大写十六进制

算法三步,注意第二步的拼接方式:

  1. 把除 appSecret、sign 外的系统参数按参数名 ASCII 升序排列;
  2. 按「参数名 + 参数值」直接相连(不是 k=v&k=v),末尾再拼上业务参数的 JSON 串;
  3. 以 appSecret 为密钥做 hmac-md5,结果转大写。

官方文档给的样例,拼出来是这样一串(注意中间没有任何分隔符):

appKeyyourappKeyformatjsonmethodgetFullAddressservicevipapis.address.AddressServicetimestamp1406851200version1.0.0{"area_code":"0","is_show_gat":"SHOW_GAT","is_bind":false}

2.3 可运行实现(附官方样例自检)

import hashlib
import hmac
import json
import time

# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
def vop_sign(app_key, app_secret, service, method, version="1.0.0",
             timestamp=None, fmt="json", biz=None):
    """返回 (sign, 待签名原文)。sign 为 32 位大写十六进制。"""
    ts = str(timestamp if timestamp is not None else int(time.time()))
    sys_params = {
   "appKey": app_key, "format": fmt, "method": method,
                  "service": service, "timestamp": ts, "version": version}

    # ① 按参数名排序,② 名字和值直接拼,无分隔符
    raw = "".join(k + str(v) for k, v in sorted(sys_params.items()))

    # ③ 业务参数用紧凑 JSON 追加在最后(不参与排序)
    if biz:
        raw += json.dumps(biz, separators=(",", ":"), ensure_ascii=False)

    sign = hmac.new(app_secret.encode(), raw.encode(), hashlib.md5).hexdigest().upper()
    return sign, raw


if __name__ == "__main__":
    sign, raw = vop_sign(
        "yourappKey", "yourappSecret",
        "vipapis.address.AddressService", "getFullAddress", "1.0.0", 1406851200,
        biz={
   "area_code": "0", "is_show_gat": "SHOW_GAT", "is_bind": False},
    )
    print(raw)
    print(sign)

跑出来的结果:

2880112276AB2FB2187DABA140B4DACC

与官方文档样例完全一致——建议你接 VOP 前先用这段自检,签名对了再谈业务,能省掉一半“401 无权限”的排查时间。

2.4 四个必踩的坑

坑 现象 正解
拼成 k=v&k=v 签名校验失败 名字和值直接相连
把业务参数也排序了 校验失败 业务参数 JSON 整体拼在最后,内部顺序保持原样
JSON 带空格 / False 校验失败 separators=(",", ":"),布尔用 false
16 位时间戳 / 小写结果 校验失败 10 位秒级;hexdigest().upper()

此外,appSecret 只出现在签名计算里,永远不要放进请求参数。


三、商品详情:num_iid 是复合键

请求

GET https://api-gw.onebound.cn/vip/item_get/?key=&secret=&num_iid=1710613157-6918711233889249157
参数 必填 说明
num_iid 是 店铺ID-商品ID,中间用 - 连接
cache 否 yes 默认走缓存;对账/比价传 no
lang 否 cn / en / ru
result_type 否 json / jsonu(中文不转义,调试好用)

num_iid 怎么来

唯品会详情页地址是 https://detail.vip.com/detail-{brandId}-{productId}.html,detail- 后面整段就是 num_iid:

import re

def parse_num_iid(text: str) -> str:
    m = re.search(r"detail-(\d+)-(\d+)", text)          # 链接
    if m:
        return f"{m.group(1)}-{m.group(2)}"
    m = re.search(r"(\d+)-(\d+)", text)                  # 已经是 num_iid
    return f"{m.group(1)}-{m.group(2)}" if m else text.strip()

返回里值得重点取的字段

字段 含义 备注
title 标题 常带「【2件起售】」这类前缀,入库前建议清洗
price / orginal_price 特卖价 / 专柜价 注意官方字段名把 original 拼成了 orginal,别自己“纠正”
num 总库存 为 0 时回落到 SKU 库存求和
delist_time 下架时间(毫秒时间戳) 判断是否在售,比 status 靠谱
skus.sku[] 规格列表 含 sku_id、properties_name、price、quantity
props_list 属性键值对 {"134:0": "颜色:黑色9000"}
item_imgs[].url / desc_img[] 主图 / 详情长图 两处都要取,详情图不在 item_imgs 里
seller_info.shopname / shop_type 店铺名 / 店铺类型 区分「唯品自营」和第三方
brand / brandId 品牌名 / 品牌 ID 做品牌维度聚合的天然分组键

一个很实用的细节:delist_time 是未来时间戳 = 还在卖,是过去时间 = 已下架。做价格监控时先过这一层,能砍掉大量无效详情请求。


四、分页:page 从 1 开始,但别信返回值

搜索请求

GET https://api-gw.onebound.cn/vip/item_search/?key=&secret=&q=运动鞋&page=1&page_size=120&sort=_sale
参数 说明
q 关键词
cat 类目 ID,做垂直品类采集时比关键词干净
start_price / end_price 价格区间,深翻突破的关键
sort bid / _bid(总价)、bid2 / _bid2(商品价)、_sale(销量)、_credit(信用),_ 前缀表示从大到小
page 页码,从 1 开始
page_size 单页条数,上限 120

返回结构

{
   
  "items": {
   
    "page": 1,
    "page_size": 120,
    "total_results": "1761",
    "real_total_results": "1761",
    "item": [
      {
   
        "num_iid": "3693373-2170802633",
        "title": "【2件起售】踩屎感凉拖鞋女夏季居家...",
        "price": "22.9",
        "promotion_price": "8.9",
        "pic_url": "http://h2.appsimg.com/...jpg",
        "detail_url": "https://detail.vip.com/detail-3693373-2170802633.html",
        "sales": 0
      }
    ]
  }
}

4.1 三个终止条件,缺一不可

if not items:                     return      # ① 空页:到头了(或翻过深翻上限)
if total and len(seen) >= total:  return      # ② 累计去重数已达 total
if len(items) < page_size:        return      # ③ 不满一页:最后一页

为什么不能信返回的 page:它是上游透传值,缺省时会返回 0(文档示例就是 "page": 0),你写 if resp.page >= pages: break 会第一页就退出。

total_results 和 real_total_results 的区别:前者是当前可翻页的结果数(可能被截断),后者是真实命中数。分页终止用 total_results,做数据量预估用 real_total_results。

4.2 深翻:唯品会的翻页有天花板

任何电商搜索都只放前 N 页(几十页量级),翻过头就是空页。想要更多数据,正确做法是把查询拆小,而不是硬翻:

# 按价格区间切片,每段 100 元,规避深翻上限
for lo in range(0, 1000, 100):
    items = search(q="运动鞋", start_price=lo, end_price=lo + 100, page_size=120)

同理还可以叠加 cat(类目)、sort(换排序会拿到不同的结果集)。切片维度越多,重复率越高——这就必须靠正文第五节去重。

4.3 翻页为什么会重复

  • 排序权重实时变化:_sale 按销量排,翻页那几秒销量变了,商品就跳到另一页;
  • 缓存不一致:不同页命中不同时间的快照;
  • 同一 q 下商品有多个活动链接。

对策:固定排序 + 记录已见 + 记录 total 快照。用变化小的排序(如 bid2 价格排序)比 _sale 稳定得多。


五、去重:三层,少一层数据就脏

5.1 第一层:商品级(num_iid)

最有效、也最容易被做错的一层。必须保留完整的复合键:

# ❌ 只取后半段,会把不同店铺的同名商品误合并
pid = num_iid.split("-")[-1]

# ✅ 原样保留
key = num_iid

跨页/跨关键词去重只需要一个集合:

seen = set()
for it in page_items:
    if it["num_iid"] in seen:
        continue
    seen.add(it["num_iid"])
    yield it

5.2 第二层:SKU 级(sku_id)

详情接口偶尔会返回重复的规格组合(同一 sku_id 出现两次),直接入库会触发唯一键冲突,或者让库存翻倍:

skus, seen_sku = [], set()
for s in raw_skus:
    sid = str(s.get("sku_id") or "")
    if not sid or sid in seen_sku:
        continue
    seen_sku.add(sid)
    skus.append(s)

5.3 第三层:内容级(同款不同链接)

不同店铺卖同一个款、或者同一商品挂着不同活动链接,num_iid 完全不同,集合去重无能为力。用「品牌分桶 + 标题 n-gram 相似度」兜底:

import re

PROMO = re.compile(r"[【】\[\]()()]|包邮|正品|新款|特卖|专柜|限时|秒杀|清仓|直播|[0-9]+折")

def norm_title(t: str) -> str:
    t = PROMO.sub("", t or "")
    t = re.sub(r"[^\u4e00-\u9fa5a-zA-Z0-9]", "", t)
    return t.lower()

def ngrams(s: str, n: int = 3) -> set:
    return {
   s[i:i + n] for i in range(max(len(s) - n + 1, 1))}

def similar(a: str, b: str, threshold: float = 0.75) -> bool:
    ga, gb = ngrams(norm_title(a)), ngrams(norm_title(b))
    if not ga or not gb:
        return False
    return len(ga & gb) / len(ga | gb) >= threshold

用法:先按 brand + 类目 分桶,桶内两两比较(桶控制小了,复杂度就下来了)。数据量再大就换 SimHash 或 MinHash,但别只对标题做哈希——差一个字就漏,比不去重还糟。

5.4 落库必须幂等

去重的最后一道关在数据库。两个唯一键 + upsert,重复跑任务也不会产生脏数据:

CREATE TABLE IF NOT EXISTS vip_item (
    num_iid        TEXT PRIMARY KEY,   -- 商品级去重
    title          TEXT,
    price          REAL,
    original_price REAL,
    stock          INTEGER,
    sales          INTEGER,
    brand          TEXT,
    shop_name      TEXT,
    updated_at     INTEGER
);
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
CREATE TABLE IF NOT EXISTS vip_sku (
    sku_id  TEXT PRIMARY KEY,          -- SKU 级去重
    num_iid TEXT NOT NULL,
    spec    TEXT,
    price   REAL,
    stock   INTEGER
);
CREATE INDEX IF NOT EXISTS idx_sku_item ON vip_sku(num_iid);

INSERT INTO vip_item (num_iid, title, price, stock, updated_at)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(num_iid) DO UPDATE SET
    title=excluded.title, price=excluded.price,
    stock=excluded.stock, updated_at=excluded.updated_at;

配一句统计,你就知道去重到底有没有效果:

new, updated = store.new_items, store.seen_items
total = new + updated
print(f"新增 {new} 条,更新 {updated} 条,重复率 {updated / total:.1%}")

正常情况:首次全量新增率高(>90%),二次增量应该几乎全是更新。如果二次同步还大量“新增”,说明键选错了——大概率是把 num_iid 拆开用了。


六、完整源码与运行方式

文末的 vip_api.py 把上面三件事串成了一个可直接用的命令行工具,纯标准库、无第三方依赖:

export ONEBOUND_KEY=你的key
export ONEBOUND_SECRET=你的secret

# 1) 自检签名实现(输出与官方样例一致)
python vip_api.py sign

# 2) 搜索 + 分页 + 去重 + 落库 + 导出
python vip_api.py search "运动鞋" --pages 5 --sort _sale --min-price 100 --max-price 300 \
       --db vip.db --csv items.csv

# 3) 查详情(含 SKU 去重)
python vip_api.py detail 1710613157-6918711233889249157 --db vip.db

search 命令的中间输出会把每一页的账算清楚:

[page 1] 返回 120 条,新 118 条,页内重复 2 条,累计 118/1761
[page 2] 返回 120 条,新 111 条,页内重复 9 条,累计 229/1761
...
共 1180 条(已按 num_iid 去重)
入库:新增 1103,更新 77;库内合计 1103 商品

(数字为示意。真实运行时,页内重复条数就是你判断“排序抖不抖”的温度计。)

脚本能力清单:

能力 实现
官方签名 vop_sign(),附官方样例自检 sign 子命令
分页 iter_search(),三重终止条件 + 固定间隔 --delay
商品去重 内存 seen 集合(单次任务内)
SKU 去重 按 sku_id 过滤重复规格
持久去重 SQLite 双唯一键 + ON CONFLICT DO UPDATE
容错 只对 4001/4002/4017/4000/5000 重试,鉴权类直接退出
导出 CSV(UTF-8 BOM,Excel 不乱码)
数据新鲜度 默认 cache=no,对账/比价不会被旧缓存坑

七、上线前的八条清单

  1. num_iid 全程保留 店铺ID-商品ID 的复合格式,不要 split;
  2. 签名先跑官方样例自检,再排查业务权限;
  3. 时间戳用 10 位秒级,机器上装 NTP,别让服务器慢 10 分钟;
  1. page 从 1 开始,终止条件用「空页 / 累计数 / 不满页」三条,不看返回的 page;
  2. 深翻超限就按价格区间、类目切片,别硬翻;
  3. 去重三层全上:num_iid → sku_id → 标题相似度;
  4. 落库用唯一键 + upsert,任务可重复执行才是好任务;
  5. 监控两个指标:去重率和新增率,异常波动先查键,再查上游。

结语

签名的难点在“拼接没有分隔符”,分页的难点在“别信返回值、深翻要切片”,去重的难点在“商品 ID 是复合键、同款不同链接”。

这三件事做对之后,唯品会的数据管道就稳了。剩下的价格监控、竞品分析、选品打分,都只是在干净数据上加一层业务逻辑——而干净,永远是第一层。

相关文章
|
13天前
|
Linux 开发工具 iOS开发
Codex保姆级教程(下载+安装+登录+使用,含所有版本,2026最新)
Codex是OpenAI推出的开源AI编程智能体,本地运行,可理解整个代码库,支持自然语言指令改代码、执行命令、跑测试、修Bug,兼容Win/macOS/Linux。(239字)
5549 41
|
14天前
|
JavaScript API iOS开发
DeepSeek Harness下载、安装、配置一篇搞定(2026整理,送常用插件)
DeepSeek Harness(DSH)是DeepSeek于2026年开源的MIT协议AI Agent框架,支持插件化组装模型、工具、沙箱与UI,兼容主流大模型。本文提供免Node.js的桌面端安装包(Win/macOS),含详细图文教程及热门插件推荐。
4816 58
|
8天前
|
Linux 开发工具 iOS开发
2026最新codex下载安装保姆级使用教程(下载+安装+登录+使用,含所有历史版本)
本文详解OpenAI Codex国内快速上手指南:提供Win/macOS/Linux全平台免安装包、npm/Homebrew等多方式安装、账号/API Key双登录、简体中文切换,覆盖下载、安装、汉化、使用及高频避坑(如微软商店失败、npm超时等),10分钟即可跑通。
2105 6
|
14天前
|
iOS开发 Docker MacOS
【2026实测】Docker Desktop下载、安装、汉化、配置教程(附官网安装包)
Docker Desktop 是 Docker 官方推出的桌面容器管理工具,集成引擎、CLI、图形界面与 Kubernetes,支持 Win/macOS/Linux。2026 最新版 4.88.1 强化 ARM 原生运行、镜像加速及资源可视化,附中文汉化与国内镜像配置指南。
2291 7
|
6天前
|
人工智能 安全 API
DeepSeek Harness下载、安装、配置一篇搞定(2026整理,送常用插件)
DeepSeek Harness(dsh)是DeepSeek AI于2026年开源的轻量级Agent运行框架,基于Cordis内核,践行“一切皆插件”理念。支持Web/桌面双端,开箱即用;内置多模型接入、丰富插件生态(超3.5万),适配编程、办公、创作等场景。(239字)
1305 0
|
8天前
|
云安全 人工智能 安全
|
1天前
|
云安全 人工智能 安全
|
7天前
|
人工智能 并行计算 调度
本地 AI 一键部署工具 LocModel:Windows 端集成画图、视频生成、Agent 的开源模型运行方案
LocModel 是专为 Windows 设计的一站式本地 AI 工具,支持零配置启动、自动硬件检测、GGUF 模型一键加载、文生图/视频生成及 Agent 自动化任务。适配 8G 显存,内置显存分片调度,轻松实现 AI 漫剧批量创作与离线大模型调用。(239 字)