# 接口台账 · 小红书(xiao_hong_shu)

> 唯一证据来源:`/tmp/aiddit_captures/` 下真实抓包。host=`http://crawler.aiddit.com`。
> 整体状态:**部分可用** —— 搜索 `code=0` ✅、详情 `code=0` ✅、作者 blogger 请求超时 blocked。
> 平台特征:以**图文为主**(`image_list` / `image_url_list`),搜索结果以**视频笔记**为主(`note_card.type=video`)。
> **关键提取规则:搜索接口内容字段全部嵌在 `note_card` 嵌套下**(见下,这是核心抽取规则)。
> 鉴权:抓包请求体无显式 token,统一走 crawler.aiddit.com 网关。

---

## 1. 关键词搜索(PLT_XIAOHONGSHU_KEYWORD)

### ① 身份
- source_id:`PLT_XIAOHONGSHU_KEYWORD`
- 接口名:小红书关键词搜索
- 用途:按关键词检索笔记(搜索页 → 内容)
- 状态:**verified** ✅(`code=0` 有数据)。证据:`xhs_search.json`。

### ② 请求标准
- method:`POST`
- path:`/crawler/xiao_hong_shu/keyword`
- 鉴权:无显式 token(走网关)
- 参数表:

| 名 | 类型 | 必填 | 示例 |
|---|---|---|---|
| keyword | string | 是 | `"彩虹"` |
| content_type | string | 否 | `"视频"`(可选笔记类型过滤) |
| sort_type | string | 否 | `"综合"` |
| publish_time | string | 否 | `""` |
| cursor | string | 否 | `""`(首页传空) |

### ③ 请求示例(真实 request_body)
```bash
curl -X POST 'http://crawler.aiddit.com/crawler/xiao_hong_shu/keyword' \
  -H 'Content-Type: application/json' \
  -d '{"keyword":"彩虹","content_type":"视频","sort_type":"综合","publish_time":"","cursor":""}'
```

### ④ 响应信封 + 翻页
- 信封:`{code, msg, data}`,`code=0`,`msg=null`
- `data` = `{has_more, next_cursor, data:[...]}`
- 翻页真实取值:`has_more=false`,`next_cursor=null`(本次单页到底)

### ⑤ 字段台账(raw 名)——⚠️ 内容字段嵌在 note_card 下
> 列表项位于 `data.data[]`,每项结构:`item = {id, model_type, note_card}`。
> **真正的内容字段都在 `note_card` 嵌套里**,提取必须深入 `note_card`(及其子对象 `note_card.user`、`note_card.interact_info`)。

| 字段名(raw 路径) | 类型 | 含义 | 提取标注 | 真实示例值 |
|---|---|---|---|---|
| id | string | 笔记ID(内容ID) | 直接可用 | `696b90ce000000002102be77` |
| model_type | string | 模型类型 | 直接可用 | `note` |
| **note_card.type** | string | 笔记类型(图文/视频) | 直接可用(嵌套) | `video` |
| **note_card.display_title** | string/null | 笔记标题 | 直接可用(嵌套,常null) | `彩虹🌈` / `null` |
| **note_card.desc** | string | 正文/描述(含话题) | 直接可用 / tag需正则抽(嵌套) | `"夏日雨后彩虹草原 1. 核心主体…"` |
| **note_card.image_list[].image_url** | string | 图片地址(图文主体) | 直接可用(嵌套) | `https://ci.xiaohongshu.com/1040g00831remfdem…?imageView2/2/w/1080/format/webp` |
| **note_card.user.user_id** | string | 作者ID | 直接可用(嵌套) | `63ee8949000000000f0102f3` |
| **note_card.user.nick_name** | string | 作者昵称 | 直接可用(嵌套) | `一方盛景` |
| **note_card.user.nickname** | string | 作者昵称(同义冗余字段) | 直接可用(嵌套) | `一方盛景` |
| **note_card.user.avatar** | string | 作者头像 | 直接可用(嵌套) | `https://sns-avatar-qc.xhscdn.com/avatar/…?imageView2/2/w/80/format/jpg` |
| **note_card.interact_info.liked_count** | int | 点赞数 | 直接可用(嵌套) | `7` / `2363` |
| **note_card.interact_info.collected_count** | int | 收藏数 | 直接可用(嵌套) | `1` / `380` |
| **note_card.interact_info.comment_count** | int | 评论数 | 直接可用(嵌套) | `1` / `137` |
| **note_card.interact_info.shared_count** | int | 分享数 | 直接可用(嵌套) | `0` / `826` |
| **note_card.publish_timestamp** | long | 发布时间(秒) | 需后处理(嵌套,秒级) | `1768657102` |
| has_more | bool | 是否有下一页 | 直接可用 | `false` |
| next_cursor | string/null | 下一页游标 | 直接可用 | `null` |

