# 小红书搜索模块

## 快速开始

### Python API（推荐）

```python
from script.search import search_xiaohongshu
from script.detail import get_xiaohongshu_detail

# 搜索笔记
data = search_xiaohongshu("产品测试")

# 获取详情
for note in data['notes']:
    note_id = note['channel_content_id']
    detail = get_xiaohongshu_detail(note_id)
    print(f"{detail['title']}")
    print(f"{detail['body_text']}")  # 完整正文
```

### 命令行工具

```bash
# 搜索
python script/search/xiaohongshu_search.py --keyword "产品测试"

# 详情
python script/detail/xiaohongshu_detail.py --note-id "6915588b00000000040143b5"
```

---

## API 文档

### 1. 搜索接口

#### 函数签名

```python
data = search_xiaohongshu(
    keyword: str,           # 必填：搜索关键词
    content_type="不限",    # 可选：不限、视频、图文
    sort_type="综合",       # 可选：综合、最新、最多点赞、最多评论
    publish_time="不限",    # 可选：不限、一天内、一周内、半年内
    page=1,                # 可选：页码（自动翻页）
    force=False            # 可选：强制刷新
)
```

### 返回值

```python
{
  "search_params": {      # 搜索参数
    "keyword": "产品测试",
    "content_type": "视频",
    "sort_type": "最新",
    "publish_time": "一周内",
    "cursor": "",
    "page": 1,
    "timestamp": "20251113_133258"
  },
  "has_more": True,       # 是否有更多
  "next_cursor": "...",   # 下一页游标（内部使用）
  "notes": [...]          # 笔记列表
}
```

### 笔记字段

| 字段 | 类型 | 说明 |
|------|------|------|
| channel_content_id | string/null | 笔记ID |
| link | string/null | 笔记链接 |
| title | string/null | 标题 |
| desc | string/null | 摘要（搜索接口返回） |
| body_text | null | 完整正文（搜索接口不返回，需调用详情接口） |
| channel_account_name | string/null | 作者名称 |
| channel_account_id | string/null | 作者ID |
| like_count | number/null | 点赞数 |
| comment_count | number/null | 评论数 |
| collect_count | number/null | 收藏数 |
| shared_count | number/null | 分享数 |
| images | array | 图片URL列表（空数组表示无图片） |
| video | null | 视频链接（搜索接口不返回，需调用详情接口） |
| content_type | string/null | 内容类型（video/note） |

**注意**: 不存在的字段统一用 `null` 表示，而非空字符串或 0。

### 2. 详情接口

#### 函数签名

```python
detail = get_xiaohongshu_detail(
    note_id: str,          # 必填：笔记ID
    force=False            # 可选：强制刷新
)
```

#### 返回值

```python
{
  "channel_content_id": "68d62e4500000000130085fc",
  "link": "https://www.xiaohongshu.com/explore/...",
  "comment_count": null,
  "images": ["http://res.cybertogether.net/..."],
  "like_count": 14,
  "body_text": "完整正文内容...",
  "title": "笔记标题",
  "collect_count": 6,
  "channel_account_id": "664954500000000007006ac0",
  "channel_account_name": "作者名称",
  "content_type": "video",  # 根据 video 字段自动判断：有视频="video"，否则="normal"
  "video": "http://sns-video-hw.xhscdn.com/...",
  "publish_timestamp": 1758877418000,
  "publish_time": "2025-09-26 17:03:38"
}
```

#### 字段说明

| 字段 | 类型 | 说明 |
|------|------|------|
| channel_content_id | string/null | 笔记ID |
| link | string/null | 笔记链接 |
| title | string/null | 标题 |
| body_text | string/null | 完整正文内容 |
| channel_account_name | string/null | 作者名称 |
| channel_account_id | string/null | 作者ID |
| like_count | number/null | 点赞数 |
| comment_count | number/null | 评论数 |
| collect_count | number/null | 收藏数 |
| images | array | 图片URL列表（已去重） |
| video | string/null | 视频链接 |
| content_type | string | 内容类型（"video" 或 "normal"，根据 video 字段自动判断） |
| publish_timestamp | number/null | 发布时间戳（毫秒） |
| publish_time | string/null | 发布时间（格式：YYYY-MM-DD HH:MM:SS） |

**注意**:
- 不存在的字段统一用 `null` 表示
- 图片已自动按顺序去重
- `content_type` 自动判断：有 `video` 为 "video"，否则为 "normal"

#### 使用示例

```python
from script.detail import get_xiaohongshu_detail

# 获取笔记详情
detail = get_xiaohongshu_detail("68d62e4500000000130085fc")

print(f"标题: {detail['title']}")
print(f"正文: {detail['body_text']}")
print(f"视频: {detail['video']}")
print(f"类型: {detail['content_type']}")
print(f"发布时间: {detail['publish_time']}")
```

