
Category Selection
- 36 installs
- 658 repo stars
- Updated July 8, 2026
- liangdabiao/amazon-sorftime-research-mcp-skill
Helps with ai & agent building tasks.
About
category-selection is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- category-selection
- AI & Agent Building
- AI-coding skill
Category Selection by the numbers
- 36 all-time installs (skills.sh)
- +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #8,638 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/liangdabiao/amazon-sorftime-research-mcp-skill --skill category-selectionAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 36 |
|---|---|
| repo stars | ★ 658 |
| Last updated | July 8, 2026 |
| Repository | liangdabiao/amazon-sorftime-research-mcp-skill ↗ |
What it does
Helps with ai & agent building tasks.
Files
快速参考
一键执行工作流 (推荐)
# 使用品类名称
python .claude/skills/category-selection/scripts/workflow.py "Sofas" US 20
# 直接使用 NodeID (推荐,避免类目搜索问题)
python .claude/skills/category-selection/scripts/workflow.py 679394011 US 20
# 指定分析数量
python .claude/skills/category-selection/scripts/workflow.py "Kitchen" US 50重要更新 (v4.0):
- ✅ 自动读取 API Key: 无需设置环境变量,自动从
.mcp.json读取 - ✅ 修复控制字符: 自动处理 JSON 字符串值中的未转义换行符、制表符
- ✅ 改进类目搜索: 支持模糊匹配和关键词变体
- ✅ 详细日志: 执行日志保存到
execution.log
核心 API 工具
| 步骤 | 工具/操作 | 用途 | 返回数据大小 |
|---|---|---|---|
| 1. 搜索类目 | category_name_search | 获取类目 nodeId | 小 |
| 2. 类目报告 | category_report | 获取 Top 产品列表和统计数据 | 大 (>25KB) |
| 3. 产品详情 | product_detail | 获取单个产品详情 | 小 |
| 4. 类目关键词 | category_keywords | 获取类目核心关键词 | 大 (>25KB) |
| 5. 类目趋势 | category_trend | 获取25个月历史趋势 | 中 |
| 6. 1688采购 | products_1688 | 获取采购成本数据 | 小 |
调用格式
curl -s -X POST "https://mcp.sorftime.com?key=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":N,"method":"tools/call","params":{"name":"TOOL_NAME","arguments":{"amzSite":"US","nodeId":"NODE_ID"}}}'---
触发条件
当用户使用以下方式请求时,启动此分析流程:
- 命令:
/category-selection {品类名称} {站点} [--limit N] - 示例:
/category-selection "Sofas" US --limit 20 - 自然语言: "分析Amazon美国站的Sofas品类"、"Sofas品类市场调研"、"Sofas品类选品"
---
角色设定
你是一位拥有10年经验的"亚马逊选品专家"和"市场分析师"。你精通品类分析方法论,能够通过数据洞察市场机会、竞争格局和进入壁垒,为用户提供可执行的选品建议。
---
五维评分模型 (标准版)
评分标准详解:
| 维度 | 分值 | 评分标准 | 数据来源 |
|---|---|---|---|
| 市场规模 | 20 分 | >$10M=20分, >$5M=17分, >$1M=14分, 其他=10分 | 类目月销额 (top100产品月销额) |
| 增长潜力 | 25 分 | 低评论产品占比>40%=22分, >20%=18分, 其他=14分 | 评论数<100的产品占比 |
| 竞争烈度 | 20 分 | Top3品牌占比<30%=18分, <50%=14分, 其他=8分 | CR3 品牌集中度 |
| 进入壁垒 | 20 分 | Amazon占比<20%且新品>40%=20分, 其他组合6-18分 | Amazon自营占比 + 低评论占比 |
| 利润空间 | 15 分 | 均价>$300=12分, >$150=10分, >$50=7分, 其他=4分 | Top100产品平均价格 |
评级标准:
| 总分 | 评级 | 建议 |
|---|---|---|
| 80-100 | 优秀 | 强烈推荐进入 |
| 70-79 | 良好 | 可以考虑进入 |
| 50-69 | 一般 | 谨慎进入 |
| 0-49 | 较差 | 不建议进入 |
完整标准请参考: scoring-standard.md
---
完整分析流程
阶段一: 数据收集
步骤 1: 搜索类目获取 nodeId
curl -s -X POST "https://mcp.sorftime.com?key={API_KEY}" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"category_name_search","arguments":{"amzSite":"US","searchName":"品类关键词"}}}'处理多个类目结果时:
- 大类目(如 "Clothing, Shoes & Jewelry")通常只返回子类目列表
- 展示给用户让其选择最匹配的类目
- 或使用具体的子类目 NodeID 直接查询
常见类目 NodeID 参考:
Traditional Laptop Computers: 13896615011
2 in 1 Laptop Computers: 13896609011
Women's Fashion Sneakers: 679394011
Women's Road Running Shoes: 14210388011
Men's Fashion Sneakers: 679312011
Kitchen Storage Accessories: 3744031步骤 2: 获取类目报告 (Top100 + 统计)
curl -s -X POST "https://mcp.sorftime.com?key={API_KEY}" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"category_report","arguments":{"amzSite":"US","nodeId":"NODE_ID"}}}'关键: category_report 返回数据通常>25KB,会保存到临时文件
响应处理:
# 使用 workflow.py 自动处理 (推荐)
python .claude/skills/category-selection/scripts/workflow.py "Sofas" US 20
# 或手动解码 SSE 响应
python .claude/skills/category-selection/scripts/sse_decoder.py {temp_file} {output_dir} 20步骤 3: 获取 Top N 产品详情 (并发)
# 并发获取 Top3 产品详情
curl ... '{"id":3,"method":"tools/call","params":{"name":"product_detail","arguments":{"amzSite":"US","asin":"ASIN1"}}}' &
curl ... '{"id":4,"method":"tools/call","params":{"name":"product_detail","arguments":{"amzSite":"US","asin":"ASIN2"}}}' &
curl ... '{"id":5,"method":"tools/call","params":{"name":"product_detail","arguments":{"amzSite":"US","asin":"ASIN3"}}}' &
wait步骤 4: 获取类目关键词 (可选)
curl -s -X POST "https://mcp.sorftime.com?key={API_KEY}" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"category_keywords","arguments":{"amzSite":"US","nodeId":"NODE_ID","page":1}}}'处理关键词数据:
python .claude/skills/category-selection/scripts/keywords_parser.py \
{temp_file} \
{output_dir} \
20步骤 5: 获取历史趋势数据 (可选)
curl -s -X POST "https://mcp.sorftime.com?key={API_KEY}" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"category_trend","arguments":{"amzSite":"US","nodeId":"NODE_ID"}}}'趋势数据类型:
- 类目月销量趋势 (25个月)
- 平均售价趋势
- 平均星级趋势
- 品牌数量趋势
阶段二: 数据分析
核心分析指标
1. 市场集中度分析
# HHI 指数 (赫芬达尔-赫希曼指数)
# 计算: 各品牌市场份额平方和 × 10000
# 解读: <1500=低集中度, 1500-2500=中等, >2500=高集中度
# CR3/CR5 (前N品牌集中度)
# 计算: 前N大品牌销量占比
# 解读: <30%=分散, 30-50%=中等, >50%=集中2. 品牌分析
# 品牌分布: 按销量/销额排序
# 品牌数量: 统计独立品牌数
# 品牌多样性: HHI 指数评估3. 卖家来源分析
# Amazon 自营占比
# 中国卖家占比
# 美国本土卖家占比
# 其他国际卖家占比4. 价格分析
# 价格区间分布
# 平均价格
# 价格中位数
# 价格标准差5. 新品分析
# 新产品定义: 上架时间 < 90天
# 新品占比: 新品数量 / 总数量
# 新品表现: 新品平均销量、评论数阶段三: 报告生成
生成完整报告
# 一键生成所有报告格式
python .claude/skills/category-selection/scripts/workflow.py "Sofas" US 20
# 或分步骤生成
python .claude/skills/category-selection/scripts/generate_reports.py {data_json}输出文件结构:
category-reports/
└── {Category}_{Site}_{YYYYMMDD}/
├── report.md # Markdown 分析报告
├── data.json # 完整解码数据 (中文键)
├── top_products.json # Top N 产品列表
├── scores.json # 五维评分结果
├── execution.log # 执行日志 (v4.0 新增)
├── keywords.json # 类目关键词
├── trend_data.json # 25个月趋势数据
├── adapted_data.json # Excel 适配数据 (英文键)
├── category_analysis_report.xlsx # Excel 报告
├── dashboard.html # HTML 可视化仪表板
├── data/ # 原始数据目录
│ ├── statistics.csv # 统计数据
│ ├── products.csv # 产品列表
│ └── scores.csv # 评分详情
└── *_raw.txt # 原始 SSE 响应---
数据处理工具
核心工具脚本
| 脚本 | 用途 | 版本 |
|---|---|---|
workflow.py | 一键执行完整分析流程 | v4.0 |
sse_decoder.py | 解码 category_report SSE 响应 | v6.0 |
keywords_parser.py | 解码 category_keywords 响应 | v3.0 |
trend_parser.py | 解析趋势数据 | v1.0 |
data_adapter.py | 数据格式转换 (中文→英文键) | v1.0 |
data_utils.py | 数据处理工具类 | v2.0 |
generate_reports.py | 统一报告生成器 | v3.0 |
generate_excel_report.py | Excel 报告生成 | v2.0 |
generate_markdown_report.py | Markdown 报告生成 | v2.0 |
fix_encoding.py | 编码修复工具 | v1.0 |
数据字段映射
API 响应字段 → 标准化字段:
| API 字段 | 标准化字段 | 说明 |
|---|---|---|
| ASIN | asin | 产品唯一标识 |
| 标题/title | title | 产品标题 |
| 价格/price | price | 当前售价 |
| 月销量/monthlySales | monthly_sales | 月销量 |
| 月销额/monthlyRevenue | monthly_revenue | 月销售额 |
| 评论数/reviews | review_count | 评论数量 |
| 星级/rating | rating | 平均评分 |
| 品牌/brand | brand | 品牌名称 |
| 卖家/seller | seller | 卖家名称 |
| 上架时间/daysOnline | days_online | 上架天数 |
---
HTML 可视化仪表板
特性
- 基于 ECharts 的交互式图表
- 五维评分可视化进度条
- KPI 指标卡片展示
- 7 个动态图表:销量趋势、价格趋势、价格分布、评分分布、品牌份额、卖家来源、品牌评分趋势
- Top50 产品详细表格
- 关键发现智能分析
模板变量支持
| 变量类型 | 示例变量 | 说明 |
|---|---|---|
| 基础信息 | {{CATEGORY_NAME}}, {{SITE}}, {{DATA_DATE}} | 报告基本信息 |
| 五维评分 | {{MARKET_SIZE_SCORE}}, {{MARKET_SIZE_PERCENT}} | 各维度得分和进度条百分比 |
| KPI指标 | {{TOTAL_PRODUCTS}}, {{AVG_PRICE}}, {{CR3}} | 关键指标数据 |
| 图表数据 | {{SALES_TREND_DATA}}, {{BRAND_SHARE_DATA}} | JavaScript JSON 数据 |
| 分析结论 | {{CONCENTRATION_LEVEL}}, {{RECOMMENDATION}} | 智能分析文本 |
---
故障排查
常见问题与解决方案 (v4.0 更新)
1. API Key 未设置
问题: ❌ API Key 未设置 或 Authentication required
原因: 1. 环境变量 SORFTIME_API_KEY 未设置 2. .mcp.json 文件不存在或格式错误
解决方案 (v4.0 已修复):
- workflow.py v4.0 会自动从
.mcp.json读取 API Key - 确保项目根目录存在
.mcp.json文件,格式如下:
{
"mcpServers": {
"sorftime": {
"url": "https://mcp.sorftime.com?key=YOUR_API_KEY"
}
}
}手动设置环境变量 (备用):
# Windows PowerShell
$env:SORFTIME_API_KEY="your_api_key"
# Linux/Mac
export SORFTIME_API_KEY="your_api_key"2. JSON 解析失败 - 未转义的控制字符
问题: JSONDecodeError: Invalid control character at: line 1 column 3401
原因: API 返回的 JSON 字符串值中包含原始的换行符(\n)、制表符(\t)等控制字符,这些控制字符没有被正确转义
示例:
// 错误格式(API 返回的原始格式)
{"标题": "类目:Renewed Laptops,排名:2
类目:Traditional Laptops,排名:11"}
// 正确格式
{"标题": "类目:Renewed Laptops,排名:2\\n类目:Traditional Laptops,排名:11"}解决方案 (v4.0 已修复):
escape_control_chars_in_json_strings()函数自动转义字符串值内的控制字符- 该函数只处理字符串值内部的控制字符,不影响 JSON 结构
3. 类目未找到
问题: 搜索类目时返回"未查询到对应类目"
原因: 1. 大类目(如 "Computers & Accessories")可能只返回子类目列表 2. 类目名称不准确
解决方案 (v4.0 已改进): 1. workflow.py v4.0 会自动尝试多种搜索变体 2. 使用更具体的子类目名称 3. 推荐: 直接使用类目 NodeID 查询
获取 NodeID 的方法:
# 先用大类目搜索,查看返回的子类目列表
curl -s -X POST "https://mcp.sorftime.com?key={API_KEY}" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"category_name_search","arguments":{"amzSite":"US","searchName":"Laptop"}}}'4. 数据解析失败 (Mojibake 编码问题)
问题: data.json 中的中文显示为 "Top100产å" 等乱码
原因: API 返回 Unicode-escape 格式 (\u4ea7\u54c1),解码后产生 Mojibake
解决方案 (v4.0 已自动修复):
fix_mojibake()函数自动修复编码问题- 在 Unicode-escape 解码后立即应用
.encode('latin-1').decode('utf-8')
5. Python dict 格式问题
问题: "Expecting property name enclosed in double quotes"
原因: API 返回 Python dict 格式(单引号),不是标准 JSON
解决方案 (v4.0 已修复):
python_dict_to_json()函数正确处理单引号转换- 同时处理 True/False/None 字面量
6. 大类目搜索失败
问题: "Computers & Accessories" 等大类目搜索无结果
解决方案: 1. 使用子类目名称(如 "Laptops", "Computer Accessories") 2. 先搜索大类目获取子类目列表,让用户选择 3. 直接使用已知 NodeID
版本更新记录
| 脚本 | 版本 | 更新内容 |
|---|---|---|
workflow.py | v4.0 | ✅ 从 .mcp.json 自动读取 API Key<br>✅ 修复 JSON 字符串中未转义的控制字符<br>✅ 改进类目搜索策略<br>✅ 新增执行日志<br>✅ 更详细的错误信息 |
sse_decoder.py | v6.0 | Mojibake 自动修复、括号匹配、Python dict 转换 |
generate_reports.py | v3.0 | 完整变量替换、分析文本生成 |
调试技巧
1. 查看执行日志:
# workflow.py v4.0 会自动保存执行日志
cat category-reports/{Category}_{Site}_{YYYYMMDD}/execution.log2. 查看原始响应:
# workflow.py 会自动保存原始 SSE 响应
cat category-reports/{Category}_{Site}_{YYYYMMDD}/category_report_raw.txt3. 检查编码问题:
# 检查文件字节
with open('data.json', 'rb') as f:
print(f.read(100))4. 验证 JSON 格式:
# 使用 Python 验证 JSON
python -m json.tool data.json5. 测试 API 连接:
curl -s -X POST "https://mcp.sorftime.com?key={YOUR_KEY}" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"category_name_search","arguments":{"amzSite":"US","searchName":"Kitchen"}}}'---
支持的站点
Amazon: US, GB, DE, FR, IN, CA, JP, ES, IT, MX, AE, AU, BR, SA TikTok: US, GB, MY, PH, VN, ID 1688: 中国批发平台
---
注意事项
1. API Key 配置:
- 推荐在
.mcp.json中配置(v4.0 自动读取) - 也可以使用环境变量
SORFTIME_API_KEY
2. 参数名称: 使用 amzSite 而非 site 3. id 递增: 每个请求的 id 字段必须递增 (1, 2, 3...) 4. 并发限制: 建议最多 3-5 个并发请求 5. 数据时效: 数据可能有 1-7 天延迟 6. 报告命名: 使用 {Category}_{Site}_{YYYYMMDD} 格式
---
参考文档
- 评分标准详解
- API 快速参考
- Sorftime MCP API 文档
- 类目 API 参考
---
本 Skill 由 Claude Code 维护 | 最后更新: 2026-03-05 (v4.0)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{CATEGORY_NAME}} 品类市场调研报告</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f5f7fa;
color: #333;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
border-radius: 10px;
margin-bottom: 30px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
}
.header h1 {
font-size: 28px;
margin-bottom: 10px;
}
.header .subtitle {
font-size: 14px;
opacity: 0.9;
}
/* 五维评分卡片 */
.score-card {
background: white;
border-radius: 10px;
padding: 30px;
margin-bottom: 30px;
box-shadow: 0 5px 20px rgba(0,0,0,0.08);
}
.score-card h2 {
font-size: 20px;
margin-bottom: 25px;
color: #667eea;
}
.score-bars {
display: flex;
flex-direction: column;
gap: 15px;
}
.score-item {
display: flex;
align-items: center;
gap: 20px;
}
.score-label {
width: 120px;
font-weight: 600;
}
.score-bar-container {
flex: 1;
height: 30px;
background: #e0e0e0;
border-radius: 15px;
overflow: hidden;
position: relative;
}
.score-bar {
height: 100%;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
border-radius: 15px;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 15px;
color: white;
font-weight: bold;
transition: width 1s ease;
}
.score-summary {
margin-top: 25px;
padding: 20px;
background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%);
border-radius: 10px;
text-align: center;
}
.score-summary .total {
font-size: 36px;
font-weight: bold;
color: #667eea;
}
.score-summary .rating {
font-size: 24px;
margin-top: 5px;
}
/* KPI 卡片 */
.kpi-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.kpi-card {
background: white;
border-radius: 10px;
padding: 25px;
text-align: center;
box-shadow: 0 5px 20px rgba(0,0,0,0.08);
transition: transform 0.3s ease;
}
.kpi-card:hover {
transform: translateY(-5px);
}
.kpi-card .label {
font-size: 14px;
color: #888;
margin-bottom: 10px;
}
.kpi-card .value {
font-size: 28px;
font-weight: bold;
color: #667eea;
}
/* 图表容器 */
.chart-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
gap: 30px;
margin-bottom: 30px;
}
.chart-container {
background: white;
border-radius: 10px;
padding: 25px;
box-shadow: 0 5px 20px rgba(0,0,0,0.08);
}
.chart-container h3 {
font-size: 18px;
margin-bottom: 20px;
color: #667eea;
}
.chart {
width: 100%;
height: 350px;
}
/* 表格样式 */
.table-container {
background: white;
border-radius: 10px;
padding: 25px;
margin-bottom: 30px;
box-shadow: 0 5px 20px rgba(0,0,0,0.08);
overflow-x: auto;
}
.table-container h3 {
font-size: 18px;
margin-bottom: 20px;
color: #667eea;
}
table {
width: 100%;
border-collapse: collapse;
}
thead th {
background: #667eea;
color: white;
padding: 12px;
text-align: center;
font-weight: 600;
}
tbody td {
padding: 12px;
border-bottom: 1px solid #eee;
text-align: center;
}
tbody tr:hover {
background: #f8f9fa;
}
/* 关键发现 */
.key-findings {
background: white;
border-radius: 10px;
padding: 30px;
margin-bottom: 30px;
box-shadow: 0 5px 20px rgba(0,0,0,0.08);
}
.key-findings h2 {
font-size: 22px;
margin-bottom: 20px;
color: #667eea;
}
.findings-list {
display: flex;
flex-direction: column;
gap: 15px;
}
.finding-item {
padding: 15px;
background: #f8f9fa;
border-left: 4px solid #667eea;
border-radius: 5px;
}
.finding-item .title {
font-weight: bold;
color: #667eea;
margin-bottom: 8px;
}
.finding-item ul {
margin-left: 20px;
}
.finding-item li {
margin: 5px 0;
color: #555;
}
/* 页脚 */
.footer {
text-align: center;
padding: 30px;
color: #888;
font-size: 14px;
}
/* 响应式 */
@media (max-width: 768px) {
.chart-row {
grid-template-columns: 1fr;
}
.kpi-cards {
grid-template-columns: repeat(2, 1fr);
}
.score-item {
flex-direction: column;
align-items: flex-start;
}
.score-bar-container {
width: 100%;
}
}
</style>
</head>
<body>
<div class="container">
<!-- 报告标题 -->
<div class="header">
<h1>{{CATEGORY_NAME}} 品类市场调研报告</h1>
<div class="subtitle">{{SITE}} | 数据截止: {{DATA_DATE}} | 数据来源: Sorftime</div>
</div>
<!-- 五维评分模型 -->
<div class="score-card">
<h2>五维评分模型</h2>
<div class="score-bars">
<div class="score-item">
<div class="score-label">市场规模</div>
<div class="score-bar-container">
<div class="score-bar" style="width: {{MARKET_SIZE_PERCENT}}%;">
{{MARKET_SIZE_SCORE}}/20
</div>
</div>
</div>
<div class="score-item">
<div class="score-label">增长潜力</div>
<div class="score-bar-container">
<div class="score-bar" style="width: {{GROWTH_POTENTIAL_PERCENT}}%;">
{{GROWTH_POTENTIAL_SCORE}}/25
</div>
</div>
</div>
<div class="score-item">
<div class="score-label">竞争烈度</div>
<div class="score-bar-container">
<div class="score-bar" style="width: {{COMPETITION_PERCENT}}%;">
{{COMPETITION_SCORE}}/20
</div>
</div>
</div>
<div class="score-item">
<div class="score-label">进入壁垒</div>
<div class="score-bar-container">
<div class="score-bar" style="width: {{ENTRY_BARRIER_PERCENT}}%;">
{{ENTRY_BARRIER_SCORE}}/20
</div>
</div>
</div>
<div class="score-item">
<div class="score-label">利润空间</div>
<div class="score-bar-container">
<div class="score-bar" style="width: {{PROFIT_MARGIN_PERCENT}}%;">
{{PROFIT_MARGIN_SCORE}}/15
</div>
</div>
</div>
</div>
<div class="score-summary">
<div class="total">{{TOTAL_SCORE}}/100</div>
<div class="rating">{{RATING}}</div>
<div>{{RECOMMENDATION}}</div>
</div>
</div>
<!-- KPI 指标卡片 -->
<div class="kpi-cards">
<div class="kpi-card">
<div class="label">产品总数</div>
<div class="value">{{TOTAL_PRODUCTS}}</div>
</div>
<div class="kpi-card">
<div class="label">平均价格</div>
<div class="value">${{AVG_PRICE}}</div>
</div>
<div class="kpi-card">
<div class="label">平均月销量</div>
<div class="value">{{AVG_SALES}}</div>
</div>
<div class="kpi-card">
<div class="label">平均评分</div>
<div class="value">{{AVG_RATING}}</div>
</div>
<div class="kpi-card">
<div class="label">总月销额</div>
<div class="value">${{TOTAL_SALES}}</div>
</div>
<div class="kpi-card">
<div class="label">CR3集中度</div>
<div class="value">{{CR3}}%</div>
</div>
</div>
<!-- 图表行 1 -->
<div class="chart-row">
<div class="chart-container">
<h3>销量趋势 (25个月)</h3>
<div id="salesTrendChart" class="chart"></div>
</div>
<div class="chart-container">
<h3>平均价格走势 (25个月)</h3>
<div id="priceTrendChart" class="chart"></div>
</div>
</div>
<!-- 图表行 2 -->
<div class="chart-row">
<div class="chart-container">
<h3>价格区间分布</h3>
<div id="priceDistChart" class="chart"></div>
</div>
<div class="chart-container">
<h3>评分分布</h3>
<div id="ratingDistChart" class="chart"></div>
</div>
</div>
<!-- 图表行 3 -->
<div class="chart-row">
<div class="chart-container">
<h3>品牌市场份额 Top10</h3>
<div id="brandShareChart" class="chart"></div>
</div>
<div class="chart-container">
<h3>卖家来源分布</h3>
<div id="sellerSourceChart" class="chart"></div>
</div>
</div>
<!-- 品牌评分趋势 -->
<div class="chart-container">
<h3>品牌评分趋势 (25个月)</h3>
<div id="brandRatingTrendChart" class="chart"></div>
</div>
<!-- Top50 产品表格 -->
<div class="table-container">
<h3>Top50 产品详细列表</h3>
<table>
<thead>
<tr>
<th>排名</th>
<th>ASIN</th>
<th>产品标题</th>
<th>品牌</th>
<th>价格</th>
<th>评分</th>
<th>月销量</th>
<th>市场份额</th>
</tr>
</thead>
<tbody id="top50TableBody">
<!-- JavaScript 填充数据 -->
</tbody>
</table>
</div>
<!-- 关键发现 -->
<div class="key-findings">
<h2>关键发现</h2>
<div class="findings-list">
<div class="finding-item">
<div class="title">市场特征</div>
<ul>
<li>市场集中度: {{CONCENTRATION_LEVEL}} (HHI={{HHI}})</li>
<li>CR3仅{{CR3_RAW}}%,说明{{CONCLUSION_CR3}}</li>
<li>{{BRAND_COUNT}}个品牌竞争,{{BRAND_DIVERSITY}}</li>
</ul>
</div>
<div class="finding-item">
<div class="title">竞争格局</div>
<ul>
<li>新产品占比{{NEW_PRODUCT_PERCENT}}%,{{NEW_PRODUCT_CONCLUSION}}</li>
<li>卖家来源{{SELLER_DISTRIBUTION}}</li>
<li>{{COMPETITION_CONCLUSION}}</li>
</ul>
</div>
<div class="finding-item">
<div class="title">评分结论</div>
<ul>
<li>总分{{TOTAL_SCORE}}/100,评级"{{RATING}}"</li>
<li>{{RECOMMENDATION}}</li>
<li>{{STRATEGY}}</li>
</ul>
</div>
</div>
</div>
<!-- 页脚 -->
<div class="footer">
<p>报告生成时间: {{GENERATED_TIME}} | 数据来源: Sorftime MCP</p>
<p>© 2026 Amazon Category Selection Tool</p>
</div>
</div>
<script>
// ECharts 图表配置
const salesTrendData = {{SALES_TREND_DATA}};
const priceTrendData = {{PRICE_TREND_DATA}};
const priceDistData = {{PRICE_DIST_DATA}};
const ratingDistData = {{RATING_DIST_DATA}};
const brandShareData = {{BRAND_SHARE_DATA}};
const sellerSourceData = {{SELLER_SOURCE_DATA}};
const brandRatingTrendData = {{BRAND_RATING_TREND_DATA}};
const top50Products = {{TOP50_PRODUCTS}};
// 销量趋势图
const salesTrendChart = echarts.init(document.getElementById('salesTrendChart'));
salesTrendChart.setOption({
tooltip: { trigger: 'axis' },
xAxis: { type: 'category', data: salesTrendData.dates },
yAxis: { type: 'value', name: '月销量' },
series: [{
type: 'line',
data: salesTrendData.sales,
smooth: true,
areaStyle: { opacity: 0.3 },
itemStyle: { color: '#667eea' }
}]
});
// 价格趋势图
const priceTrendChart = echarts.init(document.getElementById('priceTrendChart'));
priceTrendChart.setOption({
tooltip: { trigger: 'axis' },
xAxis: { type: 'category', data: priceTrendData.dates },
yAxis: { type: 'value', name: '平均价格($)' },
series: [{
type: 'line',
data: priceTrendData.prices,
smooth: true,
itemStyle: { color: '#f093fb' }
}]
});
// 价格区间分布饼图
const priceDistChart = echarts.init(document.getElementById('priceDistChart'));
priceDistChart.setOption({
tooltip: { trigger: 'item', formatter: '{b}: {c}个 ({d}%)' },
series: [{
type: 'pie',
radius: '70%',
data: priceDistData,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
}
}]
});
// 评分分布柱状图
const ratingDistChart = echarts.init(document.getElementById('ratingDistChart'));
ratingDistChart.setOption({
tooltip: { trigger: 'axis' },
xAxis: { type: 'category', data: ratingDistData.ranges },
yAxis: { type: 'value', name: '产品数' },
series: [{
type: 'bar',
data: ratingDistData.counts,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#667eea' },
{ offset: 1, color: '#764ba2' }
])
}
}]
});
// 品牌市场份额 Top10
const brandShareChart = echarts.init(document.getElementById('brandShareChart'));
brandShareChart.setOption({
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
xAxis: { type: 'value' },
yAxis: { type: 'category', data: brandShareData.brands, inverse: true },
series: [{
type: 'bar',
data: brandShareData.shares,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#667eea' },
{ offset: 1, color: '#764ba2' }
])
}
}]
});
// 卖家来源分布饼图
const sellerSourceChart = echarts.init(document.getElementById('sellerSourceChart'));
sellerSourceChart.setOption({
tooltip: { trigger: 'item', formatter: '{b}: {c}个 ({d}%)' },
series: [{
type: 'pie',
radius: ['40%', '70%'],
data: sellerSourceData,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
}
}]
});
// 品牌评分趋势
const brandRatingTrendChart = echarts.init(document.getElementById('brandRatingTrendChart'));
brandRatingTrendChart.setOption({
tooltip: { trigger: 'axis' },
legend: { data: brandRatingTrendData.brands },
xAxis: { type: 'category', data: brandRatingTrendData.dates },
yAxis: { type: 'value', name: '评分', min: 3.5, max: 5.0 },
series: brandRatingTrendData.brands.map(brand => ({
name: brand,
type: 'line',
data: brandRatingTrendData.data[brand],
smooth: true
}))
});
// 填充 Top50 产品表格
const tableBody = document.getElementById('top50TableBody');
top50Products.forEach((product, index) => {
const row = tableBody.insertRow();
row.innerHTML = `
<td>${index + 1}</td>
<td>${product.asin}</td>
<td style="text-align:left;">${product.title}</td>
<td>${product.brand}</td>
<td>$${product.price}</td>
<td>${product.rating}</td>
<td>${product.sales}</td>
<td>${product.marketShare}%</td>
`;
});
// 响应式处理
window.addEventListener('resize', () => {
salesTrendChart.resize();
priceTrendChart.resize();
priceDistChart.resize();
ratingDistChart.resize();
brandShareChart.resize();
sellerSourceChart.resize();
brandRatingTrendChart.resize();
});
</script>
</body>
</html>
{{CATEGORY_NAME}} 品类选品分析报告
生成时间: {{DATE}} {{TIME}} 数据来源: Sorftime MCP
---
执行摘要
综合评级: {{SCORE_评级}}
五维评分概览
| 维度 | 得分 | 满分 | 占比 |
|---|---|---|---|
| 市场规模 | {{SCORE_市场规模}} | 20 | {{SCORE_市场规模_占比}} |
| 增长潜力 | {{SCORE_增长潜力}} | 25 | {{SCORE_增长潜力_占比}} |
| 竞争烈度 | {{SCORE_竞争烈度}} | 20 | {{SCORE_竞争烈度_占比}} |
| 进入壁垒 | {{SCORE_进入壁垒}} | 20 | {{SCORE_进入壁垒_占比}} |
| 利润空间 | {{SCORE_利润空间}} | 15 | {{SCORE_利润空间_占比}} |
| 总分 | {{SCORE_总分}} | 100 | 100% |
建议
{{SCORE_建议}}
---
一、市场数据分析
1.1 市场规模
| 指标 | 数值 |
|---|---|
| Top100 月销量 | {{STAT_总销量}} |
| Top100 月销额 | {{STAT_总销额}} |
| 平均价格 | {{STAT_平均价格}} |
| 中位数价格 | {{STAT_中位数价格}} |
1.2 竞争格局
| 指标 | 数值 | 分析 |
|---|---|---|
| Top3 品牌占比 | {{STAT_Top3品牌占比}} | {{ANALYSIS_品牌集中度}} |
| Amazon 自营占比 | {{STAT_Amazon自营占比}} | {{ANALYSIS_自营影响}} |
| 低评论产品占比 | {{STAT_低评论占比}} | {{ANALYSIS_新品机会}} |
---
二、Top 产品分析
2.1 Top 20 产品
{{PRODUCTS_TABLE}}
2.2 品牌分析
| 品牌 | 产品数 | 平均价格 | 平均评分 |
|---|
{{BRANDS_TABLE}}
---
三、评分详情
3.1 市场规模 (20分)
得分: {{SCORE_市场规模}}/20
分析: {{ANALYSIS_市场规模}}
3.2 增长潜力 (25分)
得分: {{SCORE_增长潜力}}/25
分析: {{ANALYSIS_增长潜力}}
3.3 竞争烈度 (20分)
得分: {{SCORE_竞争烈度}}/20
分析: {{ANALYSIS_竞争烈度}}
3.4 进入壁垒 (20分)
得分: {{SCORE_进入壁垒}}/20
分析: {{ANALYSIS_进入壁垒}}
3.5 利润空间 (15分)
得分: {{SCORE_利润空间}}/15
分析: {{ANALYSIS_利润空间}}
---
四、选品建议
4.1 进入策略
{{STRATEGY_进入}}
4.2 产品定位
{{STRATEGY_定位}}
4.3 定价建议
{{STRATEGY_定价}}
4.4 风险提示
{{RISK_提示}}
---
五、数据文件说明
本报告配套数据文件保存在 data/ 目录中:
| 文件 | 说明 |
|---|---|
statistics.csv | 统计数据 (销量、销额、价格等) |
products.csv | Top 产品完整列表 |
scores.csv | 五维评分详细数据 |
raw_data.json | 原始 JSON 数据 |
---
六、其他格式报告
- Excel 报告:
category_analysis_report.xlsx- 包含多个工作表的详细分析 - HTML 仪表板:
dashboard.html- 可视化交互式报告 - JSON 数据:
data/raw_data.json- 用于二次处理
---
本报告由 Category-Selection Skill 自动生成 数据基于 Sorftime MCP 实时 API 建议定期更新分析以跟踪市场变化
Category-Selection Skill 变更日志
[11.0.0] - 2026-03-05
v4.0 更新 - 重大 Bug 修复和稳定性改进
背景: 在实际使用中发现多个问题,包括 API Key 配置、JSON 解析失败、类目搜索失败等。本次更新系统性地修复了所有已知问题。
主要改进
1. 自动 API Key 配置 ✅
问题: 需要手动设置环境变量 SORFTIME_API_KEY,用户体验不友好
修复:
- 新增
get_api_key()函数,自动从.mcp.json读取 API Key - 支持多源配置:环境变量 > .mcp.json 配置文件
- 添加 API Key 有效性检查和友好错误提示
# 代码示例
def get_api_key():
# 1. 尝试环境变量
api_key = os.environ.get('SORFTIME_API_KEY', '')
if api_key:
return api_key
# 2. 尝试从 .mcp.json 读取
mcp_config_path = os.path.join(PROJECT_ROOT, '.mcp.json')
if os.path.exists(mcp_config_path):
with open(mcp_config_path, 'r') as f:
config = json.load(f)
sorftime_url = config.get('mcpServers', {}).get('sorftime', {}).get('url', '')
if 'key=' in sorftime_url:
return sorftime_url.split('key=')[-1]
return ''影响: 用户无需配置环境变量,开箱即用
---
2. JSON 字符串值中未转义控制字符修复 ✅
问题: JSONDecodeError: Invalid control character at: line 1 column 3401
根本原因: API 返回的 JSON 字符串值中包含原始的换行符(\n)、制表符(\t)等控制字符,这些字符没有被正确转义为 \n、\t 序列
示例:
// API 返回的原始格式(错误)
{"标题": "类目:Renewed Laptops,排名:2
类目:Traditional Laptops,排名:11"}
// 正确格式
{"标题": "类目:Renewed Laptops,排名:2\\n类目:Traditional Laptops,排名:11"}修复: 新增 escape_control_chars_in_json_strings() 函数
def escape_control_chars_in_json_strings(json_str):
"""
转义 JSON 字符串值中的控制字符
只处理字符串值内部,不影响 JSON 结构
"""
result = []
in_string = False
escape_next = False
for c in json_str:
if escape_next:
result.append(c)
escape_next = False
elif c == '\\':
result.append(c)
escape_next = True
elif c == '"':
in_string = not in_string
result.append(c)
elif in_string and c == '\n':
result.append('\\n') # 转义换行符
elif in_string and c == '\r':
result.append('\\r') # 转义回车符
elif in_string and c == '\t':
result.append('\\t') # 转义制表符
else:
result.append(c)
return ''.join(result)影响: 所有包含换行符的 JSON 响应现在可以正确解析
---
3. 改进类目搜索策略 ✅
问题: 类目搜索失败,特别是 "Laptops" 和 "Computers" 等大类目
修复:
- 自动尝试多种搜索变体
- 支持模糊匹配和关键词变体
- 当返回多个类目时,自动使用第一个类目
- 添加搜索失败时的友好提示
# 自动尝试的搜索变体
search_variants = [
self.category, # 原始输入
self.category.replace(' & ', ' '), # 移除 & 符号
self.category.split(' ')[0], # 第一个词
self.category.rstrip('s'), # 移除复数
]影响: 类目搜索成功率显著提高
---
4. 执行日志和调试支持 ✅
问题: 难以追踪执行过程和定位问题
新增:
- 执行日志自动保存到
execution.log - 详细的错误信息和上下文
- 时间戳记录每个操作
- DEBUG、INFO、WARN、ERROR 级别
def log(self, message: str, level: str = 'INFO'):
"""记录日志"""
timestamp = datetime.now().strftime('%H:%M:%S')
log_entry = f"[{timestamp}] [{level}] {message}"
self.execution_log.append(log_entry)影响: 问题诊断更容易
---
5. 错误处理增强 ✅
问题: 错误信息不明确,难以定位问题
改进:
- API Key 未检查时提供明确的配置指引
- JSON 解析失败时保存调试信息到
parse_debug.txt - 认证失败时提供明确的错误提示
- 所有 API 调用都有超时处理
影响: 用户体验更好,问题更容易解决
---
故障排查指南更新
在 SKILL.md 中新增详细的故障排查章节,包括:
1. API Key 未设置 - 解释两种配置方式和自动加载逻辑 2. JSON 解析失败 - 控制字符 - 详细说明根本原因和修复方法 3. 类目未找到 - 提供多种解决方案 4. Mojibake 编码问题 - 手动修复方法 5. Python dict 格式问题 - 修复说明 6. 大类目搜索失败 - 工作流程建议
---
文件更新
| 文件 | 版本 | 更新内容 |
|---|---|---|
workflow.py | v4.0 | ✅ 自动 API Key 加载<br>✅ 控制字符转义修复<br>✅ 改进类目搜索<br>✅ 执行日志<br>✅ 错误处理增强 |
SKILL.md | v4.0 | ✅ 更新 API Key 配置说明<br>✅ 新增控制字符问题排查<br>✅ 更新故障排查指南<br>✅ 版本号更新到 v4.0 |
---
兼容性
- 完全向后兼容 v3.x
- 无需修改现有配置
.mcp.json配置自动识别
---
测试验证
已使用以下类目进行测试验证:
- ✅ Traditional Laptop Computers (NodeID: 13896615011)
- 月销额: $86,231,118.58
- 产品数量: 100
- 五维评分: 74/100 (良好)
---
[10.0.0] - 2026-03-04
标准化版本 - 统一评分标准与数据结构
背景: 解决多个脚本中五维评分标准不一致的问题,统一数据结构和报告生成流程。
主要改进
1. 统一五维评分标准
- 问题: workflow.py、data_utils.py、parse_category_report.py 中的评分逻辑不一致
- 修复: 统一所有脚本的评分标准为:
- 市场规模 (20分): >$10M=20, >$5M=17, >$1M=14, 其他=10
- 增长潜力 (25分): 低评论占比>40%=22, >20%=18, 其他=14
- 竞争烈度 (20分): Top3<30%=18, <50%=14, 其他=8
- 进入壁垒 (20分): Amazon占比+新品机会组合 (0-20分)
- 利润空间 (15分): 均价>$300=12, >$150=10, >$50=7, 其他=4
- 影响: 所有报告现在使用一致的评分标准
2. 优化进入壁垒评分逻辑
- 旧逻辑: 基于平均评论数和Amazon占比的组合判断
- 新逻辑: Amazon占比评分 (0-10分) + 新品机会评分 (0-10分)
- Amazon占比: <20%=10分, <40%=6分, 其他=3分
- 新品机会: 低评论产品>40%=10分, >20%=6分, 其他=3分
- 影响: 评分更加透明,易于理解和调整
3. 统一利润空间评分标准
- 旧标准: 基于 $25/$15/$8 的价格阈值
- 新标准: 基于 $300/$150/$50 的价格阈值
- 影响: 更符合亚马逊实际品类价格分布
4. SKILL.md 文档重构
- 添加详细的五维评分标准说明
- 完善数据处理流程文档
- 更新故障排查指南
- 添加数据字段映射表
- 优化报告输出结构说明
文件更新
SKILL.md- 完全重写,添加标准化说明workflow.py- 更新评分函数,统一标准data_utils.py- 确认评分标准一致性
---
[4.1.0] - 2026-03-03
Bug 修复 - 一体化分析脚本
背景: 优化分析流程,解决数据处理、编码和报告生成的多个问题。
修复内容
1. SSE 响应解析修复
- 问题:
codecs.decode(text, 'unicode-escape')错误地二次解码已由 JSON 解码的中文字符 - 修复: 移除不必要的 unicode-escape 解码,JSON 解析器已正确处理 Unicode 转义
- 影响: 中文键名 (
Top100产品,类目统计报告) 现在可以正确提取
2. JSON 对象提取逻辑修复
- 问题: 解析器查找最后一个 JSON 对象,但产品数据在第一个对象中
- 修复: 改为查找第一个完整的 JSON 对象
- 影响: 产品列表 (100个产品) 现在可以正确提取
3. 数值格式化修复
- 问题: 模板变量替换时对字符串值使用数字格式 (
,) 导致错误 - 修复: 添加
_safe_float()和_safe_int()方法安全转换数值 - 影响: 价格、销量等数值现在可以正确格式化显示
4. Excel Font 作用域问题修复
- 问题:
OpenpyxlFont在generate_excel()方法内导入,但辅助方法无法访问 - 修复: 将 Font/PatternFill 类作为参数传递给辅助方法
- 影响: Excel 报告现在可以正常生成
新增功能
一体化分析脚本 (analyze_category.py)
一个命令完成完整的品类分析流程:
python .claude/skills/category-selection/scripts/analyze_category.py "品类名称" [站点] [数量]功能特点:
- 自动搜索类目获取 nodeId
- 调用 category_report API
- 解析 SSE 响应和中文编码
- 计算五维评分
- 生成所有格式报告 (Markdown, Excel, HTML, CSV, JSON)
报告输出结构:
category-reports/
└── YYYY/MM/
└── {品类名}_{站点}/
├── category_analysis_report.md
├── category_analysis_report.xlsx
├── dashboard.html
└── data/
├── statistics.csv
├── products.csv
├── scores.csv
└── raw_data.json技术细节
SSE 解析流程
# 旧代码 (错误):
decoded = codecs.decode(text, 'unicode-escape') # 二次解码导致乱码
# 新代码 (正确):
decoded = text # JSON 已自动解码 Unicode 转义JSON 对象提取
# 旧代码:
last_obj_start = decoded.rfind('{') # 查找最后一个对象
# 新代码:
first_obj_start = decoded.find('{') # 查找第一个对象 (包含产品数据)支持的亚马逊站点
US, GB, DE, FR, IN, CA, JP, ES, IT, MX, AE, AU, BR, SA
已知限制
- 部分统计数据包含中文描述前缀 (如 "销量前的80%产品平均价格:")
- 模板中的部分变量 (如
{{SCORE_建议}},{{ANALYSIS_*}}) 尚未实现
---
[4.0.0] - 2026-03-03
重大重构 - MCP 风格化
背景: 原版本使用 Python 脚本绕过 MCP 服务器直接调用 API,与 MCP 设计理念不符。
变更内容
删除的文件
scripts/sorftime_client.py- 独立的 HTTP 客户端(绕过 MCP)scripts/sorftime_parser.py- SSE 响应解析器(MCP 已处理)scripts/analyze.py- 主分析脚本(由 SKILL.md 替代)scripts/category_analysis_template.py- 模板脚本scripts/__pycache__/- Python 缓存目录
重写的文件
SKILL.md- 完全重写为 MCP 风格,与amazon-analyse保持一致
架构变化
旧架构 (v3.x):
Claude Code
↓
运行 Python 脚本 (analyze.py)
↓
SorftimeMCPClient (直接 HTTP 请求)
↓
Sorftime API (绕过 MCP)
↓
自定义解析器新架构 (v4.0):
Claude Code
↓
MCP 工具调用 (curl via Bash)
↓
Sorftime MCP 服务器
↓
SSE 响应
↓
Claude Code 解析功能保持
以下功能保持不变,继续提供:
必需工具
1. category_name_search - 搜索类目获取 nodeId 2. category_report - 获取类目 Top100 产品和统计数据 3. product_detail - 获取产品详情
可选工具
4. category_keywords - 获取类目核心关键词 5. products_1688 - 1688 采购成本分析
保留的辅助工具
scripts/data_utils.py- 数据处理工具(HHI、分组、评分计算等)scripts/generate_excel_report.py- Excel 报告生成(可选)
SKILL.md 主要变化
| 章节 | v3.x | v4.0 |
|---|---|---|
| MCP 调用 | 描述 Python 脚本 | 描述 curl 调用 MCP |
| 数据解析 | 导入 Python 模块 | Claude Code 直接处理 |
| 工具参考 | 混合描述 | 统一 curl 格式 |
| 报告生成 | Python 脚本 | Write 工具 |
五维评分计算
评分逻辑保持不变:
| 维度 | 分值 | 数据来源 |
|---|---|---|
| 市场规模 | 20分 | top100产品月销额 |
| 增长潜力 | 25分 | low_reviews_sales_volume_share |
| 竞争烈度 | 20分 | top3_brands_sales_volume_share |
| 进入壁垒 | 20分 | amazonOwned + low_reviews |
| 利润空间 | 15分 | average_price |
兼容性
- 与
amazon-analyseskill 保持一致的 MCP 调用风格 - 支持相同的亚马逊站点 (US, GB, DE, FR, CA, JP, ES, IT, MX, AE, AU, BR, SA)
- 使用相同的 Sorftime MCP 配置
迁移指南
如果用户之前使用 analyze.py 脚本,现在可以直接使用 /category-select 命令:
旧方式:
python .claude/skills/category-selection/scripts/analyze.py "Sofas" --site US --limit 20新方式:
/category-select "Sofas" US --limit 20---
[3.0.0] - 2026-03-02
新增
- 添加 sorftime_parser.py 内置解析器
- 修复 Unicode 转义中文解析问题
- 修复 JSON 嵌套和控制字符问题
- 添加大文件处理方案
---
[2.0.0] - 2026-03-01
初始版本
- 基础品类选品分析功能
- 五维评分模型
- Python 脚本驱动架构
Sorftime MCP API 快速参考
品类选品分析常用接口
1. category_name_search - 搜索类目
curl -s -X POST "https://mcp.sorftime.com?key={API_KEY}" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"category_name_search","arguments":{"amzSite":"US","searchName":"Sofas"}}}'返回关键数据: NodeId (用于后续调用)
---
2. category_report - 类目报告 (核心)
curl -s -X POST "https://mcp.sorftime.com?key={API_KEY}" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"category_report","arguments":{"amzSite":"US","nodeId":"3733551"}}}'返回数据:
Top100产品[]: 产品列表 (ASIN, 标题, 价格, 月销量, 星级, 品牌, 评论数, 卖家来源等)类目统计报告: 统计数据
关键统计字段:
| 字段名 | 说明 | 用途 |
|---|---|---|
top100产品月销量 | Top100 总销量 | 市场规模 |
top100产品月销额 | Top100 总销额 | 市场规模 |
average_price | 平均价格 | 定价参考 |
top3_brands_sales_volume_share | Top3 品牌占比 | 竞争集中度 |
amazonOwned_sales_volume_share | Amazon 自营占比 | 平台压力 |
low_reviews_sales_volume_share | 低评论产品占比 | 新品机会 |
---
3. product_detail - 产品详情
curl -s -X POST "https://mcp.sorftime.com?key={API_KEY}" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"product_detail","arguments":{"amzSite":"US","asin":"B0DDTCQGTR"}}}'返回关键数据: 标题, 主图URL, 价格, 星级, 评论数, 品牌, 上线日期, 月销量, 产品描述等
---
4. category_keywords - 类目关键词
curl -s -X POST "https://mcp.sorftime.com?key={API_KEY}" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"category_keywords","arguments":{"amzSite":"US","nodeId":"3733551","page":1}}}'返回关键数据:
关键词: 关键词周搜索排名: 搜索排名月搜索量: 月搜索量cpc精准竞价: PPC 竞价
---
SSE 响应处理
响应格式
event: message
data: {"result":{"content":[{"type":"text","text":"..."}}]}Python 解码示例
import codecs
# 解码 Unicode 转义
decoded = codecs.decode(encoded_text, 'unicode-escape')---
支持的站点
| 代码 | 站点 |
|---|---|
| US | 美国 |
| GB | 英国 |
| DE | 德国 |
| FR | 法国 |
| CA | 加拿大 |
| JP | 日本 |
| ES | 西班牙 |
| IT | 意大利 |
品类选品专用接口参考
本文档列出品类选品分析相关的核心接口及调用示例。
---
一、类目搜索与确认
1. 类目名称搜索 - category_name_search
用途: 根据品类名称查找对应的类目nodeid
curl -s -X POST "https://mcp.sorftime.com?key=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"category_name_search","arguments":{"site":"US","searchName":"sofas"}}}'返回数据示例:
类目名称 | nodeId
---------|---------
Sofas | 3733551
Sofa Slipcovers | 1234567
Bean Bag Chairs | 2345678---
二、市场趋势数据 (11个指标)
趋势指标列表
| ID | 趋势类型 | trendIndex参数 | 用途 |
|---|---|---|---|
| 1 | 类目月销量趋势 | 类目月销量趋势 | 市场规模评分 |
| 2 | 品牌数量趋势 | 品牌数量趋势 | 竞争烈度评分 |
| 3 | 卖家数量趋势 | 卖家数量趋势 | 竞争烈度评分 |
| 4 | 平均售价趋势 | 平均售价趋势 | 利润空间评分 |
| 5 | 平均评论数量趋势 | 平均评论数量趋势 | 进入壁垒评分 |
| 6 | 平均星级趋势 | 平均星级趋势 | 市场成熟度 |
| 7 | 新品销量占比趋势 | 上架3个月内新品销量占比趋势 | 进入壁垒评分 |
| 8 | 亚马逊自营销量占比 | 亚马逊自营销量占比 | 竞争烈度评分 |
| 9 | Top3产品销量占比 | 销量前3的产品销量占比趋势 | 市场集中度 |
| 10 | Top3品牌销量占比 | 销量前3的品牌销量占比趋势 | 市场集中度 |
| 11 | Top3卖家销量占比 | 销量前3的卖家销量占比趋势 | 市场集中度 |
调用示例
# 并发调用11个趋势接口
curl -s -X POST "https://mcp.sorftime.com?key=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"category_trend","arguments":{"site":"US","nodeId":"3733551","trendIndex":"类目月销量趋势"}}}' &
curl -s -X POST "https://mcp.sorftime.com?key=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"category_trend","arguments":{"site":"US","nodeId":"3733551","trendIndex":"品牌数量趋势"}}}' &
# ... 继续其他9个接口---
三、Top100产品数据
类目报告 - category_report
用途: 获取品类Top100产品列表
curl -s -X POST "https://mcp.sorftime.com?key=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"category_report","arguments":{"site":"US","nodeId":"3733551"}}}'返回数据字段:
| 字段 | 说明 |
|---|---|
| ASIN | 产品ASIN |
| Title | 产品标题 |
| Brand | 品牌 |
| Price | 价格 |
| Rating | 评分 |
| ReviewCount | 评论数 |
| MonthlySales | 月销量 |
---
四、产品详情批量获取
产品详情 - product_detail
用途: 获取单个产品详细信息
# 需要对100个ASIN逐个调用
curl -s -X POST "https://mcp.sorftime.com?key=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":100,"method":"tools/call","params":{"name":"product_detail","arguments":{"site":"US","asin":"B07PWTJ4H1"}}}'批量获取策略:
- 并发调用,每次最多10个
- 使用不同的id (100-199)
- 失败的ASIN跳过,记录日志
---
五、类目关键词
类目核心关键词 - category_keywords
用途: 获取类目热搜关键词
curl -s -X POST "https://mcp.sorftime.com?key=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":200,"method":"tools/call","params":{"name":"category_keywords","arguments":{"site":"US","nodeId":"3733551","page":1}}}'返回数据字段:
| 字段 | 说明 |
|---|---|
| keyword | 关键词 |
| searchVolume | 月搜索量 |
| recommendBid | 推荐竞价 |
---
六、供应链分析
1688产品搜索 - products_1688
用途: 获取1688采购价格
curl -s -X POST "https://mcp.sorftime.com?key=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":300,"method":"tools/call","params":{"name":"products_1688","arguments":{"searchName":"沙发","page":1}}}'---
七、TikTok跨平台分析
TikTok产品搜索 - tiktok_product_search
用途: 搜索TikTok相似产品
curl -s -X POST "https://mcp.sorftime.com?key=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":400,"method":"tools/call","params":{"name":"tiktok_product_search","arguments":{"site":"US","searchName":"sofa","page":1}}}'---
数据收集检查清单
- [ ] Step 1.1: 类目搜索,获取nodeid
- [ ] Step 1.2: 11个市场趋势指标 (并发)
- [ ] Step 1.3: Top100产品列表
- [ ] Step 1.4: 100个产品详情 (并发×10)
- [ ] Step 1.5: 类目关键词 (可选)
- [ ] Step 1.6: 1688采购价格 (可选)
- [ ] Step 1.7: TikTok产品搜索 (可选)
---
五维评分计算参考
1. HHI指数计算
HHI = Σ(各品牌市场份额百分比)²
示例:
品牌A: 12.56% → 12.56² = 157.75
品牌B: 9.11% → 9.11² = 82.99
品牌C: 3.55% → 3.55² = 12.60
...
HHI = 157.75 + 82.99 + 12.60 + ... = 167.712. CR3集中度计算
CR3 = Top3品牌市场份额之和
示例:
品牌A: 12.56%
品牌B: 9.11%
品牌C: 3.55%
CR3 = 12.56 + 9.11 + 3.55 = 25.22%3. 同比增长率计算
同比增长率 = (本期销量 - 去年同期销量) / 去年同期销量 × 100%
示例:
2026年2月: 1233
2025年2月: 1047
增长率 = (1233 - 1047) / 1047 × 100% = 17.76%---
最后更新: 2026-03-03
五维评分模型标准
版本: v1.0 最后更新: 2026-03-04 状态: ✅ 正式版本
---
评分维度总览
| 维度 | 分值 | 评估指标 | 数据字段 |
|---|---|---|---|
| 市场规模 | 20 分 | Top100 月销额 | top100产品月销额 |
| 增长潜力 | 25 分 | 低评论产品销量占比 | low_reviews_sales_volume_share |
| 竞争烈度 | 20 分 | Top3 品牌销量占比 | top3_brands_sales_volume_share |
| 进入壁垒 | 20 分 | Amazon 自营 + 新品机会 | amazonOwned_sales_volume_share + low_reviews_sales_volume_share |
| 利润空间 | 15 分 | 平均价格 | average_price |
| 总分 | 100 分 |
---
1. 市场规模 (20 分)
评估指标: top100产品月销额 (Top100 产品月销额)
| 销额范围 | 得分 |
|---|---|
| > $10,000,000 | 20 |
| > $5,000,000 | 17 |
| > $1,000,000 | 14 |
| 其他 | 10 |
Python 代码:
revenue = float(stats.get('top100产品月销额', 0))
if revenue > 10_000_000:
market_size_score = 20
elif revenue > 5_000_000:
market_size_score = 17
elif revenue > 1_000_000:
market_size_score = 14
else:
market_size_score = 10---
2. 增长潜力 (25 分)
评估指标: low_reviews_sales_volume_share (低评论产品销量占比,即评价数<300的产品)
| 占比范围 | 得分 | 说明 |
|---|---|---|
| > 40% | 22 | 新品空间大 |
| > 20% | 18 | 新品有机会 |
| 其他 | 14 | 新品空间有限 |
Python 代码:
low_review_share = float(stats.get('low_reviews_sales_volume_share', 0))
if low_review_share > 40:
growth_score = 22
elif low_review_share > 20:
growth_score = 18
else:
growth_score = 14---
3. 竞争烈度 (20 分)
评估指标: top3_brands_sales_volume_share (Top3 品牌销量占比)
| 占比范围 | 得分 | 竞争程度 |
|---|---|---|
| < 30% | 18 | 低度集中,机会大 |
| < 50% | 14 | 中度集中 |
| 其他 | 8 | 高度集中,竞争激烈 |
Python 代码:
top3_share = float(stats.get('top3_brands_sales_volume_share', 0))
if top3_share < 30:
competition_score = 18
elif top3_share < 50:
competition_score = 14
else:
competition_score = 8---
4. 进入壁垒 (20 分)
评估指标:
amazonOwned_sales_volume_share(Amazon 自营占比)low_reviews_sales_volume_share(新品机会)
评分逻辑: Amazon 占比越低 + 新品机会越大 = 壁垒越低
Amazon 自营影响 (0-10 分)
| 占比范围 | 得分 |
|---|---|
| < 20% | 10 |
| < 40% | 6 |
| 其他 | 3 |
新品机会影响 (0-10 分)
| 占比范围 | 得分 |
|---|---|
| > 40% | 10 |
| > 20% | 6 |
| 其他 | 3 |
总分计算
进入壁垒得分 = Amazon 自营得分 + 新品机会得分
范围: 6-20 分
Python 代码:
amazon_share = float(stats.get('amazonOwned_sales_volume_share', 0))
low_review_share = float(stats.get('low_reviews_sales_volume_share', 0))
barrier_score = 0
# Amazon 占影响分
if amazon_share < 20:
barrier_score += 10
elif amazon_share < 40:
barrier_score += 6
else:
barrier_score += 3
# 新品机会得分
if low_review_share > 40:
barrier_score += 10
elif low_review_share > 20:
barrier_score += 6
else:
barrier_score += 3---
5. 利润空间 (15 分)
评估指标: average_price (平均价格)
| 价格范围 | 得分 |
|---|---|
| > $300 | 12 |
| > $150 | 10 |
| > $50 | 7 |
| 其他 | 4 |
Python 代码:
avg_price = float(stats.get('average_price', 0))
if avg_price > 300:
profit_score = 12
elif avg_price > 150:
profit_score = 10
elif avg_price > 50:
profit_score = 7
else:
profit_score = 4---
评级判定
| 总分范围 | 评级 | 建议 |
|---|---|---|
| 80 - 100 | 优秀 | 强烈推荐进入 |
| 70 - 79 | 良好 | 可以考虑进入 |
| 50 - 69 | 一般 | 谨慎进入 |
| 0 - 49 | 较差 | 不建议进入 |
---
字段名称映射表
| 中文名称 | 英文键名 | 数据来源 |
|---|---|---|
| Top100 产品月销额 | top100产品月销额 | category_report |
| Top100 产品月销量 | top100产品月销量 | category_report |
| 平均价格 | average_price | category_report |
| 中位数价格 | median_price | category_report |
| Top3 品牌销量占比 | top3_brands_sales_volume_share | category_report |
| Amazon 自营占比 | amazonOwned_sales_volume_share | category_report |
| 高评分产品占比 | high_rated_sales_volume_share | category_report |
| 低评论产品占比 | low_reviews_sales_volume_share | category_report |
---
实现文件清单
以下文件应使用本标准:
| 文件 | 状态 | 备注 |
|---|---|---|
scripts/data_utils.py | ✅ 已修复 | calculate_five_dimension_score() |
scripts/parse_sorftime_sse.py | ✅ 正确 | calculate_scores() |
scripts/sse_decoder.py | ✅ 已添加 | calculate_five_dimension_score() |
SKILL.md | ✅ 正确 | 文档说明 |
---
测试用例
测试案例 1: Sofas 品类 (美国)
stats = {
'top100产品月销额': 24869166.89, # $24.87M
'low_reviews_sales_volume_share': 52.99, # 52.99%
'top3_brands_sales_volume_share': 19.49, # 19.49%
'amazonOwned_sales_volume_share': 6.37, # 6.37%
'average_price': 323.75
}
# 预期得分:
# 市场规模: 20 (>$10M)
# 增长潜力: 22 (>40%)
# 竞争烈度: 18 (<30%)
# 进入壁垒: 20 (10 + 10)
# 利润空间: 12 (>$300)
# 总分: 92/100 → 优秀测试案例 2: 小品类
stats = {
'top100产品月销额': 800000, # $0.8M
'low_reviews_sales_volume_share': 15, # 15%
'top3_brands_sales_volume_share': 55, # 55%
'amazonOwned_sales_volume_share': 45, # 45%
'average_price': 35
}
# 预期得分:
# 市场规模: 10 (<$1M)
# 增长潜力: 14 (<20%)
# 竞争烈度: 8 (>50%)
# 进入壁垒: 6 (3 + 3)
# 利润空间: 4 (<$50)
# 总分: 42/100 → 较差---
本文档由 Claude Code 维护 | 如有修改请同步更新所有实现文件
Sorftime MCP API 接口文档
调用方式
curl -s -X POST "https://mcp.sorftime.com?key={API_KEY}" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":N,"method":"tools/call","params":{"name":"TOOL_NAME","arguments":{...}}}'---
一、产品相关接口
1.1 产品详情 (product_detail)
调用消耗: 1
用途: 查询亚马逊电商平台上产品的详情数据
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | 亚马逊站点 US/GB/DE/FR/IN/CA/JP/ES/IT/MX/AE/AU/BR/SA |
| asin | string | 是 | 产品ASIN |
返回数据: 标题、价格、评分、评论数、品牌、类目、排名、销量等
---
1.2 产品子体明细 (product_variations)
调用消耗: 1
用途: 查询亚马逊电商平台产品的子体明细
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | 亚马逊站点 |
| asin | string | 是 | 产品ASIN(仅支持单ASIN) |
---
1.3 产品历史趋势 (product_trend)
调用消耗: 1
用途: 查询产品的历史趋势数据,支持月销量/月销额/价格/排名趋势
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | 亚马逊站点 |
| asin | string | 是 | 产品ASIN |
| productTrendType | string | 否 | 月销量趋势/月销额趋势/价格趋势/所属大类排名趋势 |
---
1.4 产品评论 (product_reviews)
调用消耗: 1
用途: 查询产品近一年的用户留评,最多返回100条
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | 亚马逊站点 |
| asin | string | 是 | 产品ASIN |
| reviewType | string | 否 | 全部(不限星级)/积极评论(4-5星)/消极评论(1-3星) |
---
1.5 产品流量关键词 (product_traffic_terms)
调用消耗: 1
用途: 产品反查关键词,返回产品在哪些关键词前3页中曝光
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | 亚马逊站点 |
| asin | string | 是 | 产品ASIN |
| page | int | 否 | 页码索引,默认第1页,每页50条 |
---
1.6 竞品关键词布局 (competitor_product_keywords)
调用消耗: 1
用途: 获取竞品在各核心关键词下的曝光位置(自然曝光)
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | 亚马逊站点 |
| asin | string | 是 | 产品ASIN |
| page | int | 否 | 页码索引,默认第1页 |
---
1.7 产品关键词排名趋势 (product_keyword_rank_trend)
调用消耗: 1
用途: 产品在指定关键词下曝光的排名趋势
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | 亚马逊站点 |
| asin | string | 是 | 产品ASIN |
| keyword | string | 是 | 关键词 |
| page | int | 否 | 页码索引,默认第1页 |
---
1.8 产品搜索 (product_search)
调用消耗: 1
用途: 搜索或筛选亚马逊产品,支持多维度筛选实现选品功能
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | 亚马逊站点 |
| searchName | string | 否 | 搜索产品名称 |
| brand | string | 否 | 筛选品牌 |
| delivery_type | string | 否 | 发货方式 |
| month_sales_volume_range | string | 否 | 月销量范围[x,y] |
| price_range | string | 否 | 价格范围[x,y] |
| property_name | string | 否 | 标题或属性包含词 |
| ratings_count_range | string | 否 | 评论数量范围[x,y] |
| ratings_range | string | 否 | 星级范围[x,y] |
| seasonal_popular_product | string | 否 | 热销旺季产品 |
| seller_name | string | 否 | 卖家名称 |
| subcategory_rank_range | string | 否 | 细分类目排名范围[x,y] |
| variation_count_range | string | 否 | 子体数量范围[x,y] |
| sortby_potential_index | string | 否 | 按潜力指数排序 |
---
1.9 潜力产品搜索 (potential_product_search)
调用消耗: 1
用途: 搜索亚马逊平台上的潜力产品
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | 支持的站点 US/GB/DE |
| searchName | string | 否 | 产品名称 |
| price_range | string | 否 | 价格范围[x,y] |
| month_sales_volume_range | string | 否 | 月销量范围[x,y] |
| delivery_type | string | 否 | 发货方式 |
---
二、类目相关接口
2.1 类目名称搜索 (category_name_search)
调用消耗: 1
用途: 基于名称查询细分类目市场,返回nodeid和name
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | 亚马逊站点 |
| searchName | string | 是 | 类目市场名称 |
---
2.2 类目树结构 (category_tree)
调用消耗: 5
用途: 查询类目产品的特点
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | 亚马逊站点 |
| searchName | string | 是 | 类目名称 |
---
2.3 细分类目报告 (category_report)
调用消耗: 1
用途: 细分类目实时数据报告,基于Top100产品统计
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | 亚马逊站点 |
| nodeId | string | 否 | 细分类目nodeid |
---
2.4 细分类目历史报告 (category_history_report)
调用消耗: 1
用途: 细分类目历史指定时间段数据报告
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | 亚马逊站点 |
| nodeId | string | 否 | 细分类目nodeid |
| startDate | string | 是 | 起始时间(yyyy-MM-dd) |
| endDate | string | 否 | 截止时间,最长40天 |
---
2.5 类目趋势 (category_trend)
调用消耗: 1
用途: 查询类目市场趋势数据,基于Top100统计
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | 亚马逊站点 |
| nodeId | string | 是 | 细分类目nodeid |
| trendIndex | string | 是 | 趋势类型(见下方) |
趋势类型 (trendIndex):
- 类目月销量趋势
- 品牌数量趋势
- 卖家数量趋势
- 平均售价趋势
- 平均评论数量趋势
- 平均星级趋势
- 上架3个月内新品销量占比趋势
- 亚马逊自营销量占比趋势
- 销量前3的产品销量占比趋势
- 销量前3的品牌销量占比趋势
- 销量前3的卖家销量占比趋势
---
2.6 类目市场搜索 (category_market_search)
调用消耗: 1
用途: 查询或搜索细分类目市场
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | 亚马逊站点 |
| searchName | string | 否 | 类目市场名称 |
| month_sales_volume_range | string | 否 | 月销量范围[x,y] |
| ratings_range | string | 否 | 星级范围[x,y] |
| ratings_count_range | string | 否 | 评论数范围[x,y] |
| price_range | string | 否 | 平均销售价范围[x,y] |
| seasonal_popular_product | string | 否 | 热销旺季 |
| top3Product_sales_share | string | 否 | Top3产品销量占比x,y |
| amazonOwned_sales_share | string | 否 | 亚马逊自营占比x,y |
| top100_top400_sales_share | string | 否 | Top100在Top400占比x,y |
| newproduct_sales_share | string | 否 | 新品销量占比x,y |
---
2.7 类目核心关键词 (category_keywords)
调用消耗: 1
用途: 查询细分类目市场的核心关键词
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | 亚马逊站点 |
| nodeId | string | 是 | 细分类目nodeid |
| page | int | 否 | 页码索引,默认第1页 |
---
三、关键词相关接口
3.1 关键词详情 (keyword_detail)
调用消耗: 1
用途: 查询热搜关键词详情
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | 亚马逊站点 |
| keyword | string | 是 | 查询的关键词 |
---
3.2 关键词搜索结果 (keyword_search_result)
调用消耗: 1
用途: 查询关键词搜索结果自然位产品清单
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | 亚马逊站点 |
| searchKeyword | string | 是 | 查询的关键词 |
| page | int | 否 | 页码索引,默认第1页 |
---
3.3 关键词历史趋势 (keyword_trend)
调用消耗: 1
用途: 查询关键词历史趋势(搜索量/搜索排名/CPC价格)
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | 亚马逊站点 |
| searchKeyword | string | 是 | 查询的关键词 |
---
3.4 关键词延伸词 (keyword_related_words)
调用消耗: 1
用途: 查询关键词的延伸词,用于发现长尾词
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | 亚马逊站点 |
| searchKeyword | string | 是 | 查询的关键词 |
| page | int | 否 | 页码索引,默认第1页 |
---
四、关键词词库管理接口
4.1 添加关键词收藏 (add_keyword)
调用消耗: 1
参数: site, keyword, dict(可选)
---
4.2 移动关键词到收藏夹 (move_keyword)
调用消耗: 1
参数: site, keyword, toDict, fromDict(可选)
---
4.3 删除关键词收藏 (remove_keyword)
调用消耗: 1
参数: site, keyword, dict(可选)
---
4.4 查询收藏夹列表 (query_keyword_dict_list)
调用消耗: 1
参数: site, page
---
4.5 查询收藏的词 (query_keyword_dict)
调用消耗: 1
参数: site, dict(可选,all查询全部), page
---
五、1688 供货平台接口
5.1 1688产品搜索 (products_1688)
调用消耗: 1
用途: 通过1688平台找产品的采购货源,分析产品采购成本价
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| searchName | string | 是 | 查询的产品名称 |
| page | int | 否 | 页码索引,默认第1页,每页50条 |
---
六、TikTok 电商平台接口
6.1 TikTok产品搜索 (tiktok_product_search)
调用消耗: 1
用途: 查询产品在TikTok平台上的相似产品,分析销售情况
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | TikTok站点 US/GB/MY/PH/VN/ID |
| searchName | string | 是 | 查询的产品名称 |
| page | int | 是 | 页码索引,默认第1页,每页50条 |
---
6.2 TikTok产品详情 (tiktok_product_detail)
调用消耗: 1
用途: 查询TikTok平台产品详情
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | TikTok站点 US/GB/MY/PH/VN/ID |
| productId | string | 是 | 产品ID |
---
6.3 TikTok带货视频 (tiktok_product_videos)
调用消耗: 1
用途: 查询TikTok平台产品的带货视频
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | TikTok站点 US/GB/MY/PH/VN/ID |
| productId | string | 是 | 产品ID |
| page | int | 是 | 页码索引,默认第1页,每页50条 |
---
6.4 TikTok带货达人分析 (tiktok_product_influencers)
调用消耗: 1
用途: TikTok平台产品的带货达人分析
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | TikTok站点 US/GB/MY/PH/VN/ID |
| productId | string | 是 | 产品ID |
---
6.5 TikTok产品趋势 (tiktok_product_trend)
调用消耗: 1
用途: 查询TikTok平台产品趋势,返回销量、价格、星级、评论数量、新增带货视频数、新增带货达人数
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | TikTok站点 US/GB/MY/PH/VN/ID |
| productId | string | 是 | 产品ID |
---
6.6 TikTok达人搜索 (tiktok_influencer_search)
调用消耗: 1
用途: 按产品名称搜索相关带货达人
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | TikTok站点 US/GB/MY/PH/VN/ID |
| searchName | string | 是 | 搜索的产品名称 |
| page | int | 是 | 页码索引,默认第1页,每页50条 |
---
6.7 TikTok类目搜索 (tiktok_category_name_search)
调用消耗: 1
用途: 按名称搜索TikTok上相关类目市场,返回类目市场名称和nodeid
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | TikTok站点 US/GB/MY/PH/VN/ID |
| searchName | string | 是 | 搜索的产品名称 |
---
6.8 TikTok类目报告 (tiktok_category_report)
调用消耗: 1
用途: 查询TikTok电商平台指定类目的类目数据报告
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amzSite | string | 是 | TikTok站点 US/GB/MY/PH/VN/ID |
| nodeId | string | 是 | 类目市场nodeid,可通过tiktok_category_name_search获得 |
---
支持的平台站点
亚马逊 (14个站点)
US, GB, DE, FR, IN, CA, JP, ES, IT, MX, AE, AU, BR, SA
TikTok (6个站点)
US, GB, MY, PH, VN, ID
1688 供货平台
国内批发采购平台
调用限制
- 大部分接口调用消耗: 1
- category_tree: 5
- 返回数据为SSE格式,需解析
---
最后更新: 2026-03-03
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
品类选品一体化分析脚本
一个命令完成:API 调用 → 数据解析 → 报告生成
"""
import os
import sys
import json
import re
import codecs
import requests
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional
class CategoryAnalyzer:
"""品类选品一体化分析器"""
def __init__(self, api_key: Optional[str] = None):
"""初始化分析器"""
self.api_key = api_key or self._load_api_key()
self.base_url = "https://mcp.sorftime.com"
self.request_id = 0
self.category_name = None
self.site = "US"
self.limit = 100
def _load_api_key(self) -> str:
"""从配置文件加载 API Key"""
config_file = Path(".mcp.json")
if config_file.exists():
with open(config_file, 'r', encoding='utf-8') as f:
config = json.load(f)
url = config['mcpServers']['sorftime']['url']
return url.split('key=')[-1]
raise FileNotFoundError("找不到 .mcp.json 配置文件")
def _get_next_id(self) -> int:
"""获取下一个请求 ID"""
self.request_id += 1
return self.request_id
def _call_api(self, tool_name: str, arguments: Dict) -> Optional[Dict]:
"""调用 Sorftime MCP API"""
url = f"{self.base_url}?key={self.api_key}"
payload = {
'jsonrpc': '2.0',
'id': self._get_next_id(),
'method': 'tools/call',
'params': {
'name': tool_name,
'arguments': arguments
}
}
try:
response = requests.post(
url,
json=payload,
timeout=120,
headers={'Content-Type': 'application/json'}
)
if response.status_code != 200:
print(f" ✗ HTTP {response.status_code}")
return None
# 解析 SSE 响应
return self._parse_sse_response(response.text)
except Exception as e:
print(f" ✗ 异常: {e}")
return None
def _parse_sse_response(self, raw_text: str):
"""解析 SSE 响应,支持对象和数组"""
try:
lines = raw_text.split('\n')
for line in lines:
if line.startswith('data: '):
json_data = line[6:] # 去掉 'data: ' 前缀
data = json.loads(json_data)
if 'error' in data:
return None
if 'result' in data:
result = data['result']
if 'content' in result and len(result['content']) > 0:
content = result['content'][0]
if 'text' in content:
text = content['text']
if not text:
continue
# JSON 已经自动解码了 Unicode 转义,不需要再用 codecs.decode
# 直接使用 text 即可
decoded = text
# 查找第一个完整的 JSON 对象(包含产品数据)
first_obj_start = decoded.find('{')
if first_obj_start != -1:
depth = 0
end = -1
for i in range(first_obj_start, len(decoded)):
if decoded[i] == '{':
depth += 1
elif decoded[i] == '}':
depth -= 1
if depth == 0:
end = i + 1
break
if end != -1:
json_str = decoded[first_obj_start:end]
json_str = re.sub(r'[\x00-\x1f\x7f-\x9f]', '', json_str)
return json.loads(json_str)
# 如果没找到对象,尝试数组
first_arr_start = decoded.find('[')
if first_arr_start != -1:
depth = 0
end = -1
for i in range(first_arr_start, len(decoded)):
if decoded[i] == '[':
depth += 1
elif decoded[i] == ']':
depth -= 1
if depth == 0:
end = i + 1
break
if end != -1:
json_str = decoded[first_arr_start:end]
json_str = re.sub(r'[\x00-\x1f\x7f-\x9f]', '', json_str)
return json.loads(json_str)
return None
except Exception as e:
print(f" ✗ SSE 解析异常: {e}")
return None
def _fix_chinese_keys(self, obj):
"""修复中文键名的编码问题"""
if isinstance(obj, dict):
new_dict = {}
for key, value in obj.items():
fixed_key = self._fix_key(key)
new_dict[fixed_key] = self._fix_chinese_keys(value)
return new_dict
elif isinstance(obj, list):
return [self._fix_chinese_keys(item) for item in obj]
else:
return obj
def _fix_key(self, key: str) -> str:
"""修复单个键名的编码问题"""
if not isinstance(key, str):
return key
# 检查是否包含高字节字符 (可能是编码问题)
if not any(0x80 <= ord(c) <= 0xFF for c in key):
return key
# 方法1: 尝试 latin-1 -> utf-8
try:
return key.encode('latin-1').decode('utf-8')
except:
pass
# 方法2: 尝试 ISO-8859-1 -> utf-8
try:
return key.encode('iso-8859-1').decode('utf-8')
except:
pass
# 方法3: 尝试 cp1252 -> utf-8
try:
return key.encode('cp1252').decode('utf-8')
except:
pass
# 都失败了,返回原键
return key
def search_category(self, category_name: str, site: str = "US") -> Optional[str]:
"""搜索品类获取 nodeId"""
print(f"[1/6] 搜索类目: {category_name} ({site})")
result = self._call_api('category_name_search', {
'amzSite': site,
'searchName': category_name
})
if not result:
print(f" ✗ 未找到类目: {category_name}")
return None
# 处理不同的返回格式
categories = []
if isinstance(result, list):
categories = result
elif isinstance(result, str):
# 如果是字符串,尝试解析为 JSON
try:
categories = json.loads(result)
except:
print(f" ✗ 无法解析类目数据")
return None
elif isinstance(result, dict):
# 如果是字典,可能是单条结果
categories = [result]
if not categories:
print(f" ✗ 类目列表为空")
return None
# 选择第一个类目
selected = categories[0]
node_id = selected.get('NodeId') or selected.get('nodeId')
name = selected.get('Name') or selected.get('name')
# 保存品类名称
self.category_name = name if name else category_name
print(f" ✓ 找到类目: {self.category_name} (nodeId: {node_id})")
return node_id
def get_category_report(self, node_id: str) -> Optional[Dict]:
"""获取类目报告"""
print(f"[2/6] 获取类目报告...")
result = self._call_api('category_report', {
'amzSite': self.site,
'nodeId': node_id
})
if not result:
print(f" ✗ 获取类目报告失败")
return None
result = self._fix_chinese_keys(result)
# 统计信息
stats = result.get('类目统计报告', {})
products = result.get('Top100产品', [])
print(f" ✓ 类目报告获取成功")
print(f" - 产品数量: {len(products)}")
return result
def extract_and_analyze(self, report_data: Dict) -> Dict:
"""提取数据并分析"""
print(f"[3/6] 提取和分析数据...")
# 提取统计数据
stats = report_data.get('类目统计报告', {})
# 提取产品列表
products = report_data.get('Top100产品', [])[:self.limit]
# 计算评分
scores = self._calculate_scores(stats)
print(f" ✓ 数据提取完成")
print(f" - 总销量: {stats.get('top100产品月销量', 'N/A')}")
print(f" - 平均价格: {stats.get('average_price', 'N/A')}")
return {
'category_name': self.category_name,
'site': self.site,
'limit': self.limit,
'statistics': stats,
'products': products,
'scores': scores,
'timestamp': datetime.now().isoformat()
}
def _calculate_scores(self, stats: Dict) -> Dict:
"""计算五维评分"""
def safe_float(value, default=0):
try:
return float(str(value).replace('%', '').replace(',', ''))
except:
return default
revenue = safe_float(stats.get('top100产品月销额', 0))
top3_share = safe_float(stats.get('top3_brands_sales_volume_share', 0))
amazon_share = safe_float(stats.get('amazonOwned_sales_volume_share', 0))
low_review_share = safe_float(stats.get('low_reviews_sales_volume_share', 0))
avg_price = safe_float(stats.get('average_price', 0))
scores = {}
# 市场规模
if revenue > 10000000:
scores['市场规模'] = 20
elif revenue > 5000000:
scores['市场规模'] = 17
elif revenue > 1000000:
scores['市场规模'] = 14
else:
scores['市场规模'] = 10
# 增长潜力
if low_review_share > 40:
scores['增长潜力'] = 22
elif low_review_share > 20:
scores['增长潜力'] = 18
else:
scores['增长潜力'] = 14
# 竞争烈度
if top3_share < 30:
scores['竞争烈度'] = 18
elif top3_share < 50:
scores['竞争烈度'] = 14
else:
scores['竞争烈度'] = 8
# 进入壁垒
barrier_score = 0
if amazon_share < 20:
barrier_score += 10
elif amazon_share < 40:
barrier_score += 6
else:
barrier_score += 3
if low_review_share > 40:
barrier_score += 10
elif low_review_share > 20:
barrier_score += 6
else:
barrier_score += 3
scores['进入壁垒'] = barrier_score
# 利润空间
if avg_price > 300:
scores['利润空间'] = 12
elif avg_price > 150:
scores['利润空间'] = 10
elif avg_price > 50:
scores['利润空间'] = 7
else:
scores['利润空间'] = 4
scores['总分'] = sum(scores.values())
if scores['总分'] >= 80:
scores['评级'] = '优秀'
elif scores['总分'] >= 70:
scores['评级'] = '良好'
elif scores['总分'] >= 50:
scores['评级'] = '一般'
else:
scores['评级'] = '较差'
return scores
def generate_reports(self, data: Dict) -> Path:
"""生成所有格式的报告"""
print(f"[4/6] 生成报告...")
# 导入报告生成器
try:
from generate_reports import CategoryReportGenerator
except ImportError:
print(" ✗ 报告生成模块未找到")
return None
# 创建输出目录
date_str = datetime.now().strftime('%Y/%m')
safe_name = self._sanitize_filename(self.category_name)
output_dir = Path('category-reports') / date_str / f"{safe_name}_{self.site}"
# 生成报告
generator = CategoryReportGenerator(data, str(output_dir))
report_files = generator.generate_all()
print(f" ✓ 报告已保存到: {output_dir}")
# 打印文件列表
for format_type, path in report_files.items():
print(f" [{format_type.upper()}] {path}")
return output_dir
def _sanitize_filename(self, name: str) -> str:
"""清理文件名"""
illegal_chars = '<>:"/\\|?*'
for char in illegal_chars:
name = name.replace(char, '_')
name = name.replace(' ', '_')
return name[:50]
def analyze(self, category_name: str, site: str = "US", limit: int = 100) -> bool:
"""
执行完整的品类分析
Args:
category_name: 品类名称
site: 亚马逊站点
limit: 分析产品数量
Returns:
是否成功
"""
start_time = datetime.now()
print("=" * 70)
print(f"品类选品分析")
print("=" * 70)
print(f"品类: {category_name}")
print(f"站点: {site}")
print(f"分析数量: Top{limit}")
print(f"开始时间: {start_time.strftime('%Y-%m-%d %H:%M:%S')}")
print("=" * 70)
self.site = site
self.limit = limit
# 步骤 1: 搜索品类
node_id = self.search_category(category_name, site)
if not node_id:
return False
# 步骤 2: 获取类目报告
report_data = self.get_category_report(node_id)
if not report_data:
return False
# 步骤 3: 提取和分析数据
data = self.extract_and_analyze(report_data)
# 步骤 4: 生成报告
output_dir = self.generate_reports(data)
# 完成
end_time = datetime.now()
duration = (end_time - start_time).total_seconds()
print("\n" + "=" * 70)
print("分析完成!")
print("=" * 70)
print(f"总耗时: {duration:.1f} 秒")
print(f"输出目录: {output_dir}")
print(f"数据时间: {data.get('timestamp', '')}")
print(f"综合评级: {data['scores'].get('评级', 'N/A')} ({data['scores'].get('总分', 0)}/100)")
print("=" * 70)
return True
def main():
"""命令行入口"""
if len(sys.argv) < 2:
print("用法: python analyze_category.py <品类名称> [站点] [分析数量]")
print("\n示例:")
print(" python analyze_category.py \"Phone Cases\" US 20")
print(" python analyze_category.py Sofas US 50")
print("\n参数:")
print(" 品类名称 - 必填,要分析的品类名称")
print(" 站点 - 可选,默认 US")
print(" 分析数量 - 可选,默认 100")
sys.exit(1)
category_name = sys.argv[1]
site = sys.argv[2] if len(sys.argv) > 2 else "US"
limit = int(sys.argv[3]) if len(sys.argv) > 3 else 100
# 执行分析
analyzer = CategoryAnalyzer()
success = analyzer.analyze(category_name, site, limit)
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
数据结构适配器
将 sse_decoder.py 输出的中文键数据转换为 generate_excel_report.py 期望的英文键格式
"""
import json
import os
from typing import Dict, List
class DataAdapter:
"""将中文键数据转换为英文键数据"""
# 评分键名映射
SCORE_KEY_MAP = {
'市场规模': 'market_size',
'增长潜力': 'growth_potential',
'竞争烈度': 'competition',
'进入壁垒': 'entry_barrier',
'利润空间': 'profit_margin',
'总分': 'total',
'评级': 'rating'
}
# 统计数据键名映射
STATS_KEY_MAP = {
'类目名称': 'category_name',
'nodeid': 'node_id',
'top100产品月销量': 'total_monthly_sales',
'top100产品月销额': 'total_monthly_revenue',
'average_price': 'average_price',
'median_price': 'median_price',
'top3_brands_sales_volume_share': 'top3_brand_share',
'amazonOwned_sales_volume_share': 'amazon_own_share',
'high_rated_sales_volume_share': 'high_rated_share',
'low_reviews_sales_volume_share': 'low_reviews_share'
}
# 产品键名映射
PRODUCT_KEY_MAP = {
'ASIN': 'asin',
'标题': 'title',
'品牌': 'brand',
'价格': 'price',
'星级': 'rating',
'评论数': 'review_count',
'月销量': 'monthly_sales',
'月销额': 'monthly_revenue',
'卖家': 'seller',
'卖家来源': 'seller_source',
'上架天数': 'days_online',
'类目排名': 'category_rank',
'图片': 'image'
}
@classmethod
def convert_scores(cls, scores: Dict) -> Dict:
"""转换评分数据"""
converted = {}
for cn_key, value in scores.items():
en_key = cls.SCORE_KEY_MAP.get(cn_key, cn_key)
converted[en_key] = value
return converted
@classmethod
def convert_stats(cls, stats: Dict) -> Dict:
"""转换统计数据"""
converted = {}
for cn_key, value in stats.items():
en_key = cls.STATS_KEY_MAP.get(cn_key, cn_key)
converted[en_key] = value
return converted
@classmethod
def convert_products(cls, products: List[Dict]) -> List[Dict]:
"""转换产品列表"""
converted_list = []
for p in products:
converted = {}
for cn_key, value in p.items():
en_key = cls.PRODUCT_KEY_MAP.get(cn_key, cn_key)
converted[en_key] = value
converted_list.append(converted)
return converted_list
@classmethod
def adapt_for_excel(cls, data_dir: str) -> Dict:
"""
将 sse_decoder 输出的数据转换为 generate_excel_report 期望的格式
Args:
data_dir: 包含 data.json, top_products.json, scores.json 的目录
Returns:
适配后的数据字典
"""
# 读取数据文件
data_file = os.path.join(data_dir, 'data.json')
products_file = os.path.join(data_dir, 'top_products.json')
scores_file = os.path.join(data_dir, 'scores.json')
with open(data_file, 'r', encoding='utf-8') as f:
raw_data = json.load(f)
with open(products_file, 'r', encoding='utf-8') as f:
raw_products = json.load(f)
with open(scores_file, 'r', encoding='utf-8') as f:
raw_scores = json.load(f)
# 提取统计数据
stats = raw_data.get('类目统计报告', {})
# 转换评分
five_dimension_score = cls.convert_scores(raw_scores)
# 转换产品列表
top100_products = cls.convert_products(raw_products)
# 计算市场份额
total_revenue = sum(p.get('monthly_revenue', 0) for p in top100_products)
for p in top100_products:
p['market_share'] = (p.get('monthly_revenue', 0) / total_revenue * 100) if total_revenue > 0 else 0
# 构建品牌分析
brand_data = cls._analyze_brands(top100_products)
# 构建价格分布
price_distribution = cls._analyze_price_distribution(top100_products)
# 构建评分分布
rating_distribution = cls._analyze_rating_distribution(top100_products)
# 构建卖家分布
seller_distribution = cls._analyze_seller_distribution(top100_products)
# 构建新产品分析
new_products = cls._filter_new_products(top100_products)
# 构建KPI
kpi = cls._build_kpi(stats, top100_products, brand_data)
# 构建最终数据结构
adapted_data = {
'category_name': stats.get('类目名称', ''),
'site': 'US', # 默认,可以从外部传入
'node_id': stats.get('nodeid', ''),
'five_dimension_score': five_dimension_score,
'kpi': kpi,
'top100_products': top100_products,
'brand_analysis': brand_data,
'price_distribution': price_distribution,
'rating_distribution': rating_distribution,
'seller_distribution': seller_distribution,
'new_products': new_products,
# 趋势数据 (占位符)
'sales_trend': {'dates': [], 'sales': []},
'price_trend': {'dates': [], 'prices': []},
'rating_trend': {'dates': [], 'ratings': []},
'brand_count_trend': {'dates': [], 'count': []}
}
return adapted_data
@staticmethod
def _analyze_brands(products: List[Dict]) -> List[Dict]:
"""分析品牌数据"""
brands = {}
for p in products:
brand = p.get('brand', 'Unknown')
if brand not in brands:
brands[brand] = {
'brand': brand,
'product_count': 0,
'monthly_sales': 0,
'monthly_revenue': 0,
'ratings': []
}
brands[brand]['product_count'] += 1
brands[brand]['monthly_sales'] += p.get('monthly_sales', 0)
brands[brand]['monthly_revenue'] += p.get('monthly_revenue', 0)
if p.get('rating'):
brands[brand]['ratings'].append(p['rating'])
# 计算市场份额和平均评分
total_revenue = sum(b['monthly_revenue'] for b in brands.values())
result = []
for b in brands.values():
b['market_share'] = (b['monthly_revenue'] / total_revenue * 100) if total_revenue > 0 else 0
b['avg_rating'] = sum(b['ratings']) / len(b['ratings']) if b['ratings'] else 0
result.append(b)
# 按市场份额排序
result.sort(key=lambda x: x['market_share'], reverse=True)
return result
@staticmethod
def _analyze_price_distribution(products: List[Dict]) -> List[Dict]:
"""分析价格分布"""
ranges = [
{"name": "超低价", "min": 0, "max": 50},
{"name": "低价", "min": 50, "max": 150},
{"name": "中价", "min": 150, "max": 300},
{"name": "高价", "min": 300, "max": 500},
{"name": "超高价", "min": 500, "max": float('inf')},
]
result = []
for r in ranges:
group_products = [p for p in products if r['min'] <= p.get('price', 0) < r['max']]
count = len(group_products)
sales = sum(p.get('monthly_sales', 0) for p in group_products)
revenue = sum(p.get('monthly_revenue', 0) for p in group_products)
ratings = [p.get('rating', 0) for p in group_products if p.get('rating')]
avg_rating = sum(ratings) / len(ratings) if ratings else 0
result.append({
'range': f"{r['name']} (${r['min']}-{r['max'] if r['max'] != float('inf') else '+'})",
'count': count,
'percentage': (count / len(products) * 100) if products else 0,
'sales': sales,
'revenue': revenue,
'avg_rating': avg_rating
})
return result
@staticmethod
def _analyze_rating_distribution(products: List[Dict]) -> List[Dict]:
"""分析评分分布"""
ranges = [
{"name": "低分", "min": 0, "max": 3.5},
{"name": "中低分", "min": 3.5, "max": 4.0},
{"name": "中等", "min": 4.0, "max": 4.3},
{"name": "中高分", "min": 4.3, "max": 4.7},
{"name": "高分", "min": 4.7, "max": 5.0},
]
result = []
for r in ranges:
group_products = [p for p in products if r['min'] <= p.get('rating', 0) < r['max']]
count = len(group_products)
sales = sum(p.get('monthly_sales', 0) for p in group_products)
result.append({
'range': f"{r['name']} ({r['min']}-{r['max']})",
'count': count,
'sales': sales,
'sales_percentage': (sales / sum(p.get('monthly_sales', 0) for p in products) * 100) if products else 0
})
return result
@staticmethod
def _analyze_seller_distribution(products: List[Dict]) -> List[Dict]:
"""分析卖家来源分布"""
sources = {}
for p in products:
source = p.get('seller_source', '其他')
if source not in sources:
sources[source] = {'seller_count': set(), 'product_count': 0, 'revenue': 0}
sources[source]['seller_count'].add(p.get('seller', ''))
sources[source]['product_count'] += 1
sources[source]['revenue'] += p.get('monthly_revenue', 0)
total_revenue = sum(s['revenue'] for s in sources.values())
result = []
for source, data in sources.items():
result.append({
'source': source,
'seller_count': len(data['seller_count']),
'product_count': data['product_count'],
'revenue': data['revenue'],
'percentage': (data['revenue'] / total_revenue * 100) if total_revenue > 0 else 0
})
result.sort(key=lambda x: x['revenue'], reverse=True)
return result
@staticmethod
def _filter_new_products(products: List[Dict]) -> Dict:
"""筛选新产品 (评论数<100视为新品)"""
new_products = [p for p in products if p.get('review_count', 0) < 100]
new_product_sales = sum(p.get('monthly_sales', 0) for p in new_products)
total_sales = sum(p.get('monthly_sales', 0) for p in products)
return {
'count': len(new_products),
'percentage': (len(new_products) / len(products) * 100) if products else 0,
'sales_share': (new_product_sales / total_sales * 100) if total_sales > 0 else 0,
'avg_rating': sum(p.get('rating', 0) for p in new_products) / len(new_products) if new_products else 0
}
@staticmethod
def _build_kpi(stats: Dict, products: List[Dict], brands: List[Dict]) -> Dict:
"""构建KPI指标"""
# 计算CR3
total_revenue = sum(p.get('monthly_revenue', 0) for p in products)
top3_revenue = sum(b['monthly_revenue'] for b in brands[:3]) if len(brands) >= 3 else total_revenue
cr3 = (top3_revenue / total_revenue * 100) if total_revenue > 0 else 0
# 计算HHI
hhi = sum((b['market_share'] / 100) ** 2 for b in brands) if brands else 0
return {
'total_products': len(products),
'avg_price': sum(p.get('price', 0) for p in products) / len(products) if products else 0,
'avg_sales': sum(p.get('monthly_sales', 0) for p in products) / len(products) if products else 0,
'avg_rating': sum(p.get('rating', 0) for p in products) / len(products) if products else 0,
'total_sales': total_revenue,
'cr3': cr3,
'hhi': hhi * 100 # 转换为百分比形式
}
def main():
"""命令行入口"""
import sys
if len(sys.argv) < 2:
print("用法: python data_adapter.py <数据目录> [输出文件]")
print("\n示例:")
print(" python data_adapter.py category-reports/Sofas_US_20260304")
print(" python data_adapter.py category-reports/Sofas_US_20260304 adapted_data.json")
sys.exit(1)
data_dir = sys.argv[1]
output_file = sys.argv[2] if len(sys.argv) > 2 else os.path.join(data_dir, 'adapted_data.json')
# 适配数据
adapted_data = DataAdapter.adapt_for_excel(data_dir)
# 保存适配后的数据
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(adapted_data, f, ensure_ascii=False, indent=2)
print(f"适配后的数据已保存到: {output_file}")
print("\n数据结构:")
print(f" - category_name: {adapted_data.get('category_name', 'N/A')}")
print(f" - five_dimension_score: {list(adapted_data.get('five_dimension_score', {}).keys())}")
print(f" - top100_products: {len(adapted_data.get('top100_products', []))} 个产品")
print(f" - brand_analysis: {len(adapted_data.get('brand_analysis', []))} 个品牌")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
品类选品数据处理工具
包含: HHI计算、价格/评分分组、新产品筛选、增长率计算等
"""
from datetime import datetime, timedelta
from typing import List, Dict, Any
import json
class DataProcessor:
"""品类数据处理工具类"""
@staticmethod
def calculate_hhi(brand_shares: Dict[str, float]) -> float:
"""
计算HHI指数 (Herfindahl-Hirschman Index)
Args:
brand_shares: {品牌名: 市场份额百分比}
Returns:
HHI指数
"""
hhi = 0
for share in brand_shares.values():
hhi += share ** 2
return round(hhi, 2)
@staticmethod
def calculate_cr(brand_shares: Dict[str, float], top_n: int = 3) -> float:
"""
计算CRn集中度 (Concentration Ratio)
Args:
brand_shares: {品牌名: 市场份额百分比}
top_n: 前n个品牌
Returns:
CRn百分比
"""
# 按市场份额降序排序
sorted_brands = sorted(brand_shares.items(), key=lambda x: x[1], reverse=True)
top_shares = sorted_brands[:top_n]
cr = sum(share for _, share in top_shares)
return round(cr, 2)
@staticmethod
def group_by_price_range(products: List[Dict], ranges: List[Dict] = None) -> List[Dict]:
"""
按价格区间分组产品
Args:
products: 产品列表
ranges: 自定义价格区间,默认为5个区间
Returns:
分组结果
"""
if ranges is None:
ranges = [
{"name": "超低价", "min": 0, "max": 20},
{"name": "低价", "min": 20, "max": 50},
{"name": "中价", "min": 50, "max": 100},
{"name": "高价", "min": 100, "max": 200},
{"name": "超高价", "min": 200, "max": float('inf')},
]
result = []
for range_def in ranges:
group = {
"range": f"{range_def['name']} (${range_def['min']}-{range_def['max'] if range_def['max'] != float('inf') else '+'})",
"count": 0,
"sales": 0,
"revenue": 0,
"ratings": [],
"products": []
}
for product in products:
price = product.get("price", 0)
if range_def['min'] <= price < range_def['max']:
group["count"] += 1
group["sales"] += product.get("monthly_sales", 0)
group["revenue"] += product.get("monthly_revenue", 0)
if product.get("rating"):
group["ratings"].append(product["rating"])
group["products"].append(product)
# 计算占比和平均评分
total_products = len(products)
total_revenue = sum(p.get("monthly_revenue", 0) for p in products)
group["percentage"] = round(group["count"] / total_products * 100, 1) if total_products > 0 else 0
group["avg_rating"] = round(sum(group["ratings"]) / len(group["ratings"]), 2) if group["ratings"] else 0
result.append(group)
return result
@staticmethod
def group_by_rating_range(products: List[Dict], ranges: List[Dict] = None) -> List[Dict]:
"""
按评分区间分组产品
Args:
products: 产品列表
ranges: 自定义评分区间,默认为5个区间
Returns:
分组结果
"""
if ranges is None:
ranges = [
{"name": "低分", "min": 0, "max": 3.5},
{"name": "中低分", "min": 3.5, "max": 4.0},
{"name": "中等", "min": 4.0, "max": 4.3},
{"name": "中高分", "min": 4.3, "max": 4.7},
{"name": "高分", "min": 4.7, "max": 5.0},
]
result = []
for range_def in ranges:
group = {
"range": f"{range_def['name']} ({range_def['min']}-{range_def['max']})",
"count": 0,
"sales": 0,
"sales_percentage": 0
}
for product in products:
rating = product.get("rating", 0)
if range_def['min'] <= rating < range_def['max']:
group["count"] += 1
group["sales"] += product.get("monthly_sales", 0)
# 计算占比
total_sales = sum(p.get("monthly_sales", 0) for p in products)
group["sales_percentage"] = round(group["sales"] / total_sales * 100, 1) if total_sales > 0 else 0
group["percentage"] = round(group["count"] / len(products) * 100, 1) if products else 0
result.append(group)
return result
@staticmethod
def filter_new_products(products: List[Dict], days_threshold: int = 90) -> List[Dict]:
"""
筛选新产品 (上架时间小于指定天数)
Args:
products: 产品列表
days_threshold: 天数阈值,默认90天(3个月)
Returns:
新产品列表
"""
today = datetime.now()
threshold_date = today - timedelta(days=days_threshold)
new_products = []
for product in products:
days_online = product.get("days_online", 0)
if days_online and days_online <= days_threshold:
new_products.append(product)
return new_products
@staticmethod
def analyze_brand_distribution(products: List[Dict]) -> List[Dict]:
"""
分析品牌分布
Args:
products: 产品列表
Returns:
品牌分析列表,按市场份额降序排序
"""
brand_data = {}
for product in products:
brand = product.get("brand", "Unknown")
if brand not in brand_data:
brand_data[brand] = {
"brand": brand,
"product_count": 0,
"monthly_sales": 0,
"monthly_revenue": 0,
"ratings": []
}
brand_data[brand]["product_count"] += 1
brand_data[brand]["monthly_sales"] += product.get("monthly_sales", 0)
brand_data[brand]["monthly_revenue"] += product.get("monthly_revenue", 0)
if product.get("rating"):
brand_data[brand]["ratings"].append(product["rating"])
# 计算总销额
total_revenue = sum(b["monthly_revenue"] for b in brand_data.values())
# 计算市场份额和平均评分
for brand in brand_data.values():
brand["market_share"] = round(brand["monthly_revenue"] / total_revenue * 100, 2) if total_revenue > 0 else 0
brand["avg_rating"] = round(sum(brand["ratings"]) / len(brand["ratings"]), 2) if brand["ratings"] else 0
# 按市场份额降序排序
sorted_brands = sorted(brand_data.values(), key=lambda x: x["market_share"], reverse=True)
return sorted_brands
@staticmethod
def analyze_seller_distribution(products: List[Dict]) -> Dict[str, Dict]:
"""
分析卖家来源分布
Args:
products: 产品列表
Returns:
按来源地分组的统计数据
"""
source_data = {
"中国": {"seller_count": set(), "product_count": 0, "revenue": 0},
"美国": {"seller_count": set(), "product_count": 0, "revenue": 0},
"品牌": {"seller_count": set(), "product_count": 0, "revenue": 0},
"其他": {"seller_count": set(), "product_count": 0, "revenue": 0}
}
for product in products:
seller = product.get("seller", "")
source = product.get("seller_source", "其他")
if source in source_data:
source_data[source]["seller_count"].add(seller)
source_data[source]["product_count"] += 1
source_data[source]["revenue"] += product.get("monthly_revenue", 0)
# 转换为列表格式
result = []
total_revenue = sum(s["revenue"] for s in source_data.values())
for source, data in source_data.items():
result.append({
"source": source,
"seller_count": len(data["seller_count"]),
"product_count": data["product_count"],
"revenue": data["revenue"],
"percentage": round(data["revenue"] / total_revenue * 100, 1) if total_revenue > 0 else 0
})
# 按销额降序排序
result.sort(key=lambda x: x["revenue"], reverse=True)
return result
@staticmethod
def calculate_growth_rate(trend_data: List[Dict], period_months: int = 3) -> Dict[str, float]:
"""
计算增长率和环比
Args:
trend_data: 趋势数据列表,每个元素包含 {date, value}
period_months: 对比周期月数
Returns:
{同比增长率, 环比增长率}
"""
if len(trend_data) < period_months + 1:
return {"yoy": 0, "mom": 0}
current_avg = sum(d["value"] for d in trend_data[-period_months:]) / period_months
previous_avg = sum(d["value"] for d in trend_data[-(period_months*2+1):-period_months]) / period_months
yoy = round((current_avg - previous_avg) / previous_avg * 100, 2) if previous_avg > 0 else 0
# 环比 (上个月 vs 这个月)
if len(trend_data) >= 2:
last_month = trend_data[-1]["value"]
prev_month = trend_data[-2]["value"]
mom = round((last_month - prev_month) / prev_month * 100, 2) if prev_month > 0 else 0
else:
mom = 0
return {"yoy": yoy, "mom": mom}
@staticmethod
def calculate_five_dimension_score(data: Dict) -> Dict[str, float]:
"""
计算五维评分 (标准版本 - 与需求文档一致)
评分标准:
- 市场规模 (20分): >10M=20, >5M=17, >1M=14, 其他=10
- 增长潜力 (25分): 低评论占比>40%=22, >20%=18, 其他=14
- 竞争烈度 (20分): Top3<30%=18, <50%=14, 其他=8
- 进入壁垒 (20分): Amazon占比+新品机会组合评分
- 利润空间 (15分): 均价>$300=12, >$150=10, >$50=7, 其他=4
Args:
data: 包含所有市场数据的字典
Returns:
五维评分结果
"""
scores = {}
# 1. 市场规模 (20分) - 标准版本
total_revenue = data.get("total_monthly_revenue", 0)
if total_revenue > 10_000_000:
scores["market_size"] = 20
elif total_revenue > 5_000_000:
scores["market_size"] = 17
elif total_revenue > 1_000_000:
scores["market_size"] = 14
else:
scores["market_size"] = 10
# 2. 增长潜力 (25分) - 基于低评论产品占比
low_review_share = data.get("low_reviews_sales_volume_share", 0)
if low_review_share > 40:
scores["growth_potential"] = 22
elif low_review_share > 20:
scores["growth_potential"] = 18
else:
scores["growth_potential"] = 14
# 3. 竞争烈度 (20分) - 基于 Top3 品牌占比
top3_share = data.get("top3_brands_sales_volume_share", 0)
if top3_share < 30:
scores["competition"] = 18
elif top3_share < 50:
scores["competition"] = 14
else:
scores["competition"] = 8
# 4. 进入壁垒 (20分) - Amazon 占比 + 新品机会
amazon_share = data.get("amazonOwned_sales_volume_share", 0)
low_review_share = data.get("low_reviews_sales_volume_share", 0)
barrier_score = 0
# Amazon 占比越低,壁垒越小 (0-10分)
if amazon_share < 20:
barrier_score += 10
elif amazon_share < 40:
barrier_score += 6
else:
barrier_score += 3
# 新品机会越大,壁垒越小 (0-10分)
if low_review_share > 40:
barrier_score += 10
elif low_review_share > 20:
barrier_score += 6
else:
barrier_score += 3
scores["entry_barrier"] = barrier_score
# 5. 利润空间 (15分) - 基于平均价格
avg_price = data.get("average_price", 0)
if avg_price > 300:
scores["profit_margin"] = 12
elif avg_price > 150:
scores["profit_margin"] = 10
elif avg_price > 50:
scores["profit_margin"] = 7
else:
scores["profit_margin"] = 4
# 计算总分
scores["total"] = (
scores["market_size"] +
scores["growth_potential"] +
scores["competition"] +
scores["entry_barrier"] +
scores["profit_margin"]
)
# 评级
if scores["total"] >= 80:
scores["rating"] = "优秀"
elif scores["total"] >= 70:
scores["rating"] = "良好"
elif scores["total"] >= 50:
scores["rating"] = "一般"
else:
scores["rating"] = "较差"
return scores
@staticmethod
def prepare_html_data(data: Dict) -> Dict:
"""
准备HTML报告所需的数据格式
Args:
data: 原始数据
Returns:
HTML模板变量字典
"""
return {
# 基础信息
"CATEGORY_NAME": data.get("category_name", ""),
"SITE": data.get("site", "US"),
"DATA_DATE": datetime.now().strftime("%Y-%m-%d"),
# 五维评分
"MARKET_SIZE_SCORE": data.get("scores", {}).get("market_size", 0),
"MARKET_SIZE_PERCENT": int(data.get("scores", {}).get("market_size", 0) / 20 * 100),
"GROWTH_POTENTIAL_SCORE": data.get("scores", {}).get("growth_potential", 0),
"GROWTH_POTENTIAL_PERCENT": int(data.get("scores", {}).get("growth_potential", 0) / 25 * 100),
"COMPETITION_SCORE": data.get("scores", {}).get("competition", 0),
"COMPETITION_PERCENT": int(data.get("scores", {}).get("competition", 0) / 20 * 100),
"ENTRY_BARRIER_SCORE": data.get("scores", {}).get("entry_barrier", 0),
"ENTRY_BARRIER_PERCENT": int(data.get("scores", {}).get("entry_barrier", 0) / 20 * 100),
"PROFIT_MARGIN_SCORE": data.get("scores", {}).get("profit_margin", 0),
"PROFIT_MARGIN_PERCENT": int(data.get("scores", {}).get("profit_margin", 0) / 15 * 100),
"TOTAL_SCORE": data.get("scores", {}).get("total", 0),
"RATING": data.get("rating", ""),
"RECOMMENDATION": data.get("recommendation", ""),
# KPI
"TOTAL_PRODUCTS": data.get("total_products", 0),
"AVG_PRICE": f"${data.get("avg_price", 0):.2f}",
"AVG_SALES": f"{data.get("avg_sales", 0):.0f}",
"AVG_RATING": f"{data.get("avg_rating", 0):.2f}",
"TOTAL_SALES": f"${data.get("total_sales", 0):,.0f}",
"CR3": f"{data.get("cr3", 0):.2f}",
# 趋势数据 (需要JSON序列化)
"SALES_TREND_DATA": json.dumps(data.get("sales_trend", {"dates": [], "sales": []})),
"PRICE_TREND_DATA": json.dumps(data.get("price_trend", {"dates": [], "prices": []})),
"PRICE_DIST_DATA": json.dumps(data.get("price_dist", [])),
"RATING_DIST_DATA": json.dumps(data.get("rating_dist", {"ranges": [], "counts": []})),
"BRAND_SHARE_DATA": json.dumps(data.get("brand_share", {"brands": [], "shares": []})),
"SELLER_SOURCE_DATA": json.dumps(data.get("seller_source", [])),
"BRAND_RATING_TREND_DATA": json.dumps(data.get("brand_rating_trend", {"brands": [], "dates": [], "data": {}})),
"TOP50_PRODUCTS": json.dumps(data.get("top50_products", [])),
# 关键发现
"CONCENTRATION_LEVEL": data.get("concentration_level", ""),
"HHI": f"{data.get('hhi', 0):.2f}",
"CR3_RAW": f"{data.get('cr3_raw', 0)}",
"CONCLUSION_CR3": data.get("conclusion_cr3", ""),
"BRAND_COUNT": data.get("brand_count", 0),
"BRAND_DIVERSITY": data.get("brand_diversity", ""),
"NEW_PRODUCT_PERCENT": f"{data.get('new_product_percent', 0)}%",
"NEW_PRODUCT_CONCLUSION": data.get("new_product_conclusion", ""),
"SELLER_DISTRIBUTION": data.get("seller_distribution", ""),
"COMPETITION_CONCLUSION": data.get("competition_conclusion", ""),
"STRATEGY": data.get("strategy", ""),
"GENERATED_TIME": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
# 使用示例
if __name__ == "__main__":
# 示例产品数据
sample_products = [
{"asin": "B001", "brand": "Sony", "price": 150, "rating": 4.5, "monthly_sales": 1000,
"monthly_revenue": 150000, "seller": "Amazon", "seller_source": "品牌", "days_online": 45},
{"asin": "B002", "brand": "Samsung", "price": 120, "rating": 4.2, "monthly_sales": 800,
"monthly_revenue": 96000, "seller": "Seller1", "seller_source": "中国", "days_online": 200},
{"asin": "B003", "brand": "LG", "price": 180, "rating": 4.6, "monthly_sales": 500,
"monthly_revenue": 90000, "seller": "Seller2", "seller_source": "韩国", "days_online": 30},
]
processor = DataProcessor()
# 品牌分布分析
brands = processor.analyze_brand_distribution(sample_products)
print("品牌分布:", brands)
# 价格分组
price_groups = processor.group_by_price_range(sample_products)
print("价格分组:", price_groups)
# 评分分组
rating_groups = processor.group_by_rating_range(sample_products)
print("评分分组:", rating_groups)
# 新产品筛选
new_products = processor.filter_new_products(sample_products)
print("新产品:", new_products)