> 注意:搜索结果**不直接给视频直链**(仅 `image_list` 封面图);取视频/全文需对 `id` 走详情接口(见 §2)二次请求。

### ⑥ 提取与后处理规则
- **note_card 嵌套抽取(核心规则)**:遍历 `data.data[]`,对每个 `item`:内容ID取 `item.id`,其余全部从 `item.note_card.*` 取——`note_card.type`、`note_card.desc`、`note_card.display_title`、`note_card.image_list`、`note_card.user.user_id`、`note_card.user.nick_name`、`note_card.interact_info.*`、`note_card.publish_timestamp`。不要在顶层找内容字段。
- **以图文为主**:小红书内容主体是 `image_list`(搜索)/`image_url_list`(详情);即使 `type=video`,搜索阶段也只给封面图,视频直链需走详情。
- **tag**:从 `note_card.desc` 用正则抽 `#xxx`(如 `#好运彩虹`、`#雨后彩虹🌈`)。
- 时间戳为**秒级**(`publish_timestamp=1768657102`),与视频号毫秒级不同,落库注意单位。

### ⑦ 边界与失败
- 搜索本次单次成功,未观察到抖动;限流 ≥15s。
- 视频直链不在搜索结果,需二次请求详情。

### ⑧ 落点
- canonical:`platform_content_id`(←id)、`title`(←note_card.display_title,缺则 desc 截断)、`body_text`(←note_card.desc)、`cover_url`(←note_card.image_list[0].image_url)、`platform_author_id`(←note_card.user.user_id)、`author_name`(←note_card.user.nick_name)、`tags`(从 desc 正则)、`like_count`/`collect_count`/`comment_count`/`share_count`(←note_card.interact_info.*)、`publish_time`。
- 游走边:`search_page_to_content`、`video_to_author`(note_card.user.user_id)、`video_to_hashtag`(desc 抽 tag);作者展开 `author_to_works` 依赖 blogger 接口(当前 blocked)。

---

## 2. 笔记详情(PLT_XIAOHONGSHU_DETAIL)

### ① 身份
- source_id:`PLT_XIAOHONGSHU_DETAIL`
- 接口名:小红书笔记详情
- 用途:按内容ID取全文/视频/精确互动数(内容补全 / 二次请求)
- 状态:**verified** ✅(`code=0` 有数据)。证据:`xhs_detail.json`。

### ② 请求标准
- method:`POST` · path:`/crawler/xiao_hong_shu/detail`

| 名 | 类型 | 必填 | 示例 |
|---|---|---|---|
| content_id | string | 是 | `696b90ce000000002102be77` |

### ③ 请求示例
```bash
curl -X POST 'http://crawler.aiddit.com/crawler/xiao_hong_shu/detail' \
  -H 'Content-Type: application/json' \
  -d '{"content_id":"696b90ce000000002102be77"}'
```

### ④ 响应信封 + 翻页
- `code=0`,`msg=null`,`data = {has_more, next_cursor, data:{...}}`(详情对象在 `data.data`,非嵌 note_card,字段扁平)
- `has_more=false`,`next_cursor=null`

