# 天猫店铺评价抓取 SOP

## 核心约束
- ⚠ 此SOP针对**商家后台评价管理页面**（千牛/天猫商家中心），禁止跳转商品详情页
- ⚠ 禁止点击"查看全部评价"链接，只在后台评价管理列表页操作
- ⚠ 登录过期/验证码/滑块 → 暂停，提醒用户手动处理，不绕过
- ⚠ **必须抓取每个评价对应的商品名称/商品ID**，否则后期无法分析哪个商品出问题（历史教训：1768条评价中1037条因JS选择器`a[title]`找不到导致商品名空白）

## 前置环境确认
1. 确认当前页面URL包含 `rate.tmall.com` / `review` / `comment` / `evaluation` / `千牛` / 评价管理等关键词
2. web_scan 观察页面结构：筛选区、评价列表区、分页区
3. 检查当前筛选条件（时间范围、评价等级、有无内容、有无图片等），记录并通过JS移除非必要筛选

### ⚠ 千牛（天猫商家中心）评价管理页面 DOM特殊结构（重要！）
千牛评价管理页 `myseller.taobao.com/home.htm/comment-manage/list` 的表格结构如下：
- 每行 `tr.next-table-row` 有 **6个td**，索引从0开始：
  - `td[0]` = **空**（复选框列，无文本内容）
  - `td[1]` = **情感等级**（"正面评价"/"情感识别中"/"负面评价"）
  - `td[2]` = **评价内容 + 评价时间**（混在一起，需拆分：内容为评价文本，时间从中提取）
  - `td[3]` = **商品标题 + 订单号**（混在一起，需拆分提取）
  - `td[4]` = **买家昵称**（通常是 `l**` 格式）
  - `td[5]` = **操作按钮**（"投诉评价"/"回复"等）
- ⚠ 传统SOP中 `a[title]` 选择器在千牛页面的 `td[3]` 中不可用（商品名无`a[title]`），直接取 `td[3].innerText` 后按"订单号："拆分
- ⚠ **必须先web_scan确认列数**，不同后台页面td列数可能不同（有6列或5列），验证后再编码

## 筛选优化（优先做）
1. 查找"每页显示条数"下拉框，优先设为最大（50/100条）
2. 移除"有内容评价"等过滤条件（如需抓全部评价）
3. 如有时间筛选，确认覆盖范围

## 评价列表识别
- 表结构：`.next-table-row` 或 `[class*="table-row"]` 或 `tr[class*="row"]`
- 每行包含：商品信息、买家昵称、评价等级、评价内容、评价时间、回复状态等
- 评价等级标签：`正面评价`(好评)、`中性评价`(中评)、`负面评价`(差评)
- ⚠ 后台新增`情感识别中`标签：需通过内容关键词修正
- 有图评价：图片图标或 `[class*="image"]` 或 `img` 元素
- 追评：包含`[追评]`标签或`.rate-content`出现两次