---

## 使用示例

### 1. 基本搜索

```python
from script.search import search_xiaohongshu

data = search_xiaohongshu("产品测试")

print(f"找到 {len(data['notes'])} 条笔记")
for note in data['notes']:
    print(f"- {note['title']} ({note['like_count']} 赞)")
```

### 2. 带参数搜索

```python
data = search_xiaohongshu(
    keyword="产品测试",
    content_type="视频",
    sort_type="最新",
    publish_time="一周内"
)
```

### 3. 翻页（自动处理）

```python
# 直接指定页码，自动处理 cursor
page1 = search_xiaohongshu("产品测试", page=1)
page2 = search_xiaohongshu("产品测试", page=2)
page3 = search_xiaohongshu("产品测试", page=3)
```

### 4. 强制刷新

```python
# 忽略缓存，重新请求 API
data = search_xiaohongshu("产品测试", force=True)
```

### 5. 批量搜索

```python
keywords = ["产品测试", "软件测试", "性能测试"]

for keyword in keywords:
    data = search_xiaohongshu(keyword)
    print(f"{keyword}: {len(data['notes'])} 条笔记")
```

### 6. 数据分析

```python
from script.search import search_xiaohongshu

def analyze_topic(keyword):
    """分析话题热度"""
    data = search_xiaohongshu(
        keyword=keyword,
        sort_type="最新",
        publish_time="一周内"
    )

    notes = data['notes']
    total_likes = sum(n['like_count'] for n in notes)
    avg_likes = total_likes / len(notes) if notes else 0

    print(f"关键词: {keyword}")
    print(f"笔记数: {len(notes)}")
    print(f"总点赞: {total_likes}")
    print(f"平均点赞: {avg_likes:.1f}")

analyze_topic("产品测试")
```

### 7. 搜索 + 详情（完整正文）

```python
from script.search import search_xiaohongshu
from script.detail import get_xiaohongshu_detail

# 搜索笔记
data = search_xiaohongshu("产品测试", publish_time="一周内")

# 获取前3条的完整详情
for note in data['notes'][:3]:
    note_id = note['channel_content_id']

    # 获取详情
    detail = get_xiaohongshu_detail(note_id)

    print(f"\n标题: {note['title']}")
    print(f"摘要: {note['desc'][:50]}...")
    print(f"完整正文: {detail['result']['body_text'][:100]}...")
    print(f"点赞: {note['like_count']}")
```

---

## 命令行使用

### 搜索接口

#### 基本搜索

```bash
python script/search/xiaohongshu_search.py --keyword "产品测试"
```

#### 带参数搜索

```bash
python script/search/xiaohongshu_search.py \
  --keyword "产品测试" \
  --content-type "视频" \
  --sort-type "最新" \
  --publish-time "一周内"
```

#### 强制刷新

```bash
python script/search/xiaohongshu_search.py --keyword "产品测试" --force
```

#### 禁用缓存

```bash
python script/search/xiaohongshu_search.py --keyword "产品测试" --no-cache
```

#### 完整参数

| 参数 | 默认值 | 说明 |
|------|--------|------|
| --keyword | 必填 | 搜索关键词 |
| --content-type | "不限" | 内容类型：不限、视频、图文 |
| --sort-type | "综合" | 排序：综合、最新、最多点赞、最多评论 |
| --publish-time | "不限" | 时间：不限、一天内、一周内、半年内 |
| --page | 1 | 页码 |
| --cursor | "" | 翻页游标 |
| --force | False | 强制刷新 |
| --no-cache | False | 禁用缓存 |
| --results-dir | data/search | 输出目录 |
| --timeout | 30 | 超时时间（秒） |
| --max-retries | 5 | 最大重试次数 |
| --retry-delay | 2 | 重试延迟（秒） |

### 详情接口

#### 基本使用

```bash
python script/detail/xiaohongshu_detail.py --note-id "6915588b00000000040143b5"
```

#### 强制刷新

```bash
python script/detail/xiaohongshu_detail.py --note-id "6915588b00000000040143b5" --force
```

#### 完整参数

| 参数 | 默认值 | 说明 |
|------|--------|------|
| --note-id | 必填 | 笔记ID |
| --force | False | 强制刷新 |
| --no-cache | False | 禁用缓存 |
| --results-dir | data/detail | 输出目录 |
| --timeout | 30 | 超时时间（秒） |
| --max-retries | 5 | 最大重试次数 |
| --retry-delay | 2 | 重试延迟（秒） |

---

## 核心特性

### 1. 自动缓存（默认开启）

相同的搜索参数会自动使用缓存：

```python
# 第一次：请求 API
data1 = search_xiaohongshu("产品测试")

# 第二次：使用缓存
data2 = search_xiaohongshu("产品测试")  # 瞬间返回

# 强制刷新
data3 = search_xiaohongshu("产品测试", force=True)
```