### ⑤ 字段台账(raw 名)——详情为扁平结构(非 note_card)
| 字段名(data.data 下) | 类型 | 含义 | 提取标注 | 真实示例值 |
|---|---|---|---|---|
| channel_content_id | string | 内容ID | 直接可用 | `696b90ce000000002102be77` |
| content_link | string | 笔记链接 | 直接可用 | `https://www.xiaohongshu.com/explore/696b90ce000000002102be77` |
| title | string | 标题 | 直接可用(可空) | `""` |
| content_type | string | 类型 | 直接可用 | `video` |
| body_text | string | 全文正文(含话题串) | 直接可用 / tag需正则抽 | `"夏日雨后彩虹草原\n1. 核心主体：…#彩虹的约定[话题]#…"` |
| location | string | 归属地 | 直接可用 | `陕西` |
| topic_list | array | 话题列表 | 直接可用 | `["彩虹的约定"]` |
| image_url_list[].image_url | string | 图片地址 | 直接可用 | `https://ci.xiaohongshu.com/1040g00831remfdem…` |
| image_url_list[].image_type | int | 图片类型 | 直接可用 | `2` / `1` |
| video_url_list | array/null | 视频地址列表 | 需后处理下载(本样本null) | `null` |
| channel_account_id | string | 作者ID | 直接可用 | `63ee8949000000000f0102f3` |
| channel_account_name | string | 作者昵称 | 直接可用 | `一方盛景` |
| like_count | int | 点赞数 | 直接可用 | `7` |
| collect_count | int | 收藏数 | 直接可用 | `1` |
| comment_count | int | 评论数 | 直接可用 | `1` |
| share_count | int | 分享数 | 直接可用 | `0` |
| publish_timestamp | long | 发布时间(ms) | 需后处理(/1000) | `1768657102000` |
| is_original | bool | 是否原创 | 直接可用 | `false` |
| is_cache | bool | 是否缓存命中 | 直接可用 | `true` |

> 注意:详情接口的 `publish_timestamp` 为**毫秒**(`1768657102000`),与搜索接口的秒级(`1768657102`)不同——同一笔记两接口单位不一致,落库统一前必须按接口区分换算。

### ⑥ 提取与后处理规则
- 详情结构扁平,**不嵌 note_card**(与搜索不同),内容字段在 `data.data.*` 直接取。
- 视频地址在 `video_url_list`(本样本 `null`,图文笔记常无);取到则需后处理下载。
- 全文 `body_text` 比搜索 `desc` 完整,tag 从 `topic_list`(结构化)或 `body_text` 正则(`#xxx[话题]#`)抽取。

### ⑦ 边界与失败
- 本次成功;`is_cache=true` 表示命中缓存。限流 ≥15s。

### ⑧ 落点
- canonical:`platform_content_id`、`title`/`body_text`、`tags`(←topic_list)、`platform_author_id`(←channel_account_id)、`like_count`/`collect_count`/`comment_count`/`share_count`、`publish_time`、`location`。
- 游走边:`video_to_author`、`video_to_hashtag`。

---

## 3. 作者主页 blogger(PLT_XIAOHONGSHU_BLOGGER)

### ① 身份
- source_id:`PLT_XIAOHONGSHU_BLOGGER`
- 接口名:小红书博主作品列表
- 用途:作者 → 作品(author_to_works)
- 状态:**blocked(请求超时)**。证据:`xhs_blogger.json`(`error: timed out`)。

### ② 请求标准
- method:`POST` · path:`/crawler/xiao_hong_shu/blogger`

| 名 | 类型 | 必填 | 示例 |
|---|---|---|---|
| account_id | string | 是 | `63ee8949000000000f0102f3` |
| cursor | string | 否 | `""` |

### ③ 请求示例
```bash
curl -X POST 'http://crawler.aiddit.com/crawler/xiao_hong_shu/blogger' \
  -H 'Content-Type: application/json' \
  -d '{"account_id":"63ee8949000000000f0102f3","cursor":""}'
```

### ④ 响应信封
- 无响应,`error: timed out` → **失败(超时)**。

### ⑤ 字段台账
- 无数据(blocked)。

### ⑥/⑦ 边界与失败
- blocked:请求超时,未返回。建议加超时重试 + 退避;`account_id` 可由搜索结果 `note_card.user.user_id` 提供。

### ⑧ 落点
- 期望游走边 `author_to_works`,**当前超时 blocked**。