## 字段提取（JS）——商品信息抓取为核心
```
字段清单：
- 店铺名称
- 商品标题 → 优先td列索引定位（千牛用td[3]），后备多级选择器
- 商品链接/商品ID → 优先tr内a[href*="item"], 后备td[3]分析
- 订单号 → 优先从td[3]按"订单号："拆分，或 [class*="orderId"]

### ⚠ 商品标题选择器（关键！历史教训修正版）
**不要只用单个选择器**，后台页面不同分页的DOM结构可能不同（第1页常缺少a[title]）。
多级备选方案（JS实现）：
```
// 第1级：a[title]（标准情况）
var prodEl = tr.querySelector('a[title]');
// 第2级：商品链接a[href*="item.taobao.com"]
if (!prodEl) prodEl = tr.querySelector('a[href*="item.taobao.com"], a[href*="item.tmall.com"]');
// 第3级：含商品关键词的td文本
if (!prodEl) {
  var tds = tr.querySelectorAll('td');
  for (var ti = 0; ti < tds.length; ti++) {
    if (tds[ti].textContent.trim().includes('金尊') || tds[ti].querySelector('[class*="prod"]')) {
      prodEl = tds[ti]; break;
    }
  }
}
var productTitle = prodEl ? (prodEl.getAttribute('title') || prodEl.textContent.trim()) : '';
var productLink = prodEl && prodEl.href ? prodEl.href : tr.querySelector('a[href*="item"]')?.href || '';
```
**验证标准**：采集完成后，检查商品标题空值率 < 5%。若高于此值，检查页面DOM结构并调整选择器。

### ⚠ 千牛页面字段提取完整示例（6列索引法，优先于选择器）
对于 `myseller.taobao.com/home.htm/comment-manage/list` 这类千牛页面，直接用td索引提取更可靠：
```javascript
// 1. 先验证td列数
var tds = tr.querySelectorAll('td');
if (tds.length === 6) {
  // 千牛标准6列布局（td[0]为复选框，无文本）
  var emotion = tds[1].innerText.trim();  // "正面评价"/"情感识别中"/"负面评价"
  var rawContent = tds[2].innerText.trim();
  var rawProduct = tds[3].innerText.trim();
  var buyer = tds[4].innerText.trim();
  
  // 拆分评价内容和时间（内容中可能混有"初次评价: 2025-11-23 16:10"）
  var rateTime = '';
  var content = rawContent;
  var timeMatch = rawContent.match(/(初次评价|追评|追加评价)[：:]\s*(\d{4}[-/]\d{1,2}[-/]\d{1,2}\s*\d{1,2}[:：]\d{2})/);
  if (timeMatch) {
    rateTime = timeMatch[0];
    content = rawContent.replace(timeMatch[0], '').trim();
  }
  
  // 拆分商品名和订单号（如"金尊XXX订单号：4873076137889906916"）
  var productName = rawProduct;
  var orderId = '';
  var orderMatch = rawProduct.match(/订单号[：:]\s*(\d+)/);
  if (orderMatch) {
    orderId = orderMatch[1];
    productName = rawProduct.replace(orderMatch[0], '').trim();
  }
}
```
**注意**：不同后台页面的列数可能不同（5列或6列），必须先通过 `tr:first-child td` 验证列数和内容分布。
- 买家昵称 → [class*="user-name"] 或 [class*="nick"]
- 评价等级 → 文本匹配"正面评价/中性评价/负面评价"
- 评价内容 → [class*="rate-content"] 或 [class*="content"]
- 评价图片数 → 统计img元素或图片图标
- 追评内容 → 第二个.rate-content或[追评]后内容
- 商家回复 → [class*="reply"] 或 [class*="seller-reply"]
- 是否已回复 → 回复区域是否存在
- 评价时间 → [class*="time"] 或 日期正则
- SKU/规格 → [class*="sku"] 或 [class*="spec"]
- 当前页码
- 抓取时间 → new Date().toISOString()
```

## 翻页/滚动策略
### 分页模式（优先）
1. 找页码按钮/下一页按钮：`button`/`a`/`[class*="next"]`/`.next-pagination`
2. 记录当前页号，提取数据
3. 点击"下一页" 或 使用URL跳转翻页
4. 等待2秒确认新数据加载（比较行数或检测表格DOM变化）
5. 重复直到"下一页"禁用/不存在

### ⚠ 千牛页面翻页特例：URL参数跳转法（比点击更可靠）
千牛 `myseller.taobao.com/home.htm/comment-manage/list` 页面翻页通过URL参数 `current` 控制：
```javascript
// 翻到第N页（比点击"下一页"按钮更稳定）
window.location.href = 'https://myseller.taobao.com/home.htm/comment-manage/list/rateWait4PC?current=' + pageNum + '&pageSize=20';
// 等待页面重新加载（约2-3秒）
```
**优点**：
- 避免按钮被遮挡/不可点击问题
- 可直接跳到任意页（如第1页->第21页，无需逐页点击）
- 页码从1开始，`pageSize` 通常固定为20