### 2. 自动重试（失败重试 5 次）

- 超时错误：自动重试
- 连接错误：自动重试
- 5xx 服务器错误：自动重试
- 4xx 客户端错误：不重试

指数退避策略：2秒 → 4秒 → 8秒 → 16秒 → 32秒

### 3. 自动保存（后台完成）

搜索结果自动保存到 `data/search/xiaohongshu_search/`

目录结构：
```
data/search/xiaohongshu_search/
└── {关键词}/
    ├── raw/                           # 原始数据
    │   └── {时间戳}_page{页码}_{参数}.json
    └── clean/                         # 清洗数据
        └── {时间戳}_page{页码}_{参数}.json
```

文件名示例：
- 默认参数：`20251113_133315_page1_不限_综合_不限.json`
- 自定义参数：`20251113_133258_page1_视频_最新_一周内.json`

### 4. 自动翻页（内部处理 cursor）

```python
# 无需手动管理 cursor
page1 = search_xiaohongshu("产品测试", page=1)
page2 = search_xiaohongshu("产品测试", page=2)  # 自动获取 page1 的 cursor
page3 = search_xiaohongshu("产品测试", page=3)  # 自动获取 page2 的 cursor
```

### 5. 关键词自动清理

特殊字符会自动处理，避免文件名冲突：

```python
# 自动清理特殊字符
search_xiaohongshu("测试/产品:问题?")
# → 文件夹名：测试_产品_问题_
```

---

## 数据格式

### Clean 数据（推荐使用）

```json
{
  "search_params": {
    "keyword": "产品测试",
    "content_type": "视频",
    "sort_type": "最新",
    "publish_time": "一周内",
    "cursor": "",
    "page": 1,
    "timestamp": "20251113_133258"
  },
  "has_more": true,
  "next_cursor": "2@2fl1kgnh0gdx2oarsbpxc@...",
  "notes": [
    {
      "channel_content_id": "6915588b00000000040143b5",
      "link": "https://www.xiaohongshu.com/explore/6915588b00000000040143b5",
      "title": "笔记标题",
      "desc": "笔记摘要...",
      "body_text": "",
      "channel_account_name": "作者名称",
      "channel_account_id": "5b1e2c0811be10762dee6859",
      "like_count": 2,
      "comment_count": 0,
      "collect_count": 1,
      "shared_count": 0,
      "images": ["https://..."],
      "video": "",
      "content_type": "video"
    }
  ]
}
```

### Raw 数据

完整的 API 响应，包含所有元数据和嵌套结构。

---

## 注意事项

### 关于 desc 和 body_text

- **desc**：搜索接口返回的摘要（已截断）
- **body_text**：完整正文（空，需调用详情接口 `get_xhs_detail_by_note_id` 获取）

### 关于 video

- 搜索接口不返回视频链接
- 需要调用详情接口获取

### 频率限制

- 建议每次搜索间隔 1-2 秒
- 避免短时间内大量请求

---

## 常见问题

### Q: 如何获取完整正文？

A: 搜索接口只返回摘要，完整正文需要调用详情接口：

```python
# 1. 先搜索获取笔记列表
data = search_xiaohongshu("产品测试")

# 2. 对感兴趣的笔记调用详情接口
note_id = data['notes'][0]['channel_content_id']
# 调用 get_xhs_detail_by_note_id(note_id) 获取完整正文
```

### Q: 缓存如何清理？

A:
- 方式1：手动删除 `data/search/xiaohongshu_search/{关键词}/` 目录
- 方式2：使用 `force=True` 参数强制刷新

### Q: 如何判断是否使用了缓存？

A: 看控制台输出：
- 使用缓存：`✓ 使用缓存数据: ...`
- 请求 API：`正在搜索关键词: ... (尝试 1/3)`

### Q: 翻页时 cursor 在哪里？

A: cursor 已自动处理，无需手动管理：

```python
# ✅ 推荐：直接指定页码
page2 = search_xiaohongshu("产品测试", page=2)

# ❌ 不需要：手动传 cursor
# page2 = search_xiaohongshu("产品测试", cursor="...")
```

---

## 技术细节

### 内部默认配置

- **超时时间**：30 秒
- **最大重试**：5 次
- **重试延迟**：2 秒（指数增长）
- **缓存开关**：默认开启
- **输出目录**：`data/search`

### 缓存机制

- 基于搜索参数生成缓存键（keyword + content_type + sort_type + publish_time + cursor）
- 相同参数返回最新的缓存文件
- 按文件修改时间排序

### 自动翻页原理

```python
# page=2 时自动执行：
# 1. 读取 page=1 的缓存
# 2. 提取 next_cursor
# 3. 使用 cursor 请求 page=2
```
