Shopee 的 item_search_shop
接口是获取指定店铺所有商品信息的核心接口,能够返回店铺内的商品列表、基本信息、价格、库存、销量等关键数据。对于店铺运营分析、竞品监控、市场调研等场景具有重要价值,尤其在跨境电商环境中,能帮助卖家了解竞争对手的商品布局和定价策略。
一、接口核心特性分析
- 接口功能与定位
-
核心功能:获取指定店铺的所有商品信息,支持分页和筛选
-
数据特点:
- 包含商品基础信息(ID、名称、图片、描述)
- 提供价格信息(原价、折扣价、促销价)
- 包含库存、销量、评分等运营数据
- 支持按分类、价格区间、上架时间等筛选
- 包含商品变体(颜色、尺寸等)信息
-
应用场景:
- 竞品店铺商品结构分析
- 价格策略对比研究
- 热销商品识别与分析
- 店铺运营策略研究
- 市场趋势与需求分析
- 认证机制
Shopee 开放平台的 item_search_shop
接口采用以下认证方式:
- 基于
partner_id
和shopid
的身份验证 - 通过
access_token
进行授权访问 - 所有请求需要包含
sign
签名参数确保安全性 - 签名基于
partner_key
、请求路径、时间戳和访问令牌生成
- 核心参数与响应结构
请求参数
参数名 | 类型 | 是否必填 | 说明 |
---|---|---|---|
shopid |
Integer | 是 | 店铺 ID |
page |
Integer | 否 | 页码,默认 1 |
page_size |
Integer | 否 | 每页条数,默认 20,最大 100 |
category_id |
Integer | 否 | 店铺内分类 ID,筛选特定分类商品 |
sort_by |
String | 否 | 排序方式:ctime (上架时间)、price (价格)、sales (销量) |
order_by |
String | 否 | 排序顺序:asc (升序)、desc (降序) |
price_min |
Float | 否 | 最低价格筛选 |
price_max |
Float | 否 | 最高价格筛选 |
partner_id |
Integer | 是 | 合作伙伴 ID |
access_token |
String | 是 | 访问令牌 |
timestamp |
Long | 是 | 时间戳 (毫秒) |
sign |
String | 是 | 请求签名 |
region |
String | 否 | 地区代码,如 sg, my, th 等 |
响应核心字段
-
分页信息:总商品数、总页数、当前页码
-
店铺信息:店铺名称、评分、粉丝数
-
商品列表:每个商品包含
- 基础信息:商品 ID、名称、描述、主图 URL
- 价格信息:原价、折扣价、货币单位
- 库存信息:总库存、已售数量
- 变体信息:变体 ID、属性(颜色 / 尺寸)、价格、库存
- 分类信息:店铺内分类 ID 和名称
- 评分信息:评分星级、评价数量
- 状态信息:是否在售、是否促销
二、Python 脚本实现
以下是调用 Shopee item_search_shop
接口的完整 Python 实现,包含签名生成、请求处理、数据解析和深度分析功能: import requests import time import json import logging import hashlib import hmac import os import re from typing import Dict, Optional, List, Tuple from requests.exceptions import RequestException import matplotlib.pyplot as plt import numpy as np import pandas as pd from collections import defaultdict
配置日志
logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" )
class ShopeeItemSearchShopAPI: def init(self, partner_id: int, partner_key: str, region: str = "sg"): """ 初始化Shopee店铺商品接口客户端 :param partner_id: 合作伙伴ID :param partner_key: 合作伙伴密钥 :param region: 地区代码,如sg(新加坡), my(马来西亚), th(泰国), tw(台湾) """ self.partner_id = partner_id self.partner_key = partner_key.encode('utf-8') # 用于签名的密钥 self.region = region self.base_url = self._get_base_url() self.access_token = None self.session = requests.Session() self.session.headers.update({ "Content-Type": "application/json", "Accept": "application/json", "User-Agent": "ShopeeAPI/1.0.0 (Python)" })
python
def _get_base_url(self) -> str:
"""根据地区获取基础URL"""
region_map = {
"sg": "https://partner.shopeemobile.com", # 新加坡
"my": "https://partner.shopeemobile.com", # 马来西亚
"th": "https://partner.shopeemobile.com", # 泰国
"tw": "https://partner.shopee.tw", # 台湾
"id": "https://partner.shopeemobile.com", # 印度尼西亚
"vn": "https://partner.shopeemobile.com", # 越南
"ph": "https://partner.shopeemobile.com" # 菲律宾
}
return region_map.get(self.region, region_map["sg"])
def _generate_signature(self, path: str, timestamp: int, access_token: str = "") -> str:
"""生成请求签名"""
# 构建待签名字符串
base_string = f"{self.partner_id}{path}{timestamp}"
if access_token:
base_string += access_token
# 使用HMAC-SHA256计算签名
signature = hmac.new(
self.partner_key,
base_string.encode('utf-8'),
hashlib.sha256
).hexdigest()
return signature
def set_access_token(self, access_token: str) -> None:
"""设置访问令牌"""
self.access_token = access_token
def search_shop_items(self,
shopid: int,
page: int = 1,
page_size: int = 20,
category_id: Optional[int] = None,
sort_by: str = "ctime",
order_by: str = "desc",
price_min: Optional[float] = None,
price_max: Optional[float] = None) -> Optional[Dict]:
"""
获取店铺商品列表
:param shopid: 店铺ID
:param page: 页码
:param page_size: 每页条数
:param category_id: 店铺内分类ID
:param sort_by: 排序方式:ctime(上架时间)、price(价格)、sales(销量)
:param order_by: 排序顺序:asc(升序)、desc(降序)
:param price_min: 最低价格筛选
:param price_max: 最高价格筛选
:return: 商品列表数据
"""
# 验证参数
if page_size < 1 or page_size > 100:
logging.error(f"每页条数必须在1-100之间,当前为: {page_size}")
return None
valid_sort_by = ["ctime", "price", "sales"]
if sort_by not in valid_sort_by:
logging.error(f"无效的排序方式: {sort_by},支持: {valid_sort_by}")
return None
valid_order_by = ["asc", "desc"]
if order_by not in valid_order_by:
logging.error(f"无效的排序顺序: {order_by},支持: {valid_order_by}")
return None
# 检查access_token
if not self.access_token:
logging.error("请先设置access_token")
return None
# 构建请求路径和参数
path = "/api/v2/item/search_shop"
timestamp = int(time.time() * 1000) # 毫秒时间戳
signature = self._generate_signature(path, timestamp, self.access_token)
params = {
"partner_id": self.partner_id,
"shopid": shopid,
"page": page,
"page_size": page_size,
"sort_by": sort_by,
"order_by": order_by,
"timestamp": timestamp,
"access_token": self.access_token,
"sign": signature
}
# 添加可选参数
if category_id is not None:
params["category_id"] = category_id
if price_min is not None:
params["price_min"] = price_min
if price_max is not None:
params["price_max"] = price_max
try:
url = f"{self.base_url}{path}"
response = self.session.get(url, params=params, timeout=15)
response.raise_for_status()
result = response.json()
# 检查响应状态
if result.get("error") != 0:
logging.error(f"获取店铺商品失败: {result.get('message', '未知错误')} (错误码: {result.get('error')})")
return None
# 格式化商品数据
return self._format_item_data(result, shopid)
except RequestException as e:
logging.error(f"获取店铺商品请求异常: {str(e)}")
return None
except json.JSONDecodeError:
logging.error(f"商品响应解析失败: {response.text[:200]}...")
return None
def _format_item_data(self, item_data: Dict, shopid: int) -> Dict:
"""格式化商品数据"""
# 分页信息
pagination = {
"total_items": item_data.get("data", {}).get("total_count", 0),
"total_pages": (item_data.get("data", {}).get("total_count", 0) +
item_data.get("data", {}).get("page_size", 20) - 1) //
item_data.get("data", {}).get("page_size", 20),
"current_page": item_data.get("data", {}).get("page", 1),
"page_size": item_data.get("data", {}).get("page_size", 20)
}
# 店铺信息
shop_info = {
"shopid": shopid,
"shop_name": item_data.get("data", {}).get("shop_name", ""),
"rating": item_data.get("data", {}).get("shop_rating", 0),
"follower_count": item_data.get("data", {}).get("follower_count", 0),
"item_count": item_data.get("data", {}).get("total_count", 0)
}
# 店铺分类信息
categories = []
for cat in item_data.get("data", {}).get("categories", []):
categories.append({
"category_id": cat.get("category_id"),
"name": cat.get("name"),
"item_count": cat.get("item_count", 0)
})
# 格式化商品列表
items = []
for item in item_data.get("data", {}).get("items", []):
# 处理价格信息(Shopee返回的价格单位为分,需转换为元)
price = item.get("price", 0) / 100000
original_price = item.get("original_price", 0) / 100000
discount = 0
if original_price > 0 and price < original_price:
discount = round((1 - price / original_price) * 100, 1)
# 处理库存和销量
stock = item.get("stock", 0)
sold = item.get("sold", 0)
# 处理变体信息
variants = []
if item.get("variants"):
for variant in item.get("variants"):
variants.append({
"variant_id": variant.get("variant_id"),
"attributes": [{"name": attr.get("name"), "value": attr.get("value")}
for attr in variant.get("attributes", [])],
"price": variant.get("price", 0) / 100000,
"stock": variant.get("stock", 0),
"image_url": variant.get("image_url", "")
})
# 处理分类信息
category_info = {
"category_id": item.get("category_id"),
"category_name": self._get_category_name(categories, item.get("category_id"))
}
items.append({
"item_id": item.get("item_id"),
"shopid": shopid,
"name": item.get("name", ""),
"description": item.get("description", "")[:200], # 截取部分描述
"image_url": item.get("image", ""),
"images": item.get("images", []),
"price": price,
"original_price": original_price,
"discount": discount,
"currency": item.get("currency", "SGD"),
"stock": stock,
"sold": sold,
"rating": item.get("item_rating", {}).get("rating_star", 0),
"rating_count": item.get("item_rating", {}).get("rating_count", 0),
"categories": category_info,
"variants": variants,
"is_on_sale": item.get("is_on_sale", False),
"is_promotion": item.get("is_promotion", False),
"created_time": item.get("ctime", 0),
"updated_time": item.get("mtime", 0),
"url": f"https://shopee.{self.region}/product/{shopid}/{item.get('item_id')}"
})
return {
"pagination": pagination,
"shop_info": shop_info,
"categories": categories,
"items": items,
"raw_data": item_data # 保留原始数据
}
def _get_category_name(self, categories: List[Dict], category_id: int) -> str:
"""根据分类ID获取分类名称"""
for cat in categories:
if cat["category_id"] == category_id:
return cat["name"]
return ""
def get_all_shop_items(self, shopid: int, max_pages: int = 20, **kwargs) -> Dict:
"""
获取店铺的所有商品
:param shopid: 店铺ID
:param max_pages: 最大页数限制
:return: 包含所有商品的完整数据
"""
all_items = []
page = 1
shop_info = None
categories = None
while page <= max_pages:
logging.info(f"获取店铺商品第 {page} 页")
result = self.search_shop_items(
shopid=shopid,
page=page,
page_size=100, # 使用最大页大小
**kwargs
)
if not result or not result["items"]:
break
# 保存店铺信息和分类信息
if not shop_info:
shop_info = result["shop_info"]
if not categories:
categories = result["categories"]
all_items.extend(result["items"])
# 检查是否已到最后一页
if page >= result["pagination"]["total_pages"]:
break
page += 1
# 控制请求频率,遵守Shopee API的QPS限制
time.sleep(0.1)
logging.info(f"共获取到 {len(all_items)} 件商品")
return {
"shop_info": shop_info,
"categories": categories,
"total_items": len(all_items),
"items": all_items
}
def analyze_shop_items(self, shop_data: Dict) -> Dict:
"""分析店铺商品数据"""
if not shop_data or not shop_data["items"]:
return {}
items = shop_data["items"]
total = len(items)
# 价格分析
price_analysis = self._analyze_prices(items)
# 分类分析
category_analysis = self._analyze_categories(items, shop_data["categories"])
# 销售分析
sales_analysis = self._analyze_sales(items)
# 变体分析
variant_analysis = self._analyze_variants(items)
# 折扣分析
discount_analysis = self._analyze_discounts(items)
# 评分分析
rating_analysis = self._analyze_ratings(items)
# 识别热门商品
top_items = self._identify_top_items(items)
return {
"shop_summary": shop_data["shop_info"],
"total_items": total,
"price_analysis": price_analysis,
"category_analysis": category_analysis,
"sales_analysis": sales_analysis,
"variant_analysis": variant_analysis,
"discount_analysis": discount_analysis,
"rating_analysis": rating_analysis,
"top_items": top_items
}
def _analyze_prices(self, items: List[Dict]) -> Dict:
"""分析商品价格分布"""
prices = [item["price"] for item in items]
if not prices:
return {}
min_price = min(prices)
max_price = max(prices)
avg_price = round(sum(prices) / len(prices), 2)
median_price = self._calculate_median(prices)
# 价格区间分布
price_ranges = self._get_price_ranges(min_price, max_price)
range_counts = defaultdict(int)
for price in prices:
for r in price_ranges:
if r[0] <= price < r[1]:
range_counts[f"{r[0]}-{r[1]}"] += 1
break
else:
range_counts[f"{price_ranges[-1][1]}+"] += 1
# 价格与销量相关性
price_sales_correlation = self._calculate_correlation(
[item["price"] for item in items],
[item["sold"] for item in items]
)
return {
"min_price": min_price,
"max_price": max_price,
"avg_price": avg_price,
"median_price": median_price,
"range_distribution": dict(range_counts),
"price_sales_correlation": round(price_sales_correlation, 4)
}
def _analyze_categories(self, items: List[Dict], categories: List[Dict]) -> Dict:
"""分析商品分类分布"""
category_counts = defaultdict(int)
category_sales = defaultdict(int)
category_avg_price = defaultdict(list)
for item in items:
cat_id = item["categories"]["category_id"]
cat_name = item["categories"]["category_name"] or f"分类{cat_id}"
category_counts[cat_name] += 1
category_sales[cat_name] += item["sold"]
category_avg_price[cat_name].append(item["price"])
# 计算每个分类的平均价格
category_avg_price = {k: round(sum(v)/len(v), 2) for k, v in category_avg_price.items()}
# 计算每个分类的商品占比
total = len(items)
category_ratio = {k: round(v/total*100, 1) for k, v in category_counts.items()}
# 按商品数量排序
sorted_categories = sorted(category_counts.items(), key=lambda x: x[1], reverse=True)
return {
"total_categories": len(category_counts),
"distribution": dict(category_counts),
"ratio": category_ratio,
"sales_by_category": dict(category_sales),
"avg_price_by_category": category_avg_price,
"top_categories": sorted_categories[:5]
}
def _analyze_sales(self, items: List[Dict]) -> Dict:
"""分析商品销售情况"""
total_sold = sum(item["sold"] for item in items)
avg_sold = round(total_sold / len(items), 1) if items else 0
# 销量分布
sold_ranges = [(0, 0), (1, 10), (11, 50), (51, 100), (101, 500), (501, float('inf'))]
sold_distribution = defaultdict(int)
for item in items:
sold = item["sold"]
for r in sold_ranges:
if r[0] <= sold <= r[1]:
sold_distribution[f"{r[0]}-{r[1] if r[1] != float('inf') else '+'}"] += 1
break
# 计算有销量的商品比例
has_sales_ratio = round(sum(1 for item in items if item["sold"] > 0) / len(items) * 100, 1)
return {
"total_sold": total_sold,
"avg_sold_per_item": avg_sold,
"distribution": dict(sold_distribution),
"has_sales_ratio": has_sales_ratio
}
def _analyze_variants(self, items: List[Dict]) -> Dict:
"""分析商品变体情况"""
variant_counts = [len(item["variants"]) for item in items]
total_variants = sum(variant_counts)
# 变体数量分布
variant_distribution = defaultdict(int)
for count in variant_counts:
if count == 0:
variant_distribution["无变体"] += 1
elif count == 1:
variant_distribution["1个变体"] += 1
elif count <= 5:
variant_distribution["2-5个变体"] += 1
elif count <= 10:
variant_distribution["6-10个变体"] += 1
else:
variant_distribution["10+个变体"] += 1
# 有变体的商品比例
has_variant_ratio = round(sum(1 for item in items if item["variants"]) / len(items) * 100, 1)
return {
"total_variants": total_variants,
"avg_variants_per_item": round(total_variants / len(items), 1) if items else 0,
"distribution": dict(variant_distribution),
"has_variant_ratio": has_variant_ratio
}
def _analyze_discounts(self, items: List[Dict]) -> Dict:
"""分析商品折扣情况"""
discounted_items = [item for item in items if item["discount"] > 0]
discounted_ratio = round(len(discounted_items) / len(items) * 100, 1) if items else 0
# 折扣力度分布
discount_distribution = defaultdict(int)
for item in discounted_items:
discount = item["discount"]
if discount <= 10:
discount_distribution["0-10%"] += 1
elif discount <= 20:
discount_distribution["11-20%"] += 1
elif discount <= 30:
discount_distribution["21-30%"] += 1
elif discount <= 50:
discount_distribution["31-50%"] += 1
else:
discount_distribution["50%+"] += 1
# 平均折扣力度
avg_discount = round(sum(item["discount"] for item in discounted_items) / len(discounted_items), 1) if discounted_items else 0
# 促销商品比例
promotion_ratio = round(sum(1 for item in items if item["is_promotion"]) / len(items) * 100, 1) if items else 0
return {
"discounted_ratio": discounted_ratio,
"avg_discount": avg_discount,
"discount_distribution": dict(discount_distribution),
"promotion_ratio": promotion_ratio
}
def _analyze_ratings(self, items: List[Dict]) -> Dict:
"""分析商品评分情况"""
rated_items = [item for item in items if item["rating_count"] > 0]
rated_ratio = round(len(rated_items) / len(items) * 100, 1) if items else 0
# 评分分布
rating_distribution = defaultdict(int)
for item in rated_items:
rating = round(item["rating"])
rating_distribution[f"{rating}星"] += 1
# 平均评分
avg_rating = round(sum(item["rating"] for item in rated_items) / len(rated_items), 1) if rated_items else 0
# 评分与销量相关性
rating_sales_correlation = 0
if len(rated_items) >= 2:
rating_sales_correlation = self._calculate_correlation(
[item["rating"] for item in rated_items],
[item["sold"] for item in rated_items]
)
return {
"rated_ratio": rated_ratio,
"avg_rating": avg_rating,
"distribution": dict(rating_distribution),
"rating_sales_correlation": round(rating_sales_correlation, 4)
}
def _identify_top_items(self, items: List[Dict]) -> Dict:
"""识别各类热门商品"""
# 按销量排序
top_sales = sorted(items, key=lambda x: x["sold"], reverse=True)[:10]
# 按评分排序(至少有10条评价)
rated_items = [item for item in items if item["rating_count"] >= 10]
top_rated = sorted(rated_items, key=lambda x: x["rating"], reverse=True)[:10]
# 按折扣力度排序
discounted_items = [item for item in items if item["discount"] > 0]
top_discounts = sorted(discounted_items, key=lambda x: x["discount"], reverse=True)[:10]
# 最新上架
top_newest = sorted(items, key=lambda x: x["created_time"], reverse=True)[:10]
return {
"top_sales": top_sales,
"top_rated": top_rated,
"top_discounts": top_discounts,
"top_newest": top_newest
}
# 工具方法
def _get_price_ranges(self, min_price: float, max_price: float) -> List[Tuple[float, float]]:
"""生成合理的价格区间"""
if min_price >= max_price:
return [(min_price - 1, max_price + 1)]
# 计算区间数量,最多8个区间
range_count = min(8, int(max_price - min_price) // 10 + 1)
step = (max_price - min_price) / range_count
ranges = []
for i in range(range_count):
start = min_price + i * step
end = min_price + (i + 1) * step
ranges.append((round(start, 1), round(end, 1)))
return ranges
def _calculate_median(self, data: List[float]) -> float:
"""计算中位数"""
sorted_data = sorted(data)
n = len(sorted_data)
if n % 2 == 1:
return round(sorted_data[n//2], 2)
else:
return round((sorted_data[n//2 - 1] + sorted_data[n//2]) / 2, 2)
def _calculate_correlation(self, x: List[float], y: List[float]) -> float:
"""计算皮尔逊相关系数"""
if len(x) != len(y) or len(x) < 2:
return 0.0
n = len(x)
sum_x = sum(x)
sum_y = sum(y)
sum_xy = sum(xi * yi for xi, yi in zip(x, y))
sum_x2 = sum(xi **2 for xi in x)
sum_y2 = sum(yi** 2 for yi in y)
numerator = n * sum_xy - sum_x * sum_y
denominator = ((n * sum_x2 - sum_x **2) * (n * sum_y2 - sum_y** 2)) **0.5
return numerator / denominator if denominator != 0 else 0.0
def visualize_analysis(self, analysis: Dict, output_dir: str = "shop_analysis") -> None:
"""可视化分析结果"""
# 创建输出目录
if not os.path.exists(output_dir):
os.makedirs(output_dir)
# 设置中文显示
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
plt.rcParams["axes.unicode_minus"] = False
# 1. 价格分布直方图
plt.figure(figsize=(10, 6))
if analysis["price_analysis"]["range_distribution"]:
ranges = list(analysis["price_analysis"]["range_distribution"].keys())
counts = list(analysis["price_analysis"]["range_distribution"].values())
plt.bar(ranges, counts, color='skyblue')
plt.title(f"商品价格区间分布 (平均价格: {analysis['price_analysis']['avg_price']}{analysis['shop_summary'].get('currency', 'SGD')})")
plt.xlabel(f"价格区间 ({analysis['shop_summary'].get('currency', 'SGD')})")
plt.ylabel("商品数量")
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig(f"{output_dir}/price_distribution.png")
plt.close()
# 2. 分类分布饼图
plt.figure(figsize=(10, 6))
top_cats = analysis["category_analysis"]["top_categories"]
if top_cats:
labels = [cat[0] for cat in top_cats]
sizes = [cat[1] for cat in top_cats]
# 其他分类合并
if len(analysis["category_analysis"]["distribution"]) > 5:
other_count = sum(analysis["category_analysis"]["distribution"].values()) - sum(sizes)
labels.append("其他")
sizes.append(other_count)
plt.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=90)
plt.title("商品分类分布")
plt.axis('equal')
plt.tight_layout()
plt.savefig(f"{output_dir}/category_distribution.png")
plt.close()
# 3. 销量分布条形图
plt.figure(figsize=(10, 6))
sales_dist = analysis["sales_analysis"]["distribution"]
if sales_dist:
labels = list(sales_dist.keys())
counts = list(sales_dist.values())
plt.bar(labels, counts, color='#4CAF50')
plt.title("商品销量区间分布")
plt.xlabel("销量区间")
plt.ylabel("商品数量")
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig(f"{output_dir}/sales_distribution.png")
plt.close()
# 4. 折扣分布条形图
plt.figure(figsize=(10, 6))
discount_dist = analysis["discount_analysis"]["discount_distribution"]
if discount_dist:
labels = list(discount_dist.keys())
counts = list(discount_dist.values())
plt.bar(labels, counts, color='#FF9800')
plt.title("商品折扣力度分布")
plt.xlabel("折扣区间")
plt.ylabel("商品数量")
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig(f"{output_dir}/discount_distribution.png")
plt.close()
# 5. 评分分布条形图
plt.figure(figsize=(10, 6))
rating_dist = analysis["rating_analysis"]["distribution"]
if rating_dist:
# 确保按星级排序
sorted_labels = sorted(rating_dist.keys(), key=lambda x: int(x[0]))
sorted_counts = [rating_dist[label] for label in sorted_labels]
plt.bar(sorted_labels, sorted_counts, color='#2196F3')
plt.title(f"商品评分分布 (平均评分: {analysis['rating_analysis']['avg_rating']})")
plt.xlabel("评分星级")
plt.ylabel("商品数量")
plt.tight_layout()
plt.savefig(f"{output_dir}/rating_distribution.png")
plt.close()
logging.info(f"分析图表已保存至 {output_dir} 目录")
def export_to_excel(self, shop_data: Dict, output_file: str = "shop_items_analysis.xlsx") -> None:
"""将店铺商品数据导出到Excel"""
if not shop_data or not shop_data["items"]:
logging.warning("没有可导出的商品数据")
return
# 准备数据
items_data = []
for item in shop_data["items"]:
items_data.append({
"商品ID": item["item_id"],
"商品名称": item["name"],
"分类": item["categories"]["category_name"],
"价格": item["price"],
"原价": item["original_price"],
"折扣(%)": item["discount"],
"销量": item["sold"],
"库存": item["stock"],
"评分": item["rating"],
"评价数": item["rating_count"],
"变体数量": len(item["variants"]),
"是否促销": "是" if item["is_promotion"] else "否",
"商品链接": item["url"]
})
# 创建DataFrame
df = pd.DataFrame(items_data)
# 创建Excel写入器
with pd.ExcelWriter(output_file, engine='openpyxl') as writer:
# 商品列表
df.to_excel(writer, sheet_name='商品列表', index=False)
# 分类统计
category_stats = []
for cat_name, count in shop_data.get("category_analysis", {}).get("distribution", {}).items():
category_stats.append({
"分类名称": cat_name,
"商品数量": count,
"占比(%)": shop_data.get("category_analysis", {}).get("ratio", {}).get(cat_name, 0),
"总销量": shop_data.get("category_analysis", {}).get("sales_by_category", {}).get(cat_name, 0),
"平均价格": shop_data.get("category_analysis", {}).get("avg_price_by_category", {}).get(cat_name, 0)
})
pd.DataFrame(category_stats).to_excel(writer, sheet_name='分类统计', index=False)
# 热门商品
top_sales = []
for i, item in enumerate(shop_data.get("top_items", {}).get("top_sales", [])[:10], 1):
top_sales.append({
"排名": i,
"商品名称": item["name"],
"销量": item["sold"],
"价格": item["price"],
"评分": item["rating"]
})
pd.DataFrame(top_sales).to_excel(writer, sheet_name='热销商品', index=False)
logging.info(f"商品数据已导出至 {output_file}")
示例调用 if name == "main": # 替换为实际的参数(从Shopee开放平台获取) PARTNER_ID = 123456 # 合作伙伴ID PARTNER_KEY = "your_partner_key" # 合作伙伴密钥 ACCESS_TOKEN = "your_access_token" # 访问令牌 SHOPID = 987654 # 目标店铺ID REGION = "sg" # 地区代码
python
# 初始化API客户端
api = ShopeeItemSearchShopAPI(PARTNER_ID, PARTNER_KEY, region=REGION)
api.set_access_token(ACCESS_TOKEN)
# 获取店铺所有商品
shop_data = api.get_all_shop_items(
shopid=SHOPID,
max_pages=5, # 最多获取5页
# category_id=123, # 可选:指定分类ID
# sort_by="sales", # 按销量排序
# order_by="desc"
)
if shop_data and shop_data["items"]:
# 分析店铺商品数据
analysis = api.analyze_shop_items(shop_data)
print(f"=== Shopee店铺商品分析报告 (店铺ID: {SHOPID}) ===")
print(f"店铺名称: {analysis['shop_summary']['shop_name']}")
print(f"店铺评分: {analysis['shop_summary']['rating']}星")
print(f"粉丝数量: {analysis['shop_summary']['follower_count']}")
print(f"商品总数: {analysis['total_items']}件")
# 价格分析
print("\n价格分析:")
print(f" 价格范围: {analysis['price_analysis']['min_price']}-{analysis['price_analysis']['max_price']}{analysis['shop_summary'].get('currency', 'SGD')}")
print(f" 平均价格: {analysis['price_analysis']['avg_price']}{analysis['shop_summary'].get('currency', 'SGD')}")
print(f" 价格与销量相关性: {analysis['price_analysis']['price_sales_correlation']}")
# 分类分析
print("\n分类分析:")
print(f" 分类数量: {analysis['category_analysis']['total_categories']}个")
print(" 商品最多的3个分类:")
for i, (cat, count) in enumerate(analysis['category_analysis']['top_categories'][:3], 1):
ratio = analysis['category_analysis']['ratio'].get(cat, 0)
print(f" {i}. {cat}: {count}件 ({ratio}%)")
# 销售分析
print("\n销售分析:")
print(f" 总销量: {analysis['sales_analysis']['total_sold']}件")
print(f" 平均每件商品销量: {analysis['sales_analysis']['avg_sold_per_item']}件")
print(f" 有销量的商品占比: {analysis['sales_analysis']['has_sales_ratio']}%")
# 折扣分析
print("\n折扣分析:")
print(f" 有折扣的商品占比: {analysis['discount_analysis']['discounted_ratio']}%")
print(f" 平均折扣力度: {analysis['discount_analysis']['avg_discount']}%")
print(f" 促销商品占比: {analysis['discount_analysis']['promotion_ratio']}%")
# 评分分析
print("\n评分分析:")
print(f" 有评价的商品占比: {analysis['rating_analysis']['rated_ratio']}%")
print(f" 平均评分: {analysis['rating_analysis']['avg_rating']}星")
# 变体分析
print("\n变体分析:")
print(f" 有变体的商品占比: {analysis['variant_analysis']['has_variant_ratio']}%")
print(f" 平均每件商品变体数: {analysis['variant_analysis']['avg_variants_per_item']}个")
# 热门商品
print("\n热销商品TOP3:")
for i, item in enumerate(analysis['top_items']['top_sales'][:3], 1):
print(f" {i}. {item['name'][:30]}...")
print(f" 价格: {item['price']}{analysis['shop_summary'].get('currency', 'SGD')}")
print(f" 销量: {item['sold']}件")
print(f" 评分: {item['rating']}星")
# 生成可视化图表
api.visualize_analysis(analysis)
# 导出数据到Excel
api.export_to_excel(analysis)
三、接口调用注意事项
- 调用限制与规范
- QPS 限制:Shopee 开放平台对店铺商品接口的 QPS 限制通常为 10 次 / 秒,不同地区可能有所差异
- 数据权限:可以获取任意公开店铺的商品数据,但部分详细信息可能受权限限制
- 分页限制:单店铺最多可获取的商品页数有限制,通常不超过 100 页
- 调用频率:避免频繁获取同一店铺数据,建议缓存数据,更新周期可设为 12 小时
- 地区差异:不同地区的 API 端点和返回数据格式可能略有差异,需注意适配
- 常见错误及解决方案
错误码 | 说明 | 解决方案 |
---|---|---|
401 | 认证失败 | 检查 partner_id、partner_key 和 access_token 的有效性 |
403 | 权限不足 | 确认 API 权限是否已开通,部分地区可能需要特殊权限 |
404 | 店铺不存在 | 确认 shopid 是否正确有效,店铺可能已关闭 |
429 | 调用频率超限 | 降低调用频率,实现请求限流,建议 QPS 不超过 5 |
500 | 服务器错误 | 实现重试机制,最多 3 次,采用指数退避策略 |
10001 | 参数错误 | 检查请求参数格式,特别是 timestamp 和 sign 的生成是否正确 |
10003 | access_token 过期 | 重新获取 access_token |
1101 | 店铺无商品 | 该店铺可能未上架任何商品 |
- 数据解析要点
- 签名生成:严格按照 Shopee 的签名算法实现,注意时间戳的准确性(毫秒级)
- 价格单位:Shopee 返回的价格单位为分(1/100000 货币单位),需转换为标准单位
- 时间处理:商品上下架时间为 Unix 时间戳(秒级),需转换为可读性强的日期格式
- 变体信息:变体属性可能包含颜色、尺寸等多维度信息,需正确解析组合
- 分类映射:店铺内分类与平台分类可能不同,需使用店铺自身分类体系
四、应用场景与扩展建议 典型应用场景
- 竞品分析系统:全面分析竞争对手的商品结构、定价策略和销售情况
- 市场调研工具:研究特定品类的市场分布和价格区间
- 智能选品助手:基于热销商品特征,为卖家提供选品建议
- 价格监控系统:追踪目标店铺的价格变化和促销活动
- 店铺运营分析:分析自身店铺与竞品的差距,优化运营策略
扩展建议
- 实现多店铺对比分析:横向对比多个竞争店铺的商品特征和运营数据
- 开发价格趋势追踪:记录商品价格历史变化,分析定价策略调整
- 构建商品相似度识别:使用 NLP 技术识别相似商品,进行精准竞品对比
- 开发热销特征提取:分析热销商品的共同特征,指导选品和产品开发
- 实现库存预警监控:追踪竞品库存变化,识别市场机会
- 构建店铺画像系统:基于商品数据生成店铺定位和运营策略画像
通过合理使用 Shopee item_search_shop
接口,开发者可以构建功能丰富的电商分析工具,帮助卖家深入了解市场动态和竞争对手情况,优化自身的商品布局和运营策略。在实际应用中,需特别注意 API 调用频率限制和数据缓存策略,确保系统的稳定性和高效性。