VVIC(搜款网)是国内知名的服装服饰批发电商平台,专注于为商家提供时尚服饰的批发采购服务。VVICitem_get接口允许开发者通过商品 ID 获取平台上的商品详情信息,包括商品基本信息、价格、规格、库存、图片等关键数据,为服装电商从业者提供市场分析、竞品研究和采购决策的重要依据。
一、VVICitem_get 接口核心特性分析
1. 接口定位与核心价值
2. 接口权限与调用限制
3. 核心参数解析
必选参数
可选参数
二、签名生成与返回数据结构
1. 签名生成逻辑
2. 返回数据结构解析
三、Python 实现方案
import requests
import time
import hashlib
import json
import logging
import pandas as pd
import matplotlib.pyplot as plt
import re
from datetime import datetime
from typing import Dict, Optional, List, Tuple
# 配置日志
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
# 配置中文显示
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
plt.rcParams["axes.unicode_minus"] = False
class VVICItemDetail:
"""VVIC item_get接口封装类,用于根据ID获取和分析商品详情数据"""
def __init__(self, app_key: str, app_secret: str):
"""
初始化VVIC API客户端
:param app_key: 应用的app_key
:param app_secret: 应用的app_secret
"""
self.app_key = app_key
self.app_secret = app_secret
self.api_url = "https://api.vvic.com/rest/item/get"
# 频率控制
self.rate_limit = 30 # 默认基础权限,高级权限可修改为100次/分钟
self.call_timestamps = [] # 存储调用时间戳(毫秒级)
def set_rate_limit(self, limit: int) -> None:
"""设置调用频率限制(次/分钟)"""
if 30 <= limit <= 100:
self.rate_limit = limit
logging.info(f"已设置调用频率限制为 {limit} 次/分钟")
else:
logging.warning("频率限制必须在30-100之间,未修改")
def _generate_sign(self, params: Dict) -> str:
"""生成签名(MD5算法)"""
# 1. 按参数名ASCII升序排序
sorted_params = sorted(params.items(), key=lambda x: x[0])
# 2. 拼接为"key=value&key=value"格式
param_str = "&".join([f"{k}={v}" for k, v in sorted_params])
# 3. 添加secret
param_str += f"&secret={self.app_secret}"
# 4. MD5加密
md5 = hashlib.md5()
md5.update(param_str.encode('utf-8'))
return md5.hexdigest()
def _check_rate_limit(self) -> None:
"""检查并控制调用频率"""
current_time = time.time() * 1000 # 毫秒级
# 保留1分钟内的调用记录
self.call_timestamps = [t for t in self.call_timestamps if current_time - t < 60000]
# 若超过限制,计算需要等待的时间
if len(self.call_timestamps) >= self.rate_limit:
oldest_time = self.call_timestamps[0]
sleep_time = (60000 - (current_time - oldest_time)) / 1000 + 0.1 # 额外加0.1秒保险
logging.warning(f"调用频率超限,等待 {sleep_time:.1f} 秒")
time.sleep(sleep_time)
# 再次清理过期记录
self.call_timestamps = [t for t in self.call_timestamps if time.time()*1000 - t < 60000]
# 记录本次调用时间
self.call_timestamps.append(current_time)
def get_item_detail(self, item_id: str,
include_shop: bool = True,
include_spec: bool = True,
include_supply: bool = True,
fields: Optional[str] = None) -> Optional[Dict]:
"""
根据ID获取商品详情数据
:param item_id: 商品ID
:param include_shop: 是否包含店铺信息
:param include_spec: 是否包含规格详情
:param include_supply: 是否包含供货信息
:param fields: 指定返回字段,逗号分隔
:return: 商品详情数据
"""
# 构建基础参数
base_params = {
"app_key": self.app_key,
"timestamp": str(int(time.time() * 1000)), # 毫秒级时间戳
"format": "json",
"v": "1.0",
"item_id": item_id,
"include_shop": "true" if include_shop else "false",
"include_spec": "true" if include_spec else "false",
"include_supply": "true" if include_supply else "false"
}
# 添加指定字段参数
if fields:
base_params["fields"] = fields
# 生成签名
sign = self._generate_sign(base_params)
base_params["sign"] = sign
# 检查频率限制
self._check_rate_limit()
try:
# 发送请求
response = requests.get(self.api_url, params=base_params, timeout=10)
response.raise_for_status()
# 解析响应
result = response.json()
# 处理错误
if result.get("code") != 0:
logging.error(f"API调用错误: {result.get('msg')} (错误码: {result.get('code')})")
return None
# 提取结果
item_data = result.get("data", {})
if not item_data:
logging.warning("未获取到商品详情数据")
return None
logging.info(f"成功获取商品 {item_id} 的详情数据")
return item_data
except requests.exceptions.RequestException as e:
logging.error(f"请求异常: {str(e)}")
return None
except json.JSONDecodeError:
logging.error(f"响应解析失败: {response.text[:200]}...")
return None
def batch_get_item_details(self, item_ids: List[str],
include_shop: bool = True,
include_spec: bool = True,
include_supply: bool = True) -> Tuple[List[Dict], int]:
"""
批量获取多个商品的详情数据
:param item_ids: 商品ID列表
:param include_shop: 是否包含店铺信息
:param include_spec: 是否包含规格详情
:param include_supply: 是否包含供货信息
:return: 商品详情列表和成功获取的数量
"""
all_items = []
success_count = 0
for item_id in item_ids:
logging.info(f"正在获取商品 {item_id} 的详情...")
item_data = self.get_item_detail(
item_id=item_id,
include_shop=include_shop,
include_spec=include_spec,
include_supply=include_supply
)
if item_data:
all_items.append(item_data)
success_count += 1
logging.info(f"批量获取完成,共请求 {len(item_ids)} 个商品,成功获取 {success_count} 个")
return all_items, success_count
def analyze_item(self, item_data: Dict) -> Dict:
"""分析单个商品数据"""
if not item_data:
return {"error": "没有商品数据可分析"}
# 1. 价格分析
price_info = {
"wholesale_price": float(item_data.get("price", 0)),
"market_price": float(item_data.get("market_price", 0)),
"price_ratio": 0, # 批发价/市场价比例
"has_step_price": False,
"min_buy": int(item_data.get("min_buy", 1)),
"step_price_count": 0
}
# 计算批发价与市场价比例
if price_info["market_price"] > 0:
price_info["price_ratio"] = round(price_info["wholesale_price"] / price_info["market_price"], 2)
# 分析阶梯价格
step_prices = item_data.get("step_price", [])
if step_prices and isinstance(step_prices, list) and len(step_prices) > 0:
price_info["has_step_price"] = True
price_info["step_price_count"] = len(step_prices)
price_info["min_step_price"] = min([float(sp.get("price", 0)) for sp in step_prices])
price_info["max_step_quantity"] = max([int(sp.get("quantity", 0)) for sp in step_prices])
# 2. 库存与规格分析
spec_info = {
"total_stock": int(item_data.get("stock", 0)),
"spec_count": 0,
"color_count": 0,
"size_count": 0,
"has_stock_variation": False # 不同规格库存是否有差异
}
specs = item_data.get("specs", [])
if specs and isinstance(specs, list):
spec_info["spec_count"] = len(specs)
# 提取颜色和尺码数量
colors = set()
sizes = set()
stocks = []
for spec in specs:
attributes = spec.get("attributes", {})
if "颜色" in attributes:
colors.add(attributes["颜色"])
if "尺码" in attributes:
sizes.add(attributes["尺码"])
try:
stocks.append(int(spec.get("stock", 0)))
except (ValueError, TypeError):
continue
spec_info["color_count"] = len(colors)
spec_info["size_count"] = len(sizes)
# 检查库存是否有差异
if len(stocks) > 1 and len(set(stocks)) > 1:
spec_info["has_stock_variation"] = True
# 3. 销售表现分析
sales_info = {
"sales_30d": int(item_data.get("sales_30d", 0)),
"fav_count": int(item_data.get("fav_count", 0)),
"comment_count": int(item_data.get("comment_count", 0)),
"avg_daily_sales": 0
}
# 计算日均销量
if sales_info["sales_30d"] > 0:
sales_info["avg_daily_sales"] = round(sales_info["sales_30d"] / 30, 1)
# 4. 媒体资源分析
media_info = {
"main_image": item_data.get("main_image", ""),
"image_count": len(item_data.get("images", [])),
"has_detail_images": len(item_data.get("detail_images", [])) > 0,
"detail_image_count": len(item_data.get("detail_images", []))
}
# 5. 店铺分析
shop_info = {}
if "shop_info" in item_data:
shop = item_data["shop_info"]
shop_info = {
"shop_id": shop.get("shop_id"),
"shop_name": shop.get("shop_name"),
"credit_level": shop.get("credit_level"),
"goods_count": int(shop.get("goods_count", 0)),
"month_sales": int(shop.get("month_sales", 0)),
"rating": float(shop.get("rating", 0)),
"establish_time": shop.get("establish_time")
}
# 6. 供货分析
supply_info = {}
if "supply_info" in item_data:
supply = item_data["supply_info"]
supply_info = {
"delivery_time": supply.get("delivery_time"),
"min_order_amount": float(supply.get("min_order_amount", 0)),
"payment_methods": supply.get("payment_methods", []),
"shipping_methods": supply.get("shipping_methods", []),
"origin": supply.get("origin"),
"return_policy": supply.get("return_policy")
}
# 7. 属性分析
category_path = item_data.get("category", "").split(">")
attr_info = {
"category_level1": category_path[0] if len(category_path) > 0 else "",
"category_level2": category_path[1] if len(category_path) > 1 else "",
"category_level3": category_path[2] if len(category_path) > 2 else "",
"brand": item_data.get("brand", ""),
"material": "",
"style": ""
}
# 提取关键属性
attributes = item_data.get("attributes", [])
if attributes and isinstance(attributes, list):
for attr in attributes:
if attr.get("name") == "材质":
attr_info["material"] = attr.get("value", "")
elif attr.get("name") == "风格":
attr_info["style"] = attr.get("value", "")
return {
"item_id": item_data.get("item_id"),
"title": item_data.get("title"),
"price_analysis": price_info,
"spec_analysis": spec_info,
"sales_analysis": sales_info,
"media_analysis": media_info,
"shop_analysis": shop_info,
"supply_analysis": supply_info,
"attribute_analysis": attr_info,
"update_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
def compare_items(self, items_data: List[Dict]) -> Dict:
"""对比多个商品数据"""
if len(items_data) < 2:
return {"error": "至少需要2个商品进行对比"}
comparison = {
"total_items": len(items_data),
"price_comparison": [],
"sales_comparison": [],
"stock_comparison": [],
"spec_comparison": []
}
# 提取各商品关键指标用于对比
for item in items_data:
item_id = item.get("item_id")
title = item.get("title", "")[:30] # 截断长标题
# 价格对比
comparison["price_comparison"].append({
"item_id": item_id,
"title": title,
"wholesale_price": float(item.get("price", 0)),
"market_price": float(item.get("market_price", 0)),
"min_buy": int(item.get("min_buy", 1))
})
# 销量对比
comparison["sales_comparison"].append({
"item_id": item_id,
"title": title,
"sales_30d": int(item.get("sales_30d", 0)),
"avg_daily_sales": round(int(item.get("sales_30d", 0))/30, 1) if int(item.get("sales_30d", 0)) > 0 else 0,
"fav_count": int(item.get("fav_count", 0))
})
# 库存对比
comparison["stock_comparison"].append({
"item_id": item_id,
"title": title,
"total_stock": int(item.get("stock", 0)),
"spec_count": len(item.get("specs", []))
})
# 规格对比
specs = item.get("specs", [])
colors = set()
sizes = set()
for spec in specs:
attributes = spec.get("attributes", {})
if "颜色" in attributes:
colors.add(attributes["颜色"])
if "尺码" in attributes:
sizes.add(attributes["尺码"])
comparison["spec_comparison"].append({
"item_id": item_id,
"title": title,
"color_count": len(colors),
"size_count": len(sizes)
})
# 排序各对比项以便分析
comparison["price_comparison"].sort(key=lambda x: x["wholesale_price"])
comparison["sales_comparison"].sort(key=lambda x: x["sales_30d"], reverse=True)
comparison["stock_comparison"].sort(key=lambda x: x["total_stock"], reverse=True)
return comparison
def visualize_comparison(self, comparison: Dict, output_dir: str = ".") -> None:
"""可视化多个商品的对比结果"""
if "error" in comparison:
logging.warning(comparison["error"])
return
# 1. 价格对比条形图
if comparison["price_comparison"]:
plt.figure(figsize=(12, 6))
items = [item["title"] for item in comparison["price_comparison"]]
wholesale_prices = [item["wholesale_price"] for item in comparison["price_comparison"]]
market_prices = [item["market_price"] for item in comparison["price_comparison"]]
x = range(len(items))
width = 0.35
plt.bar([i - width/2 for i in x], wholesale_prices, width, label='批发价')
plt.bar([i + width/2 for i in x], market_prices, width, label='市场价')
plt.xlabel('商品')
plt.ylabel('价格 (元)')
plt.title('商品价格对比')
plt.xticks(x, items, rotation=45)
plt.legend()
plt.tight_layout()
plt.savefig(f"{output_dir}/price_comparison.png")
plt.close()
logging.info(f"价格对比图表已保存至 {output_dir}/price_comparison.png")
# 2. 销量对比条形图
if comparison["sales_comparison"]:
plt.figure(figsize=(12, 6))
items = [item["title"] for item in comparison["sales_comparison"]]
sales = [item["sales_30d"] for item in comparison["sales_comparison"]]
plt.bar(items, sales, color='orange')
plt.xlabel('商品')
plt.ylabel('30天销量')
plt.title('商品销量对比')
plt.xticks(rotation=45)
for i, v in enumerate(sales):
plt.text(i, v + 5, str(v), ha='center')
plt.tight_layout()
plt.savefig(f"{output_dir}/sales_comparison.png")
plt.close()
logging.info(f"销量对比图表已保存至 {output_dir}/sales_comparison.png")
# 3. 规格对比条形图
if comparison["spec_comparison"]:
plt.figure(figsize=(12, 6))
items = [item["title"] for item in comparison["spec_comparison"]]
color_counts = [item["color_count"] for item in comparison["spec_comparison"]]
size_counts = [item["size_count"] for item in comparison["spec_comparison"]]
x = range(len(items))
width = 0.35
plt.bar([i - width/2 for i in x], color_counts, width, label='颜色数量')
plt.bar([i + width/2 for i in x], size_counts, width, label='尺码数量')
plt.xlabel('商品')
plt.ylabel('数量')
plt.title('商品规格对比')
plt.xticks(x, items, rotation=45)
plt.legend()
plt.tight_layout()
plt.savefig(f"{output_dir}/spec_comparison.png")
plt.close()
logging.info(f"规格对比图表已保存至 {output_dir}/spec_comparison.png")
def export_to_excel(self, items_data: List[Dict], analyses: List[Dict], comparison: Optional[Dict],
filename: str) -> None:
"""导出商品数据到Excel"""
if not items_data and not analyses:
logging.warning("没有数据可导出")
return
try:
with pd.ExcelWriter(filename) as writer:
# 商品基本信息
if items_data:
basic_info = []
for item in items_data:
info = {
"商品ID": item.get("item_id"),
"标题": item.get("title"),
"类目": item.get("category"),
"品牌": item.get("brand"),
"批发价(元)": item.get("price"),
"市场价(元)": item.get("market_price"),
"起订量": item.get("min_buy"),
"30天销量": item.get("sales_30d"),
"收藏数": item.get("fav_count"),
"总库存": item.get("stock"),
"规格数量": len(item.get("specs", [])),
"图片数量": len(item.get("images", [])),
"店铺名称": item.get("shop_info", {}).get("shop_name")
}
basic_info.append(info)
df_basic = pd.DataFrame(basic_info)
df_basic.to_excel(writer, sheet_name='商品基本信息', index=False)
# 规格详情
for i, item in enumerate(items_data):
if item.get("specs"):
spec_details = []
for spec in item.get("specs", []):
details = {
"规格ID": spec.get("spec_id"),
"属性组合": ", ".join([f"{k}:{v}" for k, v in spec.get("attributes", {}).items()]),
"价格(元)": spec.get("price"),
"库存": spec.get("stock"),
"图片": spec.get("image")
}
spec_details.append(details)
df_sku = pd.DataFrame(spec_details)
df_sku.to_excel(writer, sheet_name=f'规格详情_{item.get("item_id")[:6]}', index=False)
# 分析结果
if analyses:
analysis_summary = []
for analysis in analyses:
summary = {
"商品ID": analysis.get("item_id"),
"标题": analysis.get("title"),
"批发价(元)": analysis["price_analysis"]["wholesale_price"],
"市场价(元)": analysis["price_analysis"]["market_price"],
"价格比": analysis["price_analysis"]["price_ratio"],
"起订量": analysis["price_analysis"]["min_buy"],
"是否有阶梯价": "是" if analysis["price_analysis"]["has_step_price"] else "否",
"总库存": analysis["spec_analysis"]["total_stock"],
"颜色数": analysis["spec_analysis"]["color_count"],
"尺码数": analysis["spec_analysis"]["size_count"],
"30天销量": analysis["sales_analysis"]["sales_30d"],
"日均销量": analysis["sales_analysis"]["avg_daily_sales"],
"收藏数": analysis["sales_analysis"]["fav_count"]
}
analysis_summary.append(summary)
df_analysis = pd.DataFrame(analysis_summary)
df_analysis.to_excel(writer, sheet_name='分析摘要', index=False)
# 对比结果
if comparison and "error" not in comparison:
df_price = pd.DataFrame(comparison["price_comparison"])
df_price.to_excel(writer, sheet_name='价格对比', index=False)
df_sales = pd.DataFrame(comparison["sales_comparison"])
df_sales.to_excel(writer, sheet_name='销量对比', index=False)
logging.info(f"数据已导出至 {filename}")
except Exception as e:
logging.error(f"导出Excel失败: {e}")
# 示例调用
if __name__ == "__main__":
# 替换为实际的参数(从VVIC开放平台获取)
APP_KEY = "your_app_key"
APP_SECRET = "your_app_secret"
ITEM_ID = "12345678" # 商品ID示例
# 多个商品ID用于批量获取和对比
MULTIPLE_ITEM_IDS = ["12345678", "87654321", "11223344"]
# 初始化API客户端
vvic_item = VVICItemDetail(APP_KEY, APP_SECRET)
# 若为高级权限,设置更高的频率限制
# vvic_item.set_rate_limit(100)
# 1. 获取单个商品详情
print("=== 获取单个商品详情 ===")
item_detail = vvic_item.get_item_detail(
item_id=ITEM_ID,
include_shop=True,
include_spec=True,
include_supply=True
)
if item_detail:
print(f"商品ID: {item_detail.get('item_id')}")
print(f"标题: {item_detail.get('title')[:50]}...")
print(f"类目: {item_detail.get('category')}")
print(f"批发价: ¥{item_detail.get('price')}")
print(f"市场价: ¥{item_detail.get('market_price')}")
print(f"起订量: {item_detail.get('min_buy')}")
print(f"30天销量: {item_detail.get('sales_30d')}")
print(f"总库存: {item_detail.get('stock')}")
print(f"规格数量: {len(item_detail.get('specs', []))}")
print(f"店铺名称: {item_detail.get('shop_info', {}).get('shop_name')}")
# 2. 分析商品详情
print("\n=== 商品详情分析 ===")
if item_detail:
analysis = vvic_item.analyze_item(item_detail)
print("价格分析:")
print(f" 批发价: ¥{analysis['price_analysis']['wholesale_price']}")
print(f" 市场价: ¥{analysis['price_analysis']['market_price']}")
print(f" 价格比(批发/市场): {analysis['price_analysis']['price_ratio']}")
print(f" 起订量: {analysis['price_analysis']['min_buy']}")
print(f" 是否有阶梯价: {'是' if analysis['price_analysis']['has_step_price'] else '否'}")
print("\n规格分析:")
print(f" 总库存: {analysis['spec_analysis']['total_stock']}")
print(f" 颜色数量: {analysis['spec_analysis']['color_count']}")
print(f" 尺码数量: {analysis['spec_analysis']['size_count']}")
print("\n销售分析:")
print(f" 30天销量: {analysis['sales_analysis']['sales_30d']}")
print(f" 日均销量: {analysis['sales_analysis']['avg_daily_sales']}")
print(f" 收藏数: {analysis['sales_analysis']['fav_count']}")
if analysis["shop_analysis"]:
print("\n店铺分析:")
print(f" 店铺名称: {analysis['shop_analysis']['shop_name']}")
print(f" 店铺评分: {analysis['shop_analysis']['rating']}")
print(f" 店铺商品数: {analysis['shop_analysis']['goods_count']}")
# 3. 批量获取多个商品详情并对比
print("\n=== 批量获取与商品对比 ===")
items, success_count = vvic_item.batch_get_item_details(
item_ids=MULTIPLE_ITEM_IDS
)
if items and len(items) >= 2:
# 分析每个商品
analyses = [vvic_item.analyze_item(item) for item in items]
# 对比商品
comparison = vvic_item.compare_items(items)
print("\n价格对比 (从低到高):")
for i, item in enumerate(comparison["price_comparison"][:3], 1):
print(f"{i}. {item['title']} - ¥{item['wholesale_price']} (起订量: {item['min_buy']})")
print("\n销量对比 (从高到低):")
for i, item in enumerate(comparison["sales_comparison"][:3], 1):
print(f"{i}. {item['title']} - 30天销量: {item['sales_30d']}")
print("\n规格对比:")
for i, item in enumerate(comparison["spec_comparison"][:3], 1):
print(f"{i}. {item['title']} - 颜色: {item['color_count']}种, 尺码: {item['size_count']}种")
# 4. 可视化对比结果
vvic_item.visualize_comparison(comparison)
# 5. 导出数据到Excel
vvic_item.export_to_excel(items, analyses, comparison, "VVIC商品详情分析.xlsx")
elif len(items) == 1:
print("获取的商品数量不足,无法进行对比分析")
else:
print("未获取到足够的商品数据")