**注意**：跳转后需要用 `setTimeout` 等待页面渲染（约2-3秒），然后重新获取tr行

### 滚动加载模式（备用）
1. 查找滚动容器：`div[class*="scroll"]`、`div[class*="list"]` 或 `overflow:auto/scroll` 的父容器
2. 禁用 window.scrollBy，使用容器元素的 `.scrollTop` 属性滚动
3. 优先选择包含"评价内容、买家昵称、商品信息、回复状态"的列表容器
4. 每次滚动到底部，等待2秒加载新数据
5. 用订单号或评价内容哈希去重

## 数据去重
- 使用订单号作为唯一键去重
- 无订单号时使用 昵称+时间+内容前20字 组合去重

## 数据清洗与修正（关键！）
### 1. 情感修正（评价等级）
后台标记为"情感识别中"的评价，通过内容关键词修正：
- 含`不好`/`难吃`/`差`/`硬`/`太甜`/`太油`/`过期`等负面词 → **差评**
- 含`一般`/`还行`/`普通`/`马马虎虎`等中性词 → **中评**
- 含`好吃`/`不错`/`好`/`喜欢`/`推荐`/`赞`等正面词 → **好评**
- 空内容 → 按大多数同店铺同商品倾向推断，或保留"情感识别中"

### 2. 商品名归一化
后台商品标题存在大量同商品不同命名的情况（如带/不带`优惠价`、不同规格后缀），需按关键词归类：
- 优先提取核心关键词（如"杏仁饼""鸡仔饼""蛋卷""曲奇"等）
- 合并为统一商品名（如"澳门金尊杏仁饼"、"澳门金尊蛋卷"等）

### 3. 空内容处理
空内容评价保留，统计时标注「默认评价」或「无内容」

## 数据分析（新增模块）
### 负面关键词分类统计
按7大类别对差评内容逐条识别：
1. **口感问题**（硬/难吃/太甜/太油/腻/不新鲜等）
2. **包装/物流问题**（碎了/破损/压烂/盒子变形等）
3. **保质期问题**（过期/临期/生产日期等）
4. **服务问题**（客服态度/退换/发货慢等）
5. **品控问题**（品质不一/不如以前等）
6. **卫生问题**（发霉/变坏/异物等）
7. **份量/价格问题**（不值/量少/太贵等）

### 低分商品归类
按归一化商品名统计：总评价数/负面数/负面率/主要问题类别
负面率>15%标红预警，按负面率排序输出前十

### 整改方案生成
基于问题类别分布，给出可执行的配方/包装/品控/服务优化建议

## Excel导出格式（openpyxl，扩展版）
### 工作表1: 评价明细（自动着色：负面红色/中性黄色/正面绿色，带筛选器）
字段：序号、店铺名称、商品标题、商品ID/链接、订单号、买家昵称、评价等级、评价内容、评价图片数、追评内容、商家回复、是否已回复、评价时间、SKU、页码、抓取时间、商品分类（归一化后）

### 工作表2: 商品评价统计（按负面率排序）
字段：商品标题（归一化后）、总评价数、好评数、中评数、差评数、好评率、有图评价数、追评数、未回复数、负面率、主要问题类别

### 工作表3: 差评/中评重点问题汇总
字段：序号、商品标题、评价内容、评价时间、买家昵称、是否回复、问题关键词提取、问题类别（口感/包装/保质期/服务/品控/卫生/价格）

### 工作表4: 优化整改方案
字段：问题类别、出现次数、占比、核心表现、根本原因、整改方案、优先级、预期效果

## 输出报告模板（扩展版）
1. 总共抓取 N 条评价
2. 好评 N 条（占比x%）
3. 中评 N 条（占比x%）
4. 差评 N 条（占比x%）
5. 问题类别前三名：类别名（次数）
6. 低分商品前三名：商品名（负面率x%）
7. 未回复评价 N 条
8. 有图评价 N 条
9. 追评 N 条