洞察:...
+```
+
+---
+
+## LLM Prompt 模板库
+
+详细 Prompt 模板请参考:`references/prompt_templates.md`
+
+包含 6 个模板:
+1. 属性标注 - 从产品标题提取差异化维度
+2. 交叉分析 - 发现供需缺口
+3. 竞品选择 - 选择代表性竞品
+4. 差评归类 - 按属性维度归类痛点
+5. 选品决策评估 - 五维加权决策
+6. 产品矩阵规划 - Tier 1/2/3 具体规格
+
+---
+
+## 硬性规则(⛔ 不可省略)
+
+1. ⛔ Top100 必须完整 100 条
+2. ⛔ 关键词至少 3 个维度对比
+3. ⛔ 竞品选择 6-10 个,覆盖量级标杆/功能差异/价格带/痛点
+4. ⛔ **差评必须按维度归类**(非按 ASIN 归类),输出到 `data.json` 的 `voc_analysis` 字段
+5. ⛔ VOC 分析必须包含:频次、占比、涉及品牌、品牌机会、产品方案
+6. ⛔ 选品决策评估必须量化评分
+7. ⛔ Tier 1 产品必须具体到规格(禁止"待确认"占位)
+8. ⛔ 每个数据表后有"关键洞察"段落
+9. ⛔ 空白/薄供给必须附带原因分析
+10. ⛔ **数据字段命名必须清晰**:使用 `top3_product_concentration` / `top3_brand_concentration`,禁止模糊的 `top3_concentration`
+11. ⛔ **数据一致性校验**:报告生成前必须校验 `data.json` 中的数值与报告文本一致
+
+---
+
+## 常见场景策略
+
+### 场景1:新手入门(预算<15万)
+- 价格 $10-20
+- 轻小件
+- 无售后风险
+- 中国卖家占比 > 70%
+
+### 场景2:蓝海发现
+- Top3 集中度 < 30%
+- 新品占比 > 15%
+- 关键词首页评论 < 500
+
+### 场景3:定向品类分析(用户已指定)
+- 跳过类目扫描,直接进入数据采集
+- ⛔ 必须执行属性标注
+- ⛔ 必须执行交叉分析
+- ⛔ 必须执行选品决策评估(五维评分)
+
+---
+
+## 与其他 Skills 的关系
+
+```
+category-selection (品类筛选五维评分)
+ ↓
+product-research (深度选品调研) ← 本 Skill
+ ↓
+amazon-analyse (竞品 Listing 深挖)
+ ↓
+review-analysis (评论深度分析)
+```
+
+**区别**:
+- `category-selection`:品类级别的快速筛选,五维评分
+- `product-research`:指定品类的深度调研,多维度分析 + 选品决策评估
+- `amazon-analyse`:单个竞品 Listing 的详细分析
+- `review-analysis`:评论的深度痛点分析
+
+---
+
+## 支持的站点
+
+US, GB, DE, FR, IT, ES, CA, JP, MX, AE, AU, BR, SA
+
+---
+
+## 注意事项
+
+1. **API Key**:自动从 `.mcp.json` 读取
+2. **数据时效**:Sorftime 数据可能有 1-7 天延迟
+3. **API 限流**:每批最多 8 个并发请求
+4. **编码问题**:脚本自动处理 Unicode-escape 和 Mojibake
+5. **原始数据**:所有 API 响应保存在 `raw/` 目录供验证
+
+---
+
+## 故障排查
+
+### 常见错误及解决方案
+
+| 错误信息 | 原因 | 解决方案 |
+|----------|------|----------|
+| `HTTP Error 406: Not Acceptable` | API参数错误 | 检查参数名是否为 `searchName` 而非 `productName` |
+| `An error occurred invoking 'xxx'` | API工具不存在 | 检查 TOOLS 映射表中的工具名称 |
+| `未查询到对应产品` | ASIN无效或站点错误 | 验证ASIN格式, 确认产品在该站点销售 |
+| `Authentication required` | API Key错误 | 检查 `.mcp.json` 中的 key 参数 |
+| 中文乱码 | Mojibake编码 | 脚本自动修复, 或运行 `fix_encoding.py` |
+| `IndentationError: unexpected indent` | Windows 命令行问题 | 使用脚本文件而非 `python -c` |
+| `输出目录路径错误` | 相对路径问题 | 使用 `run_analysis.py`,自动处理路径 |
+| `NameError: name 'xxx' is not defined` | 缺少 datetime 导入 | 检查脚本 import 语句 |
+| **Dashboard 渲染问题** | | |
+| Dashboard 显示空白 | data.json 结构不匹配 | **v3.5 已修复**:`run_analysis.py` 自动渲染 Dashboard |
+| Dashboard 未自动生成 | 旧版本未集成渲染 | **v3.5 已修复**:数据采集完成后自动渲染 |
+| `PermissionError: [Errno 13]` | 传递目录路径而非文件路径 | 使用绝对路径调用:`python render_dashboard.py -o output.html data.json` |
+| `unrecognized arguments` | 参数顺序错误 | 正确格式:`python render_dashboard.py -o dashboard.html data.json` |
+| Dashboard 缺少 VOC 数据 | LLM 未生成完整 voc_analysis | 确保 LLM 生成包含 voc_analysis.dimensions 的完整 data.json |
+| Dashboard 交叉分析为空 | price_type_matrix 数据缺失 | 确保数据采集包含价格区间分析 |
+| `KeyError: 'xxx'` | 字段名不一致 | **v3.4 已修复**:支持新旧字段名兼容 |
+| `AttributeError: 'str' object has no attribute 'get'` | data.json 格式问题 | **v3.4 已修复**:自动转换为列表格式 |
+
+### Dashboard 手动渲染方法
+
+如果自动渲染失败,可以手动调用:
+
+```bash
+# 从输出目录调用
+python .claude/skills/product-research/scripts/render_dashboard.py \
+ -o product-research-reports/{keyword}_{site}_{date}/dashboard.html \
+ product-research-reports/{keyword}_{site}_{date}/data.json
+
+# 或者使用绝对路径
+python "D:\amazon-mcp\.claude\skills\product-research\scripts\render_dashboard.py" \
+ -o "D:\amazon-mcp\product-research-reports\{keyword}_{site}_{date}\dashboard.html" \
+ "D:\amazon-mcp\product-research-reports\{keyword}_{site}_{date}\data.json"
+```
+
+### API 工具名称对照表
+
+| 功能 | 工具名称 | 参数 |
+|------|----------|------|
+| 类目搜索 | `category_name_search` | `amzSite`, `searchName` |
+| 类目报告 | `category_report` | `amzSite`, `nodeId` |
+| 类目趋势 | `category_trend` | `amzSite`, `nodeId`, `trendIndex` |
+| 关键词详情 | `keyword_detail` | `amzSite`, `keyword` |
+| 产品详情 | `product_detail` | `amzSite`, `asin` |
+| 产品评论 | `product_reviews` | `amzSite`, `asin`, `reviewType` |
+
+### 调试技巧
+
+1. **启用详细输出**: 在脚本中添加 `print()` 调试信息
+2. **检查原始响应**: 查看 SSE 响应的实际内容
+3. **分步执行**: 使用 Python 交互式环境逐行调试
+4. **验证API Key**: `curl "https://mcp.sorftime.com?key=YOUR_KEY" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'`
+
+---
+
+*版本: v3.6 (两阶段工作流 + 数据验证) | 最后更新: 2026-03-19*
diff --git a/skills/amazon-sorftime-research-market-skill/_meta.json b/skills/amazon-sorftime-research-market-skill/_meta.json
new file mode 100644
index 00000000..956e357c
--- /dev/null
+++ b/skills/amazon-sorftime-research-market-skill/_meta.json
@@ -0,0 +1,11 @@
+{
+ "owner": "liangdabiao",
+ "slug": "amazon-sorftime-research-market-skill",
+ "displayName": "amazon-sorftime-research-market-skill",
+ "latest": {
+ "version": "1.0.0",
+ "publishedAt": 1774337710626,
+ "commit": "https://github.com/openclaw/skills/commit/70627a27c1170993e953023a8187e11bdbb6a248"
+ },
+ "history": []
+}
diff --git a/skills/amazon-sorftime-research-market-skill/references/api-quick-reference.md b/skills/amazon-sorftime-research-market-skill/references/api-quick-reference.md
new file mode 100644
index 00000000..6a3805a6
--- /dev/null
+++ b/skills/amazon-sorftime-research-market-skill/references/api-quick-reference.md
@@ -0,0 +1,98 @@
+# Sorftime MCP API 快速参考
+
+## 品类选品分析常用接口
+
+### 1. category_name_search - 搜索类目
+
+```bash
+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 - 类目报告 (核心)
+
+```bash
+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 - 产品详情
+
+```bash
+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 - 类目关键词
+
+```bash
+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 解码示例
+```python
+import codecs
+
+# 解码 Unicode 转义
+decoded = codecs.decode(encoded_text, 'unicode-escape')
+```
+
+---
+
+## 支持的站点
+
+| 代码 | 站点 |
+|------|------|
+| US | 美国 |
+| GB | 英国 |
+| DE | 德国 |
+| FR | 法国 |
+| CA | 加拿大 |
+| JP | 日本 |
+| ES | 西班牙 |
+| IT | 意大利 |
diff --git a/skills/amazon-sorftime-research-market-skill/references/api-reference.md b/skills/amazon-sorftime-research-market-skill/references/api-reference.md
new file mode 100644
index 00000000..7fdbac21
--- /dev/null
+++ b/skills/amazon-sorftime-research-market-skill/references/api-reference.md
@@ -0,0 +1,300 @@
+# Sorftime API 快速参考 (Product-Research)
+
+## API 端点
+
+```
+https://mcp.sorftime.com?key={API_KEY}
+```
+
+## 请求格式
+
+```json
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "tools/call",
+ "params": {
+ "name": "{工具名称}",
+ "arguments": {
+ "amzSite": "US",
+ ...
+ }
+ }
+}
+```
+
+## 响应格式 (SSE)
+
+```
+event: message
+data: {"result":{"content":[{\"type\":\"text\",\"text\":\"{数据}\"}],\"isError\":false},"id":1,\"jsonrpc\":\"2.0\"}
+
+```
+
+---
+
+## 常用 API 工具
+
+### 1. category_name_search
+
+**用途**: 按名称搜索类目,获取 NodeId
+
+**参数**:
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| amzSite | string | ✓ | 站点代码 (US, GB, DE, etc.) |
+| searchName | string | ✓ | 类目名称关键词 |
+
+**示例**:
+```bash
+curl -s -X POST "https://mcp.sorftime.com?key={KEY}" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "tools/call",
+ "params": {
+ "name": "category_name_search",
+ "arguments": {
+ "amzSite": "US",
+ "searchName": "bluetooth speaker"
+ }
+ }
+ }'
+```
+
+**响应**:
+```json
+[
+ {
+ "nodeId": "7073956011",
+ "Name": "Portable Bluetooth Speakers"
+ },
+ {
+ "nodeId": "12097477011",
+ "Name": "Outdoor Speakers"
+ }
+]
+```
+
+---
+
+### 2. category_report
+
+**用途**: 获取类目 Top100 产品和统计数据
+
+**参数**:
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| amzSite | string | ✓ | 站点代码 |
+| nodeId | string | ✓ | 类目 Node ID |
+
+**示例**:
+```python
+client.get_category_report("US", "7073956011")
+```
+
+**响应结构**:
+```json
+{
+ "Top100产品": [
+ {
+ "ASIN": "B0XXXXXXXX",
+ "标题": "...",
+ "月销量": "10000",
+ "月销额": "500000.00",
+ "品牌": "JBL",
+ "价格": 49.99,
+ "评论数": 5000,
+ "星级": 4.7
+ }
+ ],
+ "类目统计报告": {
+ "nodeid": "7073956011",
+ "类目名称": "Portable Bluetooth Speakers",
+ "top100产品月销量": "279733",
+ "top100产品月销额": "19842968.40",
+ "top3_product_sales_volume_share": "19.66%"
+ }
+}
+```
+
+---
+
+### 3. category_trend
+
+**用途**: 获取类目趋势数据
+
+**参数**:
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| amzSite | string | ✓ | 站点代码 |
+| nodeId | string | ✓ | 类目 Node ID |
+| trendIndex | string | ✗ | 趋势类型 (默认: NewProductSalesAmountShare) |
+
+**trendIndex 选项**:
+- `NewProductSalesAmountShare` - 新品销量占比
+- `NewProductProductShare` - 新品数量占比
+- `BrandConcentration` - 品牌集中度
+- `PriceDistribution` - 价格分布
+
+**示例**:
+```python
+trend = client.get_category_trend("US", "7073956011", "NewProductSalesAmountShare")
+```
+
+**响应**:
+```json
+[
+ "2024年03月=3.32",
+ "2024年04月=1.98",
+ ...
+]
+```
+
+---
+
+### 4. keyword_detail
+
+**用途**: 获取关键词详情
+
+**参数**:
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| amzSite | string | ✓ | 站点代码 |
+| keyword | string | ✓ | 关键词 |
+
+**示例**:
+```python
+detail = client.get_keyword_detail("US", "bluetooth speaker")
+```
+
+**响应结构**:
+```json
+{
+ "搜索量": "50000",
+ "CPC": "1.50",
+ "竞价": "8",
+ "自然位产品": [...]
+}
+```
+
+---
+
+### 5. product_detail
+
+**用途**: 获取单个产品详情
+
+**参数**:
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| amzSite | string | ✓ | 站点代码 |
+| asin | string | ✓ | 产品 ASIN |
+
+---
+
+### 6. product_reviews
+
+**用途**: 获取产品评论
+
+**参数**:
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| amzSite | string | ✓ | 站点代码 |
+| asin | string | ✓ | 产品 ASIN |
+| reviewType | string | ✗ | 评论类型 (Both/Positive/Negative) |
+
+---
+
+## Python 客户端使用
+
+### 基本用法
+
+```python
+from api_client import SorftimeClient
+
+client = SorftimeClient()
+
+# 搜索类目
+categories = client.search_category_by_product_name("US", "bluetooth speaker")
+node_id = categories[0]['nodeId']
+
+# 获取 Top100
+top100 = client.get_category_report("US", node_id)
+products = top100.get('Top100产品', [])
+
+# 获取趋势
+trend = client.get_category_trend("US", node_id)
+
+# 获取关键词详情
+keyword_data = client.get_keyword_detail("US", "bluetooth speaker")
+```
+
+### 批量调用
+
+```python
+# 并发获取多个产品详情
+asins = ["B0XXX1", "B0XXX2", "B0XXX3"]
+details = []
+for asin in asins:
+ try:
+ detail = client.get_product_detail("US", asin)
+ details.append(detail)
+ except Exception as e:
+ print(f"Failed for {asin}: {e}")
+```
+
+---
+
+## 支持的站点
+
+| 代码 | 市场 |
+|------|------|
+| US | 美国亚马逊 |
+| GB | 英国亚马逊 |
+| DE | 德国亚马逊 |
+| FR | 法国亚马逊 |
+| IT | 意大利亚马逊 |
+| ES | 西班牙亚马逊 |
+| CA | 加拿大亚马逊 |
+| JP | 日本亚马逊 |
+| MX | 墨西哥亚马逊 |
+| AE | 阿联酋亚马逊 |
+| AU | 澳大利亚亚马逊 |
+| BR | 巴西亚马逊 |
+| SA | 沙特阿拉伯亚马逊 |
+
+---
+
+## 数据类型说明
+
+### 月销量/月销额
+
+- 类型: `string` (需要转换为数字)
+- 示例: `"28908"`, `"1443954.60"`
+- 转换: `float(value)`
+
+### 价格
+
+- 类型: `float` 或 `string`
+- 示例: `49.95`, `"29.99"`
+
+### 评论数
+
+- 类型: `int` 或 `string`
+- 示例: `14558`, `"5000"`
+
+---
+
+## 错误代码
+
+| HTTP 状态 | 含义 | 解决方案 |
+|-----------|------|----------|
+| 200 | 成功 | - |
+| 406 | 参数错误 | 检查参数名称和格式 |
+| 401 | 认证失败 | 检查 API Key |
+| 500 | 服务器错误 | 稍后重试 |
+
+---
+
+*最后更新: 2026-03-19*
diff --git a/skills/amazon-sorftime-research-market-skill/references/prompt_templates.md b/skills/amazon-sorftime-research-market-skill/references/prompt_templates.md
new file mode 100644
index 00000000..b97b7b65
--- /dev/null
+++ b/skills/amazon-sorftime-research-market-skill/references/prompt_templates.md
@@ -0,0 +1,446 @@
+# LLM Prompt 模板库
+
+本文档提供 product-research Skill 中使用的 Prompt 模板,供 LLM 执行各分析步骤时参考。
+
+---
+
+## 模板 1: 属性标注
+
+### 使用场景
+
+Step 2: 属性标注阶段 - LLM 从 Top100 产品标题中提取关键差异化维度
+
+### Prompt 模板
+
+```markdown
+你是一位产品分析专家,擅长从产品标题中识别关键差异化维度。
+
+## 任务目标
+
+分析以下 Top100 产品标题,提取 3-6 个关键差异化维度。
+
+## 分析样本
+
+### 前 20 个产品标题样本:
+{titles_sample}
+
+### 分析要求
+
+1. **识别差异化维度**(3-6 个)
+ - 维度应该是该品类的关键差异化因素
+ - 如:电子产品的功率、容量、防水等级;家居产品的材质、尺寸、风格等
+ - 避免通用维度(如颜色、包装)
+
+2. **为每个产品标注维度值**
+ - 从标题中提取信息
+ - 如果标题中缺失,标注为"未知"
+ - 标注置信度:高(标题明确)、中(需要推断)、低(缺失/不确定)
+
+3. **维度值分类**
+ - 每个维度的值应该是可分类的
+ - 如:功率 -> 20W/30W/65W/100W+
+ - 如:防水 -> IPX7/IPX5/无
+
+## 输出格式
+
+### 维度定义表
+
+| 维度名称 | 说明 | 候的分类 |
+|---------|------|---------|
+| 功率 | 输出功率 | 20W以下 / 20-30W / 30-45W / 45-65W / 65W+ |
+| 防水 | 防水等级 | IPX7+ / IPX5-6 / 无 |
+| ... | ... | ... |
+
+### 标注结果示例
+
+| ASIN | 标题 | [维度1] | [维度2] | ... | 置信度 |
+|------|------|---------|---------|-----|--------|
+| B0XXX | 产品标题... | 20W | IPX7 | ... | 高 |
+```
+
+---
+
+## 模板 2: 交叉分析
+
+### 使用场景
+
+Step 3: 交叉分析阶段 - LLM 从已标注数据中发现供需缺口
+
+### Prompt 模板
+
+```markdown
+你是一位市场机会分析师,擅长从数据中发现供需缺口和市场机会。
+
+## 任务目标
+
+基于已标注的 Top100 产品数据,执行交叉分析,发现未被满足的市场需求。
+
+## 输入数据
+
+### 已标注产品数据(部分样本)
+{annotated_products_sample}
+
+### 市场概况
+- Top100 月销量: {monthly_sales}
+- Top100 月销额: {monthly_revenue}
+- 平均价格: ${avg_price}
+
+## 分析要求
+
+### 1. 选择维度组合
+- 选择 2-3 对有意义的维度组合
+- 考虑因素:
+ - 维度之间的关联性(如:功率×价格、防水×场景)
+ - 市场需求合理性
+ - 数据完整性
+
+### 2. 识别供需状态
+对每个维度组合,识别:
+- **空白点**:产品数 = 0
+- **薄供给**:产品数 ≤ 2
+- **高需求低供给**:月销量高但产品数少
+
+### 3. 原因分析
+对每个空白/薄供给,分析:
+- 技术限制(无法实现或成本过高)
+- 需求不存在(消费者不需要)
+- 被市场忽视(存在但未被满足)
+- 供应链难度
+
+### 4. 机会评级
+按三维评估排序:
+- 市场规模(40%):月销额 $100K+ = 高,$50K-100K = 中,<$50K = 低
+- 技术可行性(30%):现有产品线 = 高,需新模具 = 中,需研发 = 低
+- 品牌匹配(30%):核心优势 = 高,部分匹配 = 中,全新领域 = 低
+
+## 输出格式
+
+### 交叉分析矩阵
+
+| 维度A × 维度B | 状态 | 产品数 | 月销量 | 均价 | 原因分析 | 机会评级 |
+|--------------|------|--------|--------|------|----------|---------|
+| 65W × $50-80 | 薄供给 | 1 | 2000 | $70 | 技术可行但被忽视 | 高 |
+| ... | ... | ... | ... | ... | ... | ... |
+
+### 关键发现
+- 哪些组合是市场主力?(高供给 + 高需求)
+- 哪些组合存在明显空白?
+- 哪些空白值得进入?
+```
+
+---
+
+## 模板 3: 竞品选择逻辑
+
+### 使用场景
+
+Step 4: 竞品与 VOC 分析 - LLM 按细分段选择代表性竞品
+
+### Prompt 模板
+
+```markdown
+你是一位竞品分析专家,需要选择代表性竞品进行深度分析。
+
+## 任务目标
+
+从 Top100 产品中选择 6-10 个代表性竞品,用于差评分析和策略制定。
+
+## 输入数据
+
+### Top100 产品数据(部分样本)
+{top100_sample}
+
+### 已标注维度
+{dimensions_summary}
+
+## 选择要求
+
+### 必须覆盖的细分段
+
+1. **量级标杆**(1-2 个)
+ - Top3-5 销量产品
+ - 代表市场标准
+
+2. **功能差异代表**(每个主要维度 1 个)
+ - 各维度的头部产品
+ - 如:高功率代表、防水等级代表
+
+3. **价格带覆盖**(高/中/低各 1 个)
+ - 高价段:> 平均价 30%
+ - 中价段:平均价 ± 20%
+ - 低价段:< 平均价 30%
+
+4. **痛点参考**(1-2 个)
+ - 差评率高或评分低的产品
+ - 用于挖掘改进机会
+
+## 输出格式
+
+| ASIN | 品牌 | 选择理由 | 竞品类型 | 覆盖维度 | 价格 | 月销量 | 评论数 |
+|------|------|----------|----------|----------|------|--------|--------|
+| B0XXX | BrandA | 类目 Top3,覆盖主力价格带 | 量级标杆 | 价格-中 | $XX | XXXX | XXX |
+| ... | ... | ... | ... | ... | ... | ... | ... |
+```
+
+---
+
+## 模板 4: 差评维度归类
+
+### 使用场景
+
+Step 4: 竞品与 VOC 分析 - LLM 按属性维度归类差评痛点
+
+### Prompt 模板
+
+```markdown
+你是一位产品开发顾问,擅长从用户评论中挖掘产品痛点和改进机会。
+
+## 任务目标
+
+将竞品差评按属性维度归类,并映射到品牌能力和产品方案。
+
+## 输入数据
+
+### 竞品选择逻辑表
+{competitor_selection}
+
+### 差评样本(部分)
+{reviews_sample}
+
+## 归类要求
+
+### 按属性维度(而非按产品)归类
+
+1. **识别主要维度**(3-6 个)
+ - 基于差评内容提取痛点类别
+ - 如:续航/电池、功率/充电、数显、线材、外观、质量等
+
+2. **每个维度包含**
+ - 痛点描述:用户不满的具体问题
+ - 频次/占比:涉及多少条差评
+ - 涉及竞品:哪些品牌/产品有此问题
+ - 品牌机会:我们的品牌/供应链能如何解决
+ - 产品方案:具体的产品改进方向
+
+### 痛点→方案映射
+
+| 要素 | 说明 | 示例 |
+|------|------|------|
+| 痛点描述 | 用户不满的具体问题 | "电池容量虚标,实际续航不足 50%" |
+| 数据支撑 | 差评频次/占比 | "涉及 45 条差评,占比 32%" |
+| 品牌机会 | 品牌/供应链能力 | "有高密度电芯供应链" |
+| 产品方案 | 具体改进方案 | "4000mAh 实标 + 实测视频营销" |
+
+## 输出格式
+
+### 差评维度归类表
+
+| 维度 | 痛点 | 频次 | 占比 | 涉及竞品 | 品牌机会 | 产品方案 |
+|------|------|------|------|----------|----------|----------|
+| 续航/电池 | 容量虚标 | 45 | 32% | BrandA,B | 高密度电芯 | 4000mAh 实标 |
+| ... | ... | ... | ... | ... | ... | ... |
+```
+
+---
+
+## 模板 5: 选品决策评估(五维评分)
+
+### 使用场景
+
+Step 5: 评估与决策 - LLM 进行量化评分并给出决策
+
+### Prompt 模板
+
+```markdown
+你是一位投资决策专家,需要基于市场数据进行选品决策量化评分。
+
+## 任务目标
+
+对选品机会进行五维加权评分,给出明确的进入决策建议。
+
+## 评分体系
+
+| 维度 | 权重 | 评分标准 (1-10) | 数据来源 |
+|------|------|---------------|----------|
+| 市场规模 | 20% | 月销额>$10M=10, >$5M=8, >$1M=6, 其他=4 | Top100 月销额 |
+| 竞争格局 | 25% | CR3<30%=10, <50%=7, 其他=4 | CR3 + 新品占比 |
+| 需求清晰度 | 15% | 关键词+交叉分析明确=10, 较明确=7, 模糊=4 | 关键词数据 + 交叉分析 |
+| 进入壁垒(反) | 20% | 低壁垒=10, 中=6, 高=3 | 六类壁垒评估 |
+| 盈利能力 | 20% | 毛利>40%=10, >30%=8, >20%=6, 其他=4 | 成本测算 |
+
+## 决策矩阵
+
+| 加权总分 | 决策结论 | 详细说明 |
+|----------|----------|----------|
+| 7.5-10 | **建议进入** | 优先推进,快速执行 |
+| 6.0-7.4 | **谨慎进入** | 需精准定位细分市场,明确准入条件 |
+| 4.0-5.9 | **暂缓观望** | 需更多数据验证,或等待时机 |
+| 0-3.9 | **不建议进入** | 风险大于机会,放弃 |
+
+## 输入数据
+
+### 市场概况
+{market_overview}
+
+### 竞争格局
+{competition_summary}
+
+### 交叉分析
+{cross_analysis_summary}
+
+### 进入壁垒
+{barriers_summary}
+
+### 成本测算
+| 项目 | 金额 |
+|------|------|
+| 采购成本 | $XX |
+| FBA 费用 | $XX |
+| 头程物流 | $XX |
+| 预估毛利 | $XX |
+| 预估毛利率 | XX% |
+
+## 输出格式
+
+### 选品决策评估表
+
+| 维度 | 权重 | 评分(1-10) | 加权分 | 依据 |
+|------|------|-----------|--------|------|
+| 市场规模 | 20% | [评分] | [分数] | [数据依据] |
+| 竞争格局 | 25% | [评分] | [分数] | [数据依据] |
+| ... | ... | ... | ... | ... |
+| **总分** | 100% | - | **[总分]** | - |
+
+### 决策结论
+
+- **决策**: 建议进入 / 谨慎进入 / 暂缓观望 / 不建议进入
+- **综合得分**: [X.XX]/10
+- **核心洞察**: [2-3句话总结]
+
+### 详细说明
+
+**准入条件** (如适用):
+1. [必须满足的条件1]
+2. [必须满足的条件2]
+...
+
+**禁止进入**:
+- [明确禁止的场景]
+
+**风险提示**:
+- [关键风险及缓解方案]
+```
+
+---
+
+## 模板 6: 产品矩阵规划
+
+### 使用场景
+
+Step 5: 评估与决策 - LLM 规划具体产品矩阵
+
+### Prompt 模板
+
+```markdown
+你是一位产品经理,需要基于分析结果规划具体的产品矩阵。
+
+## 任务目标
+
+规划 Tier 1(必须)和 Tier 2/3(可选)产品的具体规格。
+
+## 输入数据
+
+### 机会优先级
+{opportunities_ranking}
+
+### 供需缺口
+{gaps_summary}
+
+### 痛点分析
+{pain_points_summary}
+
+## 产品矩阵要求
+
+### Tier 1 产品(必须完整具体)
+
+### 结构模板
+
+```
+### Tier 1: [产品定位一句话]
+
+**目标市场**:[维度组合空白/机会,如:65W + 数显 + $50-80 价格带]
+**决策理由**:[基于 cross_analysis ch04 + pain_points ch06 的数据]
+
+| 维度 | 规格 | 决策依据 |
+|------|------|----------|
+| [维度1] | [具体值] | [为什么选这个值 - 引用数据] |
+| [维度2] | [具体值] | [为什么选这个值 - 引用数据] |
+| ... | ... | ... |
+
+**目标定价**:$XX.XX(基于 Step 5 测算,毛利率 XX%)
+**差异化主张**:[一句话核心卖点,区别于竞品]
+**对标竞品**:[ASIN] [品牌] $XX — 我们的优势:[具体差异]
+**预估月销潜力**:XX-XX 件/月(基于同组合竞品表现推算)
+```
+
+### ⛔ 硬性要求
+
+1. **禁止占位语**:
+ - ❌ "待确认"、"待定"、"建议进一步调研"
+ - ✅ 具体数值和明确依据
+
+2. **必须包含的字段**:
+ - 目标市场(维度组合空白)
+ - 决策理由(数据依据)
+ - 完整规格表(维度×规格×依据)
+ - 目标定价(基于成本测算)
+ - 差异化主张(一句话)
+ - 对标竞品(具体 ASIN)
+ - 预估月销潜力(基于数据推算)
+
+### Tier 2/3 产品(可选)
+
+如果有多个高价值机会,规划 Tier 2/3:
+- 简化规格(只列出关键差异化维度)
+- 预估优先级(何时进入)
+```
+
+---
+
+## 使用指南
+
+### 在 SKILL.md 中引用
+
+```markdown
+### Step 2: 属性标注
+
+使用 LLM Prompt 模板 [属性标注] 进行维度提取:
+
+> 请参考 `references/prompt_templates.md` 中的 [模板 1: 属性标注] 对以下产品标题进行维度标注...
+
+### Step 3: 交叉分析
+
+使用 LLM Prompt 模板 [交叉分析] 发现供需缺口:
+
+> 请参考 `references/prompt_templates.md` 中的 [模板 2: 交叉分析] 对已标注数据进行分析...
+```
+
+### 动态调整
+
+根据品类特征调整模板:
+- **电子产品**:维度通常包括功率、容量、防水、接口类型等
+- **家居产品**:维度通常包括材质、尺寸、风格、颜色等
+- **服装配饰**:维度通常包括材质、尺码、风格、季节等
+
+---
+
+*版本: v1.1 (中文决策术语) | 最后更新: 2026-03-19*
+
+## 更新日志
+
+### v1.1 (2026-03-19)
+- ✅ Go/No-Go 评分 → 选品决策评估(五维评分)
+- ✅ 决策结论更清晰:建议进入/谨慎进入/暂缓观望/不建议进入
+- ✅ 输出格式新增:准入条件、禁止进入、风险提示
+
+### v1.0 (2026-03-19)
diff --git a/skills/amazon-sorftime-research-market-skill/references/sorftime-mcp-api.md b/skills/amazon-sorftime-research-market-skill/references/sorftime-mcp-api.md
new file mode 100644
index 00000000..6a4dafe4
--- /dev/null
+++ b/skills/amazon-sorftime-research-market-skill/references/sorftime-mcp-api.md
@@ -0,0 +1,508 @@
+# Sorftime MCP API 接口文档
+
+## 调用方式
+```bash
+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](0-1) |
+| amazonOwned_sales_share | string | 否 | 亚马逊自营占比[x,y](0-1) |
+| top100_top400_sales_share | string | 否 | Top100在Top400占比[x,y](0-1) |
+| newproduct_sales_share | string | 否 | 新品销量占比[x,y](0-1) |
+
+---
+
+### 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*
diff --git a/skills/amazon-sorftime-research-market-skill/references/troubleshooting.md b/skills/amazon-sorftime-research-market-skill/references/troubleshooting.md
new file mode 100644
index 00000000..e2b264ed
--- /dev/null
+++ b/skills/amazon-sorftime-research-market-skill/references/troubleshooting.md
@@ -0,0 +1,264 @@
+# Product-Research 故障排查指南
+
+## 快速诊断流程
+
+```
+问题发生
+ ↓
+是 API 调用错误? → 查看第2节
+ ↓
+是数据解析错误? → 查看第3节
+ ↓
+是编码问题? → 查看第4节
+ ↓
+其他问题 → 查看第5节
+```
+
+---
+
+## 1. 数据采集失败
+
+### 问题: 类目搜索返回 406 错误
+
+**症状**: `HTTP Error 406: Not Acceptable`
+
+**原因**: API 参数名称错误
+
+**解决方案**:
+```python
+# ❌ 错误写法
+client._call('category_search_from_product_name', {
+ 'amzSite': 'US',
+ 'productName': 'bluetooth speaker' # 错误!
+})
+
+# ✅ 正确写法
+client.search_category_by_product_name('US', 'bluetooth speaker')
+# 或直接调用
+client._call('category_name_search', {
+ 'amzSite': 'US',
+ 'searchName': 'bluetooth speaker' # 正确!
+})
+```
+
+### 问题: 找不到类目
+
+**症状**: 返回空列表或 "未查询到对应类目"
+
+**诊断步骤**:
+1. 检查关键词拼写
+2. 尝试更通用的关键词 (如 "speaker" 而非 "portable bluetooth speaker")
+3. 检查站点是否支持该类目
+
+**解决方案**:
+```python
+# 尝试多个关键词
+keywords = ['bluetooth speaker', 'portable speaker', 'wireless speaker', 'speaker']
+for kw in keywords:
+ result = client.search_category_by_product_name('US', kw)
+ if result:
+ break
+```
+
+---
+
+## 2. API 调用错误
+
+### 问题: "An error occurred invoking 'xxx'"
+
+**原因**: 工具名称不存在
+
+**常用工具名称对照**:
+
+| 功能 | 正确名称 | 错误名称 |
+|------|----------|----------|
+| 类目搜索 | `category_name_search` | `category_search_from_product_name` ❌ |
+| 类目报告 | `category_report` | - |
+| 关键词详情 | `keyword_detail` | - |
+| 产品详情 | `product_detail` | - |
+
+### 问题: 认证失败
+
+**症状**: `Authentication required`
+
+**检查**:
+```bash
+# 验证 API Key
+curl "https://mcp.sorftime.com?key=YOUR_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
+```
+
+**解决方案**:
+1. 检查 `.mcp.json` 文件
+2. 确认 URL 格式: `https://mcp.sorftime.com?key=XXX`
+3. 获取新 API Key: https://sorftime.com/zh-cn/mcp
+
+---
+
+## 3. 数据解析错误
+
+### 问题: Top100 数据解析失败
+
+**症状**: `KeyError: 'Top100产品'` 或产品列表为空
+
+**原因**: Sorftime 返回格式可能有多种变体
+
+**解决方案**:
+```python
+def safe_extract_products(data):
+ """安全提取产品列表"""
+ if not isinstance(data, dict):
+ return []
+
+ # 尝试多个可能的键名
+ products = (
+ data.get('Top100产品') or
+ data.get('top100_products') or
+ data.get('products') or
+ data.get('productList') or
+ data.get('product_list') or
+ []
+ )
+
+ return products
+```
+
+### 问题: SSE 响应解析失败
+
+**症状**: `API 返回数据解析失败`
+
+**调试方法**:
+```python
+# 保存原始响应用于调试
+import os
+debug_file = os.path.join(output_dir, 'raw_response.txt')
+with open(debug_file, 'w', encoding='utf-8') as f:
+ f.write(response)
+
+# 检查响应格式
+print("原始响应前500字符:")
+print(response[:500])
+```
+
+---
+
+## 4. 编码问题
+
+### 问题: 中文显示为乱码
+
+**症状**: `产å` 或类似字符
+
+**解决方案**: 使用 `api_client.py` 中的修复函数
+
+```python
+from api_client import fix_mojibake
+
+fixed_text = fix_mojibake(bad_text)
+```
+
+### 问题: Unicode 转义未解码
+
+**症状**: `\u4ea7\u54c1` 格式
+
+**解决方案**:
+```python
+import codecs
+
+decoded = codecs.decode(escaped_text, 'unicode-escape')
+```
+
+---
+
+## 5. 其他常见问题
+
+### 问题: 模块导入失败
+
+**症状**: `ModuleNotFoundError: No module named 'xxx'`
+
+**解决方案**:
+```python
+# 确保脚本目录在 Python 路径中
+import sys
+import os
+
+script_dir = os.path.dirname(os.path.abspath(__file__))
+sys.path.insert(0, script_dir)
+
+from api_client import SorftimeClient
+```
+
+### 问题: 文件保存失败
+
+**症状**: `FileNotFoundError` 或权限错误
+
+**解决方案**:
+```python
+# 确保目录存在
+os.makedirs(output_dir, exist_ok=True)
+
+# 使用绝对路径
+output_path = os.path.abspath(output_dir)
+```
+
+---
+
+## 6. 调试技巧
+
+### 启用详细日志
+
+```python
+import logging
+
+logging.basicConfig(level=logging.DEBUG)
+logger = logging.getLogger(__name__)
+
+# 在代码中添加日志
+logger.debug(f"API 请求: {method_name} {arguments}")
+logger.info(f"获取到 {len(products)} 个产品")
+```
+
+### 分步测试
+
+```python
+# 测试 API 连接
+client = SorftimeClient()
+result = client._call('category_name_search', {
+ 'amzSite': 'US',
+ 'searchName': 'speaker'
+})
+print(json.dumps(result, ensure_ascii=False, indent=2))
+```
+
+### 使用 curl 直接测试
+
+```bash
+# 测试类目搜索
+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": "speaker"
+ }
+ }
+ }'
+```
+
+---
+
+## 7. 获取帮助
+
+1. 检查 `SKILL.md` 中的执行流程说明
+2. 查看 `api_client.py` 中的方法文档
+3. 参考 `category-selection` skill 的类似实现
+4. 在项目根目录运行测试命令验证环境
+
+---
+
+*最后更新: 2026-03-19*
diff --git a/skills/amazon-sorftime-research-market-skill/scripts/api_client.py b/skills/amazon-sorftime-research-market-skill/scripts/api_client.py
new file mode 100644
index 00000000..3b3c85d8
--- /dev/null
+++ b/skills/amazon-sorftime-research-market-skill/scripts/api_client.py
@@ -0,0 +1,870 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Sorftime API 客户端 - 统一的数据采集接口
+
+v2.2 - 修复大文件 JSON 解析问题
+
+为 product-research Skill 提供简洁的 API 调用方法:
+- 自动从 .mcp.json 读取 API Key
+- SSE 响应解析
+- Mojibake 编码修复
+- 控制字符转义(在 Unicode 解码后执行)
+- 返回干净的 Python dict
+
+使用示例:
+ from scripts.api_client import SorftimeClient
+
+ client = SorftimeClient()
+
+ # 获取类目 Top100
+ top100 = client.get_category_report(site="US", node_id=12345)
+
+ # 获取关键词详情
+ keyword = client.get_keyword_detail(site="US", keyword="your keyword")
+
+ # 获取产品详情
+ product = client.get_product_detail(site="US", asin="B0XXXXXXXX")
+
+ # 获取产品评论
+ reviews = client.get_product_reviews(site="US", asin="B0XXXXXXXX", review_type="Negative")
+"""
+
+import os
+import json
+import re
+import codecs
+import subprocess
+import sys
+from datetime import datetime
+from typing import Optional, Dict, List, Any
+from pathlib import Path
+
+
+# ============================================================================
+# API 配置
+# ============================================================================
+
+def get_project_root():
+ """获取项目根目录(.claude 的父目录)"""
+ path = os.path.abspath(__file__)
+ while path != os.path.dirname(path):
+ if os.path.basename(path) == '.claude':
+ return os.path.dirname(path)
+ path = os.path.dirname(path)
+ return os.getcwd()
+
+
+def get_api_key():
+ """
+ 从 .mcp.json 读取 Sorftime API Key
+
+ Returns:
+ str: API Key
+ """
+ project_root = get_project_root()
+ mcp_config_path = os.path.join(project_root, '.mcp.json')
+
+ if os.path.exists(mcp_config_path):
+ try:
+ with open(mcp_config_path, 'r', encoding='utf-8', errors='ignore') as f:
+ content = f.read()
+ config = json.loads(content)
+
+ # 从 URL 中提取 API key: https://mcp.sorftime.com?key=XXX
+ sorftime_url = config.get('mcpServers', {}).get('sorftime', {}).get('url', '')
+ if 'key=' in sorftime_url:
+ api_key = sorftime_url.split('key=')[-1]
+ if api_key:
+ return api_key
+ except Exception as e:
+ print(f"⚠ 读取 .mcp.json 失败: {e}")
+
+ # 尝试环境变量
+ api_key = os.environ.get('SORFTIME_API_KEY', '')
+ if api_key:
+ return api_key
+
+ raise ValueError(
+ "API Key 未找到。请确保:\n"
+ "1. .mcp.json 文件存在并包含 sorftime 配置,或\n"
+ "2. 设置环境变量 SORFTIME_API_KEY"
+ )
+
+
+# ============================================================================
+# 数据处理工具函数
+# ============================================================================
+
+def safe_int(value, default=0):
+ """安全转换为整数"""
+ if isinstance(value, (int, float)):
+ return int(value)
+ if isinstance(value, str):
+ cleaned = re.sub(r'[^\d.-]', '', value)
+ try:
+ return int(float(cleaned)) if cleaned else default
+ except ValueError:
+ return default
+ return default
+
+
+def safe_float(value, default=0.0):
+ """安全转换为浮点数"""
+ if isinstance(value, (int, float)):
+ return float(value)
+ if isinstance(value, str):
+ cleaned = re.sub(r'[^\d.-]', '', value)
+ try:
+ return float(cleaned) if cleaned else default
+ except ValueError:
+ return default
+ return default
+
+
+def fix_mojibake(text):
+ """
+ 修复 Mojibake 编码问题 (UTF-8/Latin-1 双重编码)
+
+ 问题: UTF-8 字节被错误解释为 Latin-1
+ 解决: 将错误编码的字符串重新编码为 Latin-1,然后用 UTF-8 解码
+ """
+ if isinstance(text, str):
+ try:
+ return text.encode('latin-1').decode('utf-8')
+ except:
+ return text
+ elif isinstance(text, dict):
+ return {fix_mojibake(k): fix_mojibake(v) for k, v in text.items()}
+ elif isinstance(text, list):
+ return [fix_mojibake(item) for item in text]
+ return text
+
+
+def escape_control_chars_in_json_strings(json_str):
+ """
+ 转义 JSON 字符串值中的控制字符
+
+ 问题: API 返回的 JSON 字符串值中包含原始的换行符、制表符等控制字符
+ 解决: 在保持 JSON 结构不变的情况下,只转义字符串值内的控制字符
+ """
+ result = []
+ i = 0
+ in_string = False
+ escape_next = False
+
+ while i < len(json_str):
+ c = json_str[i]
+
+ if escape_next:
+ result.append(c)
+ escape_next = False
+ i += 1
+ continue
+
+ if c == '\\':
+ result.append(c)
+ escape_next = True
+ i += 1
+ continue
+
+ if c == '"':
+ in_string = not in_string
+ result.append(c)
+ i += 1
+ continue
+
+ if in_string:
+ if c == '\n':
+ result.append('\\n')
+ elif c == '\r':
+ result.append('\\r')
+ elif c == '\t':
+ result.append('\\t')
+ elif ord(c) < 32:
+ result.append(' ')
+ else:
+ result.append(c)
+ else:
+ result.append(c)
+ i += 1
+
+ return ''.join(result)
+
+
+def extract_json_object(text):
+ """
+ 从文本中提取完整的 JSON 对象
+
+ 使用括号匹配算法,支持嵌套结构
+ """
+ stack = []
+ start_idx = None
+
+ for i, char in enumerate(text):
+ if char in '{[':
+ if not stack:
+ start_idx = i
+ stack.append(char)
+ elif char in '}]':
+ if stack:
+ expected = '}' if char == '}' else ']'
+ opening = '{' if expected == '}' else '['
+ if stack[-1] == opening:
+ stack.pop()
+ if not stack:
+ json_str = text[start_idx:i+1]
+ try:
+ return json.loads(json_str)
+ except json.JSONDecodeError:
+ continue
+
+ return None
+
+
+def decode_sse_response(content):
+ """
+ 解码 Sorftime SSE 响应
+
+ 处理流程:
+ 1. 清理控制字符
+ 2. 解析 SSE 格式 (event: message, data: {...})
+ 3. Unicode 解码
+ 4. Mojibake 修复
+ 5. 提取 JSON 对象
+
+ Args:
+ content: SSE 响应内容(字符串)
+
+ Returns:
+ dict: 解码后的数据
+ """
+ # 清理控制字符
+ content = re.sub(r'[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f-\x9f]', '', content)
+
+ for line in content.split('\n'):
+ if line.startswith('data: '):
+ json_text = line[6:] # 去掉 'data: ' 前缀
+ try:
+ data = json.loads(json_text)
+ result_text = data.get('result', {}).get('content', [{}])[0].get('text', '')
+ if result_text:
+ # Unicode 解码
+ decoded = codecs.decode(result_text, 'unicode-escape')
+
+ # Mojibake 修复
+ decoded = fix_mojibake(decoded)
+
+ # 转义 JSON 字符串值内的控制字符(关键步骤!)
+ decoded = escape_control_chars_in_json_strings(decoded)
+
+ # 清理剩余的控制字符
+ decoded = re.sub(r'[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f-\x9f]', '', decoded)
+
+ # 提取 JSON
+ json_obj = extract_json_object(decoded)
+ if json_obj:
+ return json_obj
+ except Exception:
+ continue
+
+ # 如果 SSE 解析失败,尝试直接解析
+ try:
+ return json.loads(content)
+ except:
+ pass
+
+ return None
+
+
+# ============================================================================
+# Sorftime API 客户端
+# ============================================================================
+
+class SorftimeClient:
+ """
+ Sorftime API 客户端
+
+ 提供简洁的方法调用 Sorftime MCP API
+ """
+
+ # API 工具名称映射
+ TOOLS = {
+ # 类目相关
+ 'search_categories_broadly': 'search_categories_broadly', # 多维度广泛搜索类目
+ 'category_name_search': 'category_name_search', # 按类目名称搜索(使用 searchName 参数)
+ 'category_report': 'category_report',
+ 'category_trend': 'category_trend',
+ 'category_keywords': 'category_keywords',
+
+ # 关键词相关
+ 'keyword_detail': 'keyword_detail',
+ 'keyword_search_results': 'keyword_search_results',
+ 'keyword_extends': 'keyword_extends',
+ 'keyword_trend': 'keyword_trend',
+
+ # 产品相关
+ 'product_detail': 'product_detail',
+ 'product_reviews': 'product_reviews',
+ 'product_traffic_terms': 'product_traffic_terms',
+ 'product_trend': 'product_trend',
+ 'product_search': 'product_search',
+
+ # 选品相关
+ 'potential_product': 'potential_product',
+ 'competitor_product_keywords': 'competitor_product_keywords',
+
+ # 供应链
+ 'ali1688': 'ali1688_similar_product',
+ }
+
+ def __init__(self, api_key: Optional[str] = None):
+ """
+ 初始化客户端
+
+ Args:
+ api_key: Sorftime API Key,如果不提供则从 .mcp.json 读取
+ """
+ self.api_key = api_key or get_api_key()
+ self.api_url = f'https://mcp.sorftime.com?key={self.api_key}'
+ self.request_id = 0
+
+ def _call(self, tool_name: str, arguments: Dict[str, Any]) -> tuple:
+ """
+ 调用 Sorftime API
+
+ Args:
+ tool_name: API 工具名称
+ arguments: API 参数
+
+ Returns:
+ tuple: (解析后的数据 dict, 原始响应 str)
+ """
+ self.request_id += 1
+
+ payload = {
+ "jsonrpc": "2.0",
+ "id": self.request_id,
+ "method": "tools/call",
+ "params": {
+ "name": tool_name,
+ "arguments": arguments
+ }
+ }
+
+ try:
+ result = subprocess.run(
+ ['curl', '-s', '-X', 'POST', self.api_url,
+ '-H', 'Content-Type: application/json',
+ '-H', 'Accept: application/json, text/event-stream',
+ '-d', json.dumps(payload)],
+ capture_output=True,
+ text=True,
+ timeout=60,
+ check=True
+ )
+
+ # 返回原始响应和解析后的数据
+ raw_response = result.stdout
+ data = decode_sse_response(raw_response)
+
+ if data is None:
+ # 即使解析失败,也返回原始响应供调试
+ return None, raw_response
+
+ return data, raw_response
+
+ except subprocess.CalledProcessError as e:
+ raise RuntimeError(f"API 调用失败: {e}")
+ except subprocess.TimeoutExpired:
+ raise RuntimeError(f"API 调用超时")
+
+ # ========================================================================
+ # 类目相关 API
+ # ========================================================================
+
+ def search_category_by_product_name(
+ self,
+ site: str,
+ product_name: str
+ ) -> Dict[str, Any]:
+ """
+ 按产品名称搜索类目
+
+ Args:
+ site: 站点 (US, GB, DE, FR, IT, ES, CA, JP, etc.)
+ product_name: 产品名称
+
+ Returns:
+ dict: 搜索结果,包含类目列表
+ """
+ return self._call(
+ self.TOOLS['category_name_search'],
+ {"amzSite": site, "searchName": product_name} # 注意: 参数是 searchName
+ )
+
+ def search_category_by_name(
+ self,
+ site: str,
+ category_name: str
+ ) -> Dict[str, Any]:
+ """
+ 按类目名称搜索(别名方法,与 search_category_by_product_name 相同)
+
+ Args:
+ site: 站点
+ category_name: 类目名称
+
+ Returns:
+ dict: 搜索结果
+ """
+ return self.search_category_by_product_name(site, category_name)
+
+ def search_categories_broadly(
+ self,
+ site: str,
+ filters: Optional[Dict[str, Any]] = None
+ ) -> Dict[str, Any]:
+ """
+ 多维度广泛搜索类目(新增 - 用于蓝海发现)
+
+ Args:
+ site: 站点 (US, GB, DE, FR, IT, ES, CA, JP, etc.)
+ filters: 筛选条件(可选)
+ - top3Product_sales_share: Top3 产品销量占比上限(如 0.4 表示<40%)
+ - top3Brands_sales_share: Top3 品牌销量占比上限
+ - newProductSalesAmountShare: 新品销量占比下限(如 0.15 表示>15%)
+ - brandCount: 品牌数量下限(如 80 表示>80 个品牌)
+ - priceRange_min: 价格范围下限
+ - priceRange_max: 价格范围上限
+ - monthlySales_min: 月销量下限
+ - monthlySales_max: 月销量上限
+
+ Returns:
+ dict: 类目列表,包含:
+ - categories: 类目列表
+ - total: 总数
+ """
+ params = {"amzSite": site}
+ if filters:
+ params.update(filters)
+ return self._call(
+ self.TOOLS['search_categories_broadly'],
+ params
+ )
+
+ def get_category_report(
+ self,
+ site: str,
+ node_id: int
+ ) -> Dict[str, Any]:
+ """
+ 获取类目 Top100 报告
+
+ Args:
+ site: 站点
+ node_id: 类目 Node ID
+
+ Returns:
+ dict: Top100 产品数据
+ """
+ return self._call(
+ self.TOOLS['category_report'],
+ {"amzSite": site, "nodeId": str(node_id)}
+ )
+
+ def get_category_trend(
+ self,
+ site: str,
+ node_id: int,
+ trend_index: str = "NewProductSalesAmountShare"
+ ) -> Dict[str, Any]:
+ """
+ 获取类目趋势数据
+
+ Args:
+ site: 站点
+ node_id: 类目 Node ID
+ trend_index: 趋势类型
+ - NewProductSalesAmountShare: 新品销量占比
+ - NewProductProductShare: 新品数量占比
+ - etc.
+
+ Returns:
+ dict: 结构化趋势数据
+ {
+ "trend_data": [
+ {"date": "2024-03", "value": 33.35},
+ ...
+ ],
+ "metric": "新品占比",
+ "node_id": "99530371011"
+ }
+ """
+ raw_data, raw_response = self._call(
+ self.TOOLS['category_trend'],
+ {"amzSite": site, "nodeId": str(node_id), "trendIndex": trend_index}
+ )
+
+ # 转换原始格式为结构化格式
+ # 原始格式: ["2024年03月=33.35", "2024年04月=27.94", ...]
+ # 目标格式: {"trend_data": [{"date": "2024-03", "value": 33.35}, ...]}
+ if isinstance(raw_data, list):
+ trend_data = []
+ for item in raw_data:
+ if isinstance(item, str) and '=' in item:
+ # 解析 "2024年03月=33.35" 格式
+ date_str, value_str = item.split('=', 1)
+ # 转换日期格式: "2024年03月" -> "2024-03"
+ date_match = re.search(r'(\d{4})年(\d{2})月', date_str)
+ if date_match:
+ year, month = date_match.groups()
+ formatted_date = f"{year}-{month}"
+ try:
+ value = float(value_str)
+ trend_data.append({
+ "date": formatted_date,
+ "value": value
+ })
+ except ValueError:
+ continue
+
+ # 指标名称映射
+ metric_names = {
+ "NewProductSalesAmountShare": "新品销量占比",
+ "NewProductProductShare": "新品数量占比",
+ }
+
+ return {
+ "trend_data": trend_data,
+ "metric": metric_names.get(trend_index, trend_index),
+ "node_id": str(node_id),
+ "site": site
+ }
+
+ return raw_data
+
+ def get_category_keywords(
+ self,
+ site: str,
+ node_id: int,
+ page: int = 1
+ ) -> Dict[str, Any]:
+ """
+ 获取类目关键词
+
+ Args:
+ site: 站点
+ node_id: 类目 Node ID
+ page: 页码
+
+ Returns:
+ dict: 关键词数据
+ """
+ return self._call(
+ self.TOOLS['category_keywords'],
+ {"amzSite": site, "nodeId": str(node_id), "page": page}
+ )
+
+ # ========================================================================
+ # 关键词相关 API
+ # ========================================================================
+
+ def get_keyword_detail(
+ self,
+ site: str,
+ keyword: str
+ ) -> Dict[str, Any]:
+ """
+ 获取关键词详情
+
+ Args:
+ site: 站点
+ keyword: 关键词
+
+ Returns:
+ dict: 关键词详情(搜索量、CPC、自然位产品等)
+ """
+ return self._call(
+ self.TOOLS['keyword_detail'],
+ {"amzSite": site, "keyword": keyword}
+ )
+
+ def get_keyword_search_results(
+ self,
+ site: str,
+ keyword: str
+ ) -> Dict[str, Any]:
+ """
+ 获取关键词搜索结果(自然位产品)
+
+ Args:
+ site: 站点
+ keyword: 关键词
+
+ Returns:
+ dict: 自然位产品列表
+ """
+ return self._call(
+ self.TOOLS['keyword_search_results'],
+ {"amzSite": site, "searchKeyword": keyword}
+ )
+
+ def get_keyword_extends(
+ self,
+ site: str,
+ keyword: str
+ ) -> Dict[str, Any]:
+ """
+ 获取关键词延伸词
+
+ Args:
+ site: 站点
+ keyword: 关键词
+
+ Returns:
+ dict: 延伸词列表
+ """
+ return self._call(
+ self.TOOLS['keyword_extends'],
+ {"amzSite": site, "keyword": keyword}
+ )
+
+ # ========================================================================
+ # 产品相关 API
+ # ========================================================================
+
+ def get_product_detail(
+ self,
+ site: str,
+ asin: str
+ ) -> Dict[str, Any]:
+ """
+ 获取产品详情
+
+ Args:
+ site: 站点
+ asin: 产品 ASIN
+
+ Returns:
+ dict: 产品详情
+ """
+ return self._call(
+ self.TOOLS['product_detail'],
+ {"amzSite": site, "asin": asin}
+ )
+
+ def get_product_reviews(
+ self,
+ site: str,
+ asin: str,
+ review_type: str = "Both"
+ ) -> Dict[str, Any]:
+ """
+ 获取产品评论
+
+ Args:
+ site: 站点
+ asin: 产品 ASIN
+ review_type: 评论类型 (Both, Positive, Negative)
+
+ Returns:
+ dict: 评论列表
+ """
+ return self._call(
+ self.TOOLS['product_reviews'],
+ {"amzSite": site, "asin": asin, "reviewType": review_type}
+ )
+
+ def get_product_traffic_terms(
+ self,
+ site: str,
+ asin: str
+ ) -> Dict[str, Any]:
+ """
+ 获取产品流量关键词(反查)
+
+ Args:
+ site: 站点
+ asin: 产品 ASIN
+
+ Returns:
+ dict: 流量关键词列表
+ """
+ return self._call(
+ self.TOOLS['product_traffic_terms'],
+ {"amzSite": site, "asin": asin}
+ )
+
+ def get_product_trend(
+ self,
+ site: str,
+ asin: str
+ ) -> Dict[str, Any]:
+ """
+ 获取产品趋势
+
+ Args:
+ site: 站点
+ asin: 产品 ASIN
+
+ Returns:
+ dict: 趋势数据
+ """
+ return self._call(
+ self.TOOLS['product_trend'],
+ {"amzSite": site, "asin": asin}
+ )
+
+ def search_products(
+ self,
+ site: str,
+ search_name: str,
+ **filters
+ ) -> Dict[str, Any]:
+ """
+ 搜索产品
+
+ Args:
+ site: 站点
+ search_name: 搜索关键词
+ **filters: 筛选条件
+
+ Returns:
+ dict: 搜索结果
+ """
+ params = {"amzSite": site, "searchName": search_name}
+ params.update(filters)
+ return self._call(self.TOOLS['product_search'], params)
+
+ # ========================================================================
+ # 选品相关 API
+ # ========================================================================
+
+ def get_potential_products(
+ self,
+ site: str,
+ search_name: str,
+ **filters
+ ) -> Dict[str, Any]:
+ """
+ 获取潜力产品
+
+ Args:
+ site: 站点
+ search_name: 搜索关键词
+ **filters: 筛选条件
+
+ Returns:
+ dict: 潜力产品列表
+ """
+ params = {"amzSite": site, "searchName": search_name}
+ params.update(filters)
+ return self._call(self.TOOLS['potential_product'], params)
+
+ def get_competitor_keywords(
+ self,
+ site: str,
+ asin: str
+ ) -> Dict[str, Any]:
+ """
+ 获取竞品关键词布局
+
+ Args:
+ site: 站点
+ asin: 产品 ASIN
+
+ Returns:
+ dict: 竞品关键词布局
+ """
+ return self._call(
+ self.TOOLS['competitor_product_keywords'],
+ {"amzSite": site, "asin": asin}
+ )
+
+ # ========================================================================
+ # 供应链 API
+ # ========================================================================
+
+ def get_1688_products(
+ self,
+ search_name: str
+ ) -> Dict[str, Any]:
+ """
+ 获取 1688 相似产品
+
+ Args:
+ search_name: 搜索关键词
+
+ Returns:
+ dict: 1688 产品列表
+ """
+ return self._call(
+ self.TOOLS['ali1688'],
+ {"searchName": search_name}
+ )
+
+
+# ============================================================================
+# 便捷函数
+# ============================================================================
+
+def create_client() -> SorftimeClient:
+ """创建 Sorftime 客户端(便捷函数)"""
+ return SorftimeClient()
+
+
+# ============================================================================
+# 命令行接口
+# ============================================================================
+
+if __name__ == "__main__":
+ import argparse
+
+ parser = argparse.ArgumentParser(description="Sorftime API 客户端")
+ parser.add_argument("tool", choices=[
+ "category_report", "keyword_detail", "product_detail",
+ "product_reviews", "category_trend"
+ ], help="API 工具名称")
+ parser.add_argument("--site", default="US", help="站点")
+ parser.add_argument("--node-id", type=int, help="类目 Node ID")
+ parser.add_argument("--keyword", help="关键词")
+ parser.add_argument("--asin", help="产品 ASIN")
+ parser.add_argument("--output", "-o", help="输出文件路径")
+
+ args = parser.parse_args()
+
+ client = SorftimeClient()
+
+ if args.tool == "category_report":
+ if not args.node_id:
+ parser.error("--node-id 是必需的")
+ result = client.get_category_report(args.site, args.node_id)
+
+ elif args.tool == "keyword_detail":
+ if not args.keyword:
+ parser.error("--keyword 是必需的")
+ result = client.get_keyword_detail(args.site, args.keyword)
+
+ elif args.tool == "product_detail":
+ if not args.asin:
+ parser.error("--asin 是必需的")
+ result = client.get_product_detail(args.site, args.asin)
+
+ elif args.tool == "product_reviews":
+ if not args.asin:
+ parser.error("--asin 是必需的")
+ result = client.get_product_reviews(args.site, args.asin)
+
+ elif args.tool == "category_trend":
+ if not args.node_id:
+ parser.error("--node-id 是必需的")
+ result = client.get_category_trend(args.site, args.node_id)
+
+ # 输出结果
+ if args.output:
+ with open(args.output, 'w', encoding='utf-8') as f:
+ json.dump(result, f, ensure_ascii=False, indent=2)
+ print(f"✓ 结果已保存到: {args.output}")
+ else:
+ print(json.dumps(result, ensure_ascii=False, indent=2))
diff --git a/skills/amazon-sorftime-research-market-skill/scripts/collect_data.py b/skills/amazon-sorftime-research-market-skill/scripts/collect_data.py
new file mode 100644
index 00000000..992c8002
--- /dev/null
+++ b/skills/amazon-sorftime-research-market-skill/scripts/collect_data.py
@@ -0,0 +1,557 @@
+#!/usr/bin/env python3
+"""
+数据采集脚本 - product-research 技能
+
+优化版本 v3.1 - 完全通用化(移除硬编码类别词)
+
+使用方法:
+ python collect_data.py "your keyword" US
+
+或直接导入:
+ from collect_data import collect_data
+ result = collect_data("your keyword", "US")
+"""
+
+import sys
+import os
+import json
+import re
+from datetime import datetime
+
+# 添加脚本目录到路径
+script_dir = os.path.dirname(os.path.abspath(__file__))
+sys.path.insert(0, script_dir)
+
+from api_client import SorftimeClient
+
+
+def create_output_dir(keyword, site):
+ """创建输出目录(使用项目根目录)"""
+ date_str = datetime.now().strftime('%Y%m%d')
+ safe_keyword = keyword.replace(' ', '_').replace('/', '_')
+
+ # 获取项目根目录
+ # 脚本路径:.claude/skills/product-research/scripts/collect_data.py
+ # 需要向上四级:scripts → product-research → skills → .claude → amazon-mcp
+ current_dir = os.path.dirname(os.path.abspath(__file__))
+ project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(current_dir))))
+
+ output_dir = os.path.join(project_root, 'product-research-reports', f'{safe_keyword}_{site}_{date_str}')
+ raw_dir = os.path.join(output_dir, 'raw')
+ os.makedirs(raw_dir, exist_ok=True)
+ return output_dir, raw_dir, date_str
+
+
+def save_json(data, filepath):
+ """安全保存 JSON 文件"""
+ try:
+ with open(filepath, 'w', encoding='utf-8') as f:
+ json.dump(data, f, ensure_ascii=False, indent=2)
+ return True
+ except Exception as e:
+ print(f" ✗ 保存失败:{e}")
+ return False
+
+
+def discover_blue_ocean_categories(client, site, keyword, max_categories=5):
+ """
+ 【新增】蓝海市场发现 - 使用 search_categories_broadly
+
+ Args:
+ client: SorftimeClient 实例
+ site: 站点
+ keyword: 产品关键词(用于筛选相关类目)
+ max_categories: 返回的类目数量
+
+ Returns:
+ list: 符合条件的类目列表
+ """
+ print("\n[Step 0.5] 蓝海市场发现...")
+
+ # 筛选条件:适合新卖家的蓝海市场
+ filters = {
+ # 低集中度
+ "top3Product_sales_share": 0.4, # Top3 产品销量占比 < 40%
+ "top3Brands_sales_share": 0.5, # Top3 品牌销量占比 < 50%
+ # 新品活跃
+ "newProductSalesAmountShare": 0.15, # 新品销量占比 > 15%
+ # 市场分散
+ "brandCount": 50, # 品牌数量 > 50
+ # 价格适中
+ "priceRange_min": 10,
+ "priceRange_max": 50,
+ # 有一定规模
+ "monthlySales_min": 5000,
+ }
+
+ try:
+ result, _ = client.search_categories_broadly(site, filters)
+
+ if result and isinstance(result, dict):
+ categories = result.get('categories', [])
+
+ # 过滤与关键词相关的类目
+ if keyword:
+ keyword_lower = keyword.lower()
+ related_categories = []
+ for cat in categories:
+ cat_name = cat.get('categoryName', '').lower()
+ if keyword_lower in cat_name or keyword_lower in cat.get('description', '').lower():
+ related_categories.append(cat)
+
+ categories = related_categories[:max_categories]
+ else:
+ categories = categories[:max_categories]
+
+ print(f" ✓ 发现 {len(categories)} 个潜力类目:")
+ for i, cat in enumerate(categories, 1):
+ print(f" {i}. {cat.get('categoryName', 'N/A')} "
+ f"(新品占比:{cat.get('newProductSalesAmountShare', 0)*100:.1f}%, "
+ f"Top3 占比:{cat.get('top3Product_sales_share', 0)*100:.1f}%)")
+
+ return categories
+ else:
+ print(f" ⚠ 未找到符合条件的类目")
+ return []
+
+ except Exception as e:
+ print(f" ⚠ 蓝海发现失败:{e} (非关键,继续执行)")
+ return []
+
+
+def find_potential_products(client, site, keyword, max_products=20):
+ """
+ 【新增】潜力产品发现 - 使用 potential_product
+
+ Args:
+ client: SorftimeClient 实例
+ site: 站点
+ keyword: 产品关键词
+ max_products: 返回的产品数量
+
+ Returns:
+ list: 潜力产品列表
+ """
+ print(f"\n[Step 1.3] 潜力产品发现:{keyword}...")
+
+ # 筛选条件:有潜力的新品
+ filters = {
+ "monthlySales_min": 500, # 月销量 > 500
+ "price_min": 10, # 价格 > $10
+ "price_max": 50, # 价格 < $50
+ "rating_min": 4.0, # 评分 > 4.0
+ "daysOnMarket_max": 180, # 上架时间 < 6 个月
+ }
+
+ try:
+ result, _ = client.get_potential_products(site, keyword, **filters)
+
+ if result and isinstance(result, dict):
+ products = result.get('products', []) or result.get('productList', [])
+
+ if not products:
+ # 尝试不同的返回格式
+ products = result.get('list', [])
+
+ print(f" ✓ 发现 {len(products)} 个潜力产品")
+
+ # 显示 Top 5
+ for i, p in enumerate(products[:5], 1):
+ asin = p.get('ASIN', 'N/A')
+ brand = p.get('品牌', 'N/A')
+ sales = p.get('月销量', 'N/A')
+ price = p.get('价格', 'N/A')
+ rating = p.get('星级', 'N/A')
+ days = p.get('上线天数', 'N/A')
+ print(f" {i}. {asin} | {brand} | 月销{sales} | ${price} | {rating}星 | {days}天")
+
+ return products[:max_products]
+ else:
+ print(f" ⚠ 未找到潜力产品")
+ return []
+
+ except Exception as e:
+ print(f" ⚠ 潜力产品发现失败:{e} (非关键,继续执行)")
+ return []
+
+
+def get_keyword_extends_data(client, site, keyword):
+ """
+ 【新增】获取关键词延伸词 - 用于维度发现
+
+ Args:
+ client: SorftimeClient 实例
+ site: 站点
+ keyword: 关键词
+
+ Returns:
+ dict: 延伸词数据
+ """
+ print(f"\n[Step 1.4] 获取关键词延伸词:{keyword}...")
+
+ try:
+ result, _ = client.get_keyword_extends(site, keyword)
+
+ if result:
+ # 解析延伸词
+ extends = result.get('extends', []) or result.get('keywords', []) or result.get('list', [])
+
+ if isinstance(extends, list) and len(extends) > 0:
+ print(f" ✓ 获取 {len(extends)} 个延伸词")
+
+ # 提取高频修饰词(用于维度发现)
+ modifiers = []
+ for item in extends:
+ if isinstance(item, dict):
+ word = item.get('keyword', item.get('word', ''))
+ search_volume = item.get('searchVolume', item.get('monthly_search', 0))
+ else:
+ word = str(item)
+ search_volume = 0
+
+ # 过滤掉品类通用词
+ if word and keyword.lower() not in word.lower():
+ modifiers.append({
+ 'word': word,
+ 'search_volume': search_volume
+ })
+
+ # 按搜索量排序
+ modifiers.sort(key=lambda x: x['search_volume'], reverse=True)
+
+ print(f" ✓ 提取 {len(modifiers)} 个修饰词(用于维度发现)")
+ if modifiers:
+ print(f" Top 5 修饰词:{', '.join([m['word'] for m in modifiers[:5]])}")
+
+ return {
+ 'extends': extends,
+ 'modifiers': modifiers[:20] # 保留 Top 20
+ }
+
+ print(f" ⚠ 延伸词数据为空")
+ return {}
+
+ except Exception as e:
+ print(f" ⚠ 延伸词获取失败:{e} (非关键,继续执行)")
+ return {}
+
+
+def collect_data(keyword, site='US', max_keywords=3, use_blue_ocean=False):
+ """
+ 执行完整的数据采集流程
+
+ Args:
+ keyword: 产品/类目关键词
+ site: 站点代码 (US, GB, DE, etc.)
+ max_keywords: 采集关键词数量
+ use_blue_ocean: 是否启用蓝海发现模式
+
+ Returns:
+ dict: 采集结果摘要
+ """
+ print(f"🔍 选品数据采集:{keyword} ({site})")
+ print("=" * 60)
+
+ # 初始化
+ client = SorftimeClient()
+ output_dir, raw_dir, date_str = create_output_dir(keyword, site)
+
+ # 结果摘要
+ result = {
+ 'keyword': keyword,
+ 'site': site,
+ 'date': date_str,
+ 'category_name': None,
+ 'node_id': None,
+ 'steps_completed': [],
+ 'errors': [],
+ 'blue_ocean_categories': [],
+ 'potential_products': [],
+ 'keyword_extends': {}
+ }
+
+ # ========== Step 0.5: 蓝海市场发现(可选) ==========
+ if use_blue_ocean:
+ blue_ocean_cats = discover_blue_ocean_categories(client, site, keyword)
+ if blue_ocean_cats:
+ result['blue_ocean_categories'] = blue_ocean_cats
+ save_json(blue_ocean_cats, os.path.join(raw_dir, 'blue_ocean_categories.json'))
+ result['steps_completed'].append('blue_ocean_discovery')
+
+ # ========== Step 1: 搜索类目 ==========
+ print("\n[Step 1] 搜索类目...")
+
+ category_result = None
+ used_keyword = keyword
+
+ try:
+ print(f" 搜索: '{keyword}'...", end=' ')
+ category_result, raw = client.search_category_by_product_name(site, keyword)
+
+ if category_result and isinstance(category_result, list) and len(category_result) > 0:
+ print(f"✓ 找到 {len(category_result)} 个类目")
+ else:
+ error_msg = f"类目搜索失败:未找到与 '{keyword}' 匹配的类目。请使用该类别最通用的核心名词(如使用 'camera' 而非 'digital wireless camera')"
+ print(f" ✗ {error_msg}")
+ raise Exception(error_msg)
+ except Exception as e:
+ print(f" ✗ 错误: {str(e)}")
+ raise
+
+ # 使用找到的类目
+ first_cat = category_result[0]
+ node_id = first_cat.get('nodeId') or first_cat.get('NodeId')
+ category_name = first_cat.get('categoryName') or first_cat.get('Name')
+
+ result['category_name'] = category_name
+ result['node_id'] = str(node_id)
+ result['searched_keyword'] = used_keyword # 记录实际使用的搜索词
+
+ print(f" ✓ 最终类目:{category_name}")
+ print(f" ✓ Node ID: {node_id}")
+ if used_keyword != keyword:
+ print(f" ℹ 使用搜索词: '{used_keyword}' (原词: '{keyword}')")
+
+ save_json(category_result, os.path.join(raw_dir, 'category_info.json'))
+ result['steps_completed'].append('category_search')
+
+ # ========== Step 2: 获取 Top100 ==========
+ print(f"\n[Step 2] 获取 Top100 产品数据...")
+ top100 = None
+ try:
+ top100, raw_response = client.get_category_report(site, result['node_id'])
+
+ # 检查返回的数据是否有效
+ if top100 is None or not isinstance(top100, dict) or len(top100) == 0:
+ raise ValueError("category_report 返回无效数据")
+
+ products = top100.get('Top100产品', []) or top100.get('Top100 产品', []) or top100.get('products', [])
+ stats = top100.get('类目统计报告', {})
+
+ print(f" ✓ 产品数量:{len(products)}")
+ if stats:
+ monthly_revenue = stats.get('top100 产品月销额', 0)
+ print(f" ✓ 类目月销额:${monthly_revenue}")
+
+ save_json(top100, os.path.join(raw_dir, 'top100.json'))
+ result['steps_completed'].append('top100')
+
+ except Exception as e:
+ # category_report 不可用时,使用 product_search 作为替代
+ print(f" ⚠ category_report 不可用,尝试使用 product_search 替代...")
+ try:
+ # 使用 product_search 工具获取产品数据
+ search_result, _ = client._call('product_search', {
+ 'amzSite': site,
+ 'searchName': keyword,
+ 'page': 1
+ })
+
+ if isinstance(search_result, list) and len(search_result) > 0:
+ # 构造类似 top100 的数据结构
+ products = search_result
+
+ # 计算类目统计数据
+ total_monthly_sales = sum(p.get('月销量', 0) for p in products)
+ total_monthly_revenue = sum(p.get('月销额', 0) for p in products)
+ avg_price = total_monthly_revenue / len(products) if products else 0
+
+ top100_data = {
+ 'Top100产品': products,
+ '类目统计报告': {
+ 'top100 产品月销额': total_monthly_revenue,
+ 'top100 产品月销量': total_monthly_sales,
+ '平均价格': avg_price,
+ '产品数量': len(products),
+ '数据来源': 'product_search (替代 category_report)'
+ }
+ }
+
+ print(f" ✓ 产品数量:{len(products)}")
+ print(f" ✓ 类目月销额:${total_monthly_revenue:,.2f}")
+ print(f" ℹ 注意:使用 product_search 数据(非完整 Top100)")
+
+ save_json(top100_data, os.path.join(raw_dir, 'top100.json'))
+ result['steps_completed'].append('top100')
+ else:
+ error_msg = f"product_search 返回空数据"
+ print(f" ✗ {error_msg}")
+ result['errors'].append(error_msg)
+
+ except Exception as e2:
+ error_msg = f"Top100 获取失败(category_report 和 product_search 都失败):{e}, {e2}"
+ print(f" ✗ {error_msg}")
+ result['errors'].append(error_msg)
+
+ # ========== Step 3: 获取趋势数据 ==========
+ print(f"\n[Step 3] 获取类目趋势...")
+ try:
+ trend = client.get_category_trend(site, result['node_id'])
+ if trend:
+ print(f" ✓ 趋势数据已获取")
+ save_json(trend, os.path.join(raw_dir, 'trend.json'))
+ result['steps_completed'].append('trend')
+ else:
+ print(f" ⚠ 趋势数据为空(非关键)")
+ except Exception as e:
+ error_msg = f"趋势获取失败:{e}"
+ print(f" ⚠ {error_msg} (非关键)")
+ result['errors'].append(error_msg)
+
+ # ========== Step 4: 获取关键词详情(通用关键词生成) ==========
+ print(f"\n[Step 4] 获取关键词详情...")
+ keywords_data = {}
+
+ def generate_keyword_variants(base_kw, max_count=5):
+ """
+ 通用关键词变体生成策略
+
+ 策略:
+ 1. 原始词
+ 2. 尝试生成复数形式
+ 3. 添加常见修饰前缀
+ """
+ variants = [base_kw]
+
+ # 复数形式生成(通用规则)
+ # 规则1: 添加 's'
+ if not base_kw.endswith('s'):
+ variants.append(base_kw + 's')
+ # 规则2: 以 y 结尾,变 'ies'
+ if base_kw.endswith('y') and len(base_kw) > 1:
+ variants.append(base_kw[:-1] + 'ies')
+ # 规则3: 以 s, x, ch, sh 结尾,添加 'es'
+ if base_kw.endswith(('s', 'x', 'ch', 'sh')):
+ variants.append(base_kw + 'es')
+
+ # 添加常见修饰前缀(完全通用)
+ common_prefixes = ['portable', 'wireless', 'digital', 'smart']
+ for prefix in common_prefixes:
+ variants.append(f"{prefix} {base_kw}")
+
+ # 去重并限制数量
+ seen = set()
+ unique_variants = []
+ for v in variants:
+ v_lower = v.lower().strip()
+ if v_lower and v_lower not in seen and len(unique_variants) < max_count:
+ seen.add(v_lower)
+ unique_variants.append(v)
+
+ return unique_variants
+
+ base_keywords = generate_keyword_variants(keyword, max_keywords)
+
+ for kw in base_keywords:
+ try:
+ print(f" - {kw}...", end=' ', flush=True)
+ kw_data, _ = client.get_keyword_detail(site, kw)
+ if kw_data:
+ keywords_data[kw] = kw_data
+ print("✓")
+ else:
+ print("✗ (空响应)")
+ except Exception as e:
+ print(f"✗ ({str(e)[:50]})")
+
+ if keywords_data:
+ save_json(keywords_data, os.path.join(raw_dir, 'keywords.json'))
+ result['steps_completed'].append('keywords')
+ print(f" ✓ 成功:{len(keywords_data)}/{len(base_keywords)} 个关键词")
+
+ # ========== Step 5: 获取关键词延伸词(新增) ==========
+ extends_data = get_keyword_extends_data(client, site, keyword)
+ if extends_data:
+ result['keyword_extends'] = extends_data
+ save_json(extends_data, os.path.join(raw_dir, 'keyword_extends.json'))
+ result['steps_completed'].append('keyword_extends')
+
+ # ========== Step 6: 发现潜力产品(新增) ==========
+ potential_products = find_potential_products(client, site, keyword)
+ if potential_products:
+ result['potential_products'] = potential_products
+ save_json(potential_products, os.path.join(raw_dir, 'potential_products.json'))
+ result['steps_completed'].append('potential_products')
+
+ # ========== Step 7: 保存汇总数据 ==========
+ print(f"\n[Step 7] 保存汇总数据...")
+
+ summary = {
+ "metadata": {
+ "keyword": keyword,
+ "site": site,
+ "date": date_str,
+ "node_id": result['node_id'],
+ "category_name": result['category_name'],
+ "collected_at": datetime.now().isoformat()
+ },
+ "files": {
+ "category_info": "raw/category_info.json",
+ "top100": "raw/top100.json",
+ "trend": "raw/trend.json",
+ "keywords": "raw/keywords.json",
+ "keyword_extends": "raw/keyword_extends.json" if extends_data else None,
+ "potential_products": "raw/potential_products.json" if potential_products else None,
+ "blue_ocean_categories": "raw/blue_ocean_categories.json" if result['blue_ocean_categories'] else None
+ },
+ "status": "success" if len(result['errors']) == 0 else "partial",
+ "steps_completed": result['steps_completed'],
+ "errors": result['errors'],
+ # 预留 Dashboard 需要的数据结构(初始为空,由后续分析填充)
+ "market_overview": {},
+ "price_ranges": [],
+ "product_types": [],
+ "cross_analysis": {"price_type_matrix": []},
+ "top_brands": [],
+ "competitors": [],
+ "voc_analysis": {"dimensions": [], "summary": ""},
+ "barriers": [],
+ "decision": {},
+ "trend_data": [],
+ "keywords": {}
+ }
+
+ save_json(summary, os.path.join(output_dir, 'data.json'))
+
+ # ========== 完成 ==========
+ print("\n" + "=" * 60)
+ print(f"✓ 数据采集完成!")
+ print(f" 输出目录:{output_dir}")
+ print(f" 完成步骤:{', '.join(result['steps_completed'])}")
+
+ if result['errors']:
+ print(f"\n⚠ 错误 ({len(result['errors'])}):")
+ for err in result['errors']:
+ print(f" - {err}")
+
+ print("=" * 60)
+
+ return result
+
+
+# ============================================================================
+# 命令行接口
+# ============================================================================
+
+if __name__ == "__main__":
+ import argparse
+
+ parser = argparse.ArgumentParser(
+ description="product-research 数据采集脚本(通用版本)",
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ epilog="""
+示例:
+ python collect_data.py "speaker" US
+ python collect_data.py "sofa" DE --keywords 5
+ python collect_data.py "mat" US --blue-ocean # 启用蓝海发现
+ """
+ )
+
+ parser.add_argument('keyword', help='产品/类目关键词')
+ parser.add_argument('site', nargs='?', default='US', help='站点代码 (默认:US)')
+ parser.add_argument('--keywords', '-k', type=int, default=3, help='采集关键词数量 (默认:3)')
+ parser.add_argument('--blue-ocean', action='store_true', help='启用蓝海发现模式')
+
+ args = parser.parse_args()
+
+ collect_data(args.keyword, args.site, args.keywords, use_blue_ocean=args.blue_ocean)
diff --git a/skills/amazon-sorftime-research-market-skill/scripts/fix_data_json.py b/skills/amazon-sorftime-research-market-skill/scripts/fix_data_json.py
new file mode 100644
index 00000000..e1476331
--- /dev/null
+++ b/skills/amazon-sorftime-research-market-skill/scripts/fix_data_json.py
@@ -0,0 +1,151 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+数据验证和修复脚本 - 确保 data.json 结构正确
+
+用法:
+ python fix_data_json.py path/to/data.json
+ python fix_data_json.py path/to/data.json --fix
+"""
+
+import sys
+import os
+import json
+import argparse
+from datetime import datetime
+from pathlib import Path
+
+
+def validate_data(data: dict) -> tuple[bool, list[str]]:
+ """验证数据结构"""
+ errors = []
+ warnings = []
+
+ # 必需字段检查
+ required_fields = ['metadata', 'market_overview']
+ for field in required_fields:
+ if field not in data:
+ errors.append(f"缺少必需字段: {field}")
+
+ # metadata 检查
+ if 'metadata' in data:
+ metadata = data['metadata']
+ required_metadata = ['category', 'site', 'date']
+ for field in required_metadata:
+ if field not in metadata:
+ warnings.append(f"metadata 缺少字段: {field}")
+
+ # market_overview 检查
+ if 'market_overview' in data:
+ mo = data['market_overview']
+ required_mo = ['top100_monthly_sales', 'top100_monthly_revenue', 'avg_price']
+ for field in required_mo:
+ if field not in mo:
+ warnings.append(f"market_overview 缺少字段: {field}")
+
+ # go_nogo 检查
+ if 'go_nogo' not in data:
+ errors.append("缺少 go_nogo 字段")
+ else:
+ gogono = data['go_nogo']
+ if 'overall_score' not in gogono and 'total_score' not in gogono:
+ warnings.append("go_nogo 缺少评分字段")
+ if 'decision' not in gogono and 'verdict' not in gogono:
+ warnings.append("go_nogo 缺少决策字段")
+
+ # dimensions 检查
+ if 'dimensions' in data and data['dimensions']:
+ # 检查每个维度是否有正确的结构
+ for i, dim in enumerate(data['dimensions']):
+ if 'dimension' not in dim and 'name' not in dim:
+ warnings.append(f"dimensions[{i}] 缺少 'dimension' 或 'name' 字段")
+
+ # voc_analysis 检查
+ if 'voc_analysis' in data and data['voc_analysis']:
+ voc = data['voc_analysis']
+ if 'dimensions' not in voc:
+ warnings.append("voc_analysis 缺少 'dimensions' 字段")
+
+ is_valid = len(errors) == 0
+ return is_valid, errors + warnings
+
+
+def fix_data(data: dict) -> dict:
+ """修复常见的数据结构问题"""
+ # 修复 go_nogo 字段名称
+ if 'go_nogo' in data:
+ gogono = data['go_nogo']
+ if 'verdict' in gogono and 'decision' not in gogono:
+ gogono['decision'] = gogono['verdict']
+ if 'total_score' in gogono and 'overall_score' not in gogono:
+ gogono['overall_score'] = gogono['total_score']
+
+ # 确保必需字段存在
+ if 'market_overview' not in data:
+ data['market_overview'] = {}
+
+ mo = data['market_overview']
+ if 'top3_product_concentration' not in mo and 'top3_concentration' in mo:
+ mo['top3_product_concentration'] = mo['top3_concentration']
+
+ return data
+
+
+def main():
+ parser = argparse.ArgumentParser(description="数据验证和修复脚本")
+ parser.add_argument("data_file", help="data.json 文件路径")
+ parser.add_argument("--fix", action="store_true", help="自动修复问题")
+ parser.add_argument("--output", "-o", help="输出文件路径(默认覆盖原文件)")
+
+ args = parser.parse_args()
+
+ data_path = Path(args.data_file)
+ if not data_path.exists():
+ print(f"✗ 文件不存在: {data_path}")
+ return 1
+
+ # 读取数据
+ print(f"读取数据: {data_path}")
+ with open(data_path, 'r', encoding='utf-8') as f:
+ data = json.load(f)
+
+ # 验证数据
+ is_valid, messages = validate_data(data)
+
+ print("\n验证结果:")
+ for msg in messages:
+ prefix = "✗" if "错误" in msg or "缺少" in msg else "⚠"
+ print(f" {prefix} {msg}")
+
+ if is_valid:
+ print("\n✓ 数据结构验证通过")
+ else:
+ print("\n✗ 数据结构存在问题")
+ if not args.fix:
+ print(" 提示: 使用 --fix 参数尝试自动修复")
+ return 1
+
+ # 修复数据
+ if args.fix:
+ print("\n修复数据...")
+ data = fix_data(data)
+
+ # 重新验证
+ is_valid_after, messages_after = validate_data(data)
+ if is_valid_after:
+ print("✓ 数据修复成功")
+ else:
+ print("⚠ 部分问题无法自动修复")
+
+ # 保存
+ output_path = Path(args.output) if args.output else data_path
+ with open(output_path, 'w', encoding='utf-8') as f:
+ json.dump(data, f, ensure_ascii=False, indent=2)
+
+ print(f"✓ 已保存: {output_path}")
+
+ return 0 if is_valid else 1
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/skills/amazon-sorftime-research-market-skill/scripts/get_reviews.py b/skills/amazon-sorftime-research-market-skill/scripts/get_reviews.py
new file mode 100644
index 00000000..5fe951bb
--- /dev/null
+++ b/skills/amazon-sorftime-research-market-skill/scripts/get_reviews.py
@@ -0,0 +1,129 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+获取竞品差评数据(通用版本)
+
+使用方法:
+ python get_reviews.py --output-dir "product-research/xxx_YYYYMMDD"
+
+注意:此脚本从 top100.json 中自动选择代表性竞品
+"""
+import json
+import os
+import sys
+from datetime import datetime
+
+# 添加脚本目录到路径
+script_dir = os.path.dirname(os.path.abspath(__file__))
+sys.path.insert(0, script_dir)
+
+from api_client import SorftimeClient
+
+def get_project_root():
+ """获取项目根目录"""
+ current_dir = os.path.dirname(os.path.abspath(__file__))
+ # 从 scripts/ 向上四级到达项目根目录
+ return os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(current_dir))))
+
+def main():
+ import argparse
+ parser = argparse.ArgumentParser(description="获取竞品差评数据(通用版本)")
+ parser.add_argument("--output-dir", "-o", required=True, help="输出目录(包含 top100.json 的目录)")
+ parser.add_argument("--site", default="US", help="站点代码")
+ parser.add_argument("--max-reviews", type=int, default=6, help="最大竞品数量")
+ args = parser.parse_args()
+
+ # 检查 top100.json 是否存在
+ top100_path = os.path.join(args.output_dir, 'raw', 'top100.json')
+ if not os.path.exists(top100_path):
+ print(f"✗ 错误:找不到 {top100_path}")
+ print(" 请确保输出目录中存在 raw/top100.json 文件")
+ return 1
+
+ client = SorftimeClient()
+
+ # 读取 Top100 数据
+ with open(top100_path, 'r', encoding='utf-8') as f:
+ data = json.load(f)
+
+ products = data.get('Top100产品', []) or data.get('Top100 产品', [])
+
+ if not products:
+ print("✗ 错误:top100.json 中没有产品数据")
+ return 1
+
+ print(f"📊 从 {len(products)} 个产品中选择代表性竞品...")
+
+ # 按销量排序
+ sorted_products = sorted(products, key=lambda x: float(x.get('月销量', 0)), reverse=True)
+
+ # 选择策略:Top3 + 不同价格带代表
+ competitors = []
+
+ # 量级标杆(Top3)
+ for i, p in enumerate(sorted_products[:3]):
+ competitors.append((p['ASIN'], f"Top{i+1} - {p.get('品牌', 'Unknown')}"))
+
+ # 按价格分组选择
+ price_groups = {
+ 'low': [p for p in sorted_products if float(p.get('价格', 0)) < 30],
+ 'mid': [p for p in sorted_products if 30 <= float(p.get('价格', 0)) < 60],
+ 'high': [p for p in sorted_products if float(p.get('价格', 0)) >= 60]
+ }
+
+ # 各价位代表
+ for price_name, price_list in [('低价', price_groups['low']), ('中价', price_groups['mid']), ('高价', price_groups['high'])]:
+ for p in price_list:
+ if p['ASIN'] not in [c[0] for c in competitors]:
+ competitors.append((p['ASIN'], f"{price_name}代表 - {p.get('品牌', 'Unknown')}"))
+ break
+
+ # 去重
+ seen = set()
+ competitors = [x for x in competitors if not (x[0] in seen or seen.add(x[0]))]
+
+ # 限制数量
+ competitors = competitors[:args.max_reviews]
+
+ print(f" 选择了 {len(competitors)} 个竞品进行差评分析")
+
+ all_reviews = {}
+ for asin, desc in competitors:
+ print(f" - {asin} ({desc})...", end=' ', flush=True)
+ try:
+ reviews, raw = client.get_product_reviews(args.site, asin, 'Negative')
+ if reviews:
+ if isinstance(reviews, list):
+ review_count = len(reviews)
+ sample = reviews[:20] if len(reviews) > 20 else reviews
+ else:
+ review_count = 'data'
+ sample = reviews
+
+ all_reviews[asin] = {
+ 'description': desc,
+ 'review_count': review_count,
+ 'reviews': sample
+ }
+ print(f"✓ {review_count}条")
+ else:
+ print("✗ 无数据")
+ except Exception as e:
+ print(f"✗ {str(e)[:40]}")
+
+ # 保存结果
+ if all_reviews:
+ reviews_path = os.path.join(args.output_dir, 'raw', 'competitor_reviews.json')
+ os.makedirs(os.path.dirname(reviews_path), exist_ok=True)
+
+ with open(reviews_path, 'w', encoding='utf-8') as f:
+ json.dump(all_reviews, f, ensure_ascii=False, indent=2)
+
+ print(f"\n✓ 差评数据已保存: {reviews_path}")
+ return 0
+ else:
+ print("\n✗ 未获取到任何差评数据")
+ return 1
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/skills/amazon-sorftime-research-market-skill/scripts/render_dashboard.py b/skills/amazon-sorftime-research-market-skill/scripts/render_dashboard.py
new file mode 100644
index 00000000..0feb2491
--- /dev/null
+++ b/skills/amazon-sorftime-research-market-skill/scripts/render_dashboard.py
@@ -0,0 +1,856 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Dashboard 渲染器 - 为 product-research 生成可视化看板
+
+修复版本 v3.1 - 适配实际 data.json 数据结构
+
+使用方式:
+ from scripts.render_dashboard import DashboardRenderer
+ renderer = DashboardRenderer()
+ renderer.render("path/to/data.json")
+
+输出:
+ dashboard.html - 可在浏览器中直接打开的交互式看板
+"""
+
+import os
+import json
+from datetime import datetime
+from pathlib import Path
+from typing import Dict, List, Any, Optional
+
+
+class DashboardRenderer:
+ """Dashboard 渲染器"""
+
+ @staticmethod
+ def validate_analysis_data(data: Dict[str, Any]) -> tuple[bool, List[str]]:
+ """
+ 验证分析数据完整性
+
+ 返回: (is_complete, missing_fields)
+ - is_complete: 数据是否完整
+ - missing_fields: 缺失的字段列表
+ """
+ missing = []
+
+ # 检查决策评估数据
+ decision = data.get('decision', data.get('go_nogo', {}))
+ if not decision.get('overall_score') and not decision.get('total_score'):
+ missing.append('decision.overall_score')
+
+ # 检查 VOC 分析数据
+ voc = data.get('voc_analysis', {})
+ if not voc.get('dimensions'):
+ missing.append('voc_analysis.dimensions')
+
+ # 检查壁垒评估数据
+ barriers = data.get('barriers', [])
+ if not barriers or (isinstance(barriers, list) and len(barriers) == 0):
+ missing.append('barriers')
+
+ # 检查交叉分析数据
+ cross = data.get('cross_analysis', {})
+ if not cross.get('price_type_matrix'):
+ missing.append('cross_analysis.price_type_matrix')
+
+ is_complete = len(missing) == 0
+ return is_complete, missing
+
+ # HTML 模板
+ HTML_TEMPLATE = """
+
+
+ '
+
+ # 价格区间表格
+ if price_ranges:
+ total = sum(p.get('count', 0) for p in price_ranges)
+ rows = ''
+ for pr in price_ranges:
+ range_label = pr.get('range', '')
+ count = pr.get('count', 0)
+ share = pr.get('share', 0) * 100
+
+ if share >= 30:
+ tag_class = 'tag-blue'
+ elif share >= 20:
+ tag_class = 'tag-green'
+ elif share >= 10:
+ tag_class = 'tag-yellow'
+ else:
+ tag_class = 'tag-gray'
+
+ rows += f'
| {range_label} | {count} | {share:.0f}% |
'
+
+ tables_html += f'''
+
+
价格区间分布
+
+
+
+ | 价格区间 |
+ 产品数 |
+ 占比 |
+
+
+
+ {rows}
+
+
+
+ '''
+
+ # 产品类型表格
+ if product_types:
+ total = sum(pt.get('count', 0) for pt in product_types)
+ rows = ''
+ for pt in product_types:
+ type_name = pt.get('type', '')
+ count = pt.get('count', 0)
+ share = pt.get('share', 0) * 100
+
+ if share >= 30:
+ tag_class = 'tag-blue'
+ elif share >= 20:
+ tag_class = 'tag-green'
+ elif share >= 10:
+ tag_class = 'tag-yellow'
+ else:
+ tag_class = 'tag-gray'
+
+ rows += f'
| {type_name} | {count} | {share:.0f}% |
'
+
+ tables_html += f'''
+
+
产品类型分布
+
+
+
+ | 产品类型 |
+ 产品数 |
+ 占比 |
+
+
+
+ {rows}
+
+
+
+ '''
+
+ tables_html += '
'
+ return tables_html
+
+ def _render_cross_analysis(self, data: Dict) -> str:
+ """渲染交叉分析部分 - 使用实际数据结构"""
+ cross_analysis = data.get('cross_analysis', {})
+ price_type_matrix = cross_analysis.get('price_type_matrix', [])
+
+ if not price_type_matrix:
+ return ''
+
+ # 从矩阵数据中提取列(产品类型)
+ product_types = set()
+ price_ranges = []
+
+ for row in price_type_matrix:
+ price_range = row.get('price_range', '')
+ if price_range not in price_ranges:
+ price_ranges.append(price_range)
+ # 获取除 price_range 外的所有键作为产品类型
+ for key in row.keys():
+ if key != 'price_range':
+ product_types.add(key)
+
+ product_types = sorted(list(product_types))
+
+ # 生成表头
+ header_cols = ''.join(f'