Skill: redfox-data/xhs-copywriter-redfox (version 1.0.0)

--- name: xhs-copywriter description: 小红书爆款笔记创作工具。日均爬取分析2000+平台实时爆款笔记，输入核心关键词即可精准匹配同赛道当下最热爆文样本，全维度拆解爆款结构、开头钩子套路、干货密度、互动引导技巧，自动生成符合平台调性、可直接发布的完整合规笔记文案。支持新人博主快速获取内容灵感，降低创作门槛；内容运营提升产出效率，完成内容KPI；账号冷启动阶段匹配赛道热点，提高内容曝光概率 ; 矩阵账号批量产出差异化合规内容。触发词："小红书笔记生成"、"小红书文案"、"爆文模板"、"小红书爆文"、"小红书热门"。 version: 1.0.0 --- # 小红书爆款文案创作 ## 简介 xhs-copywriter 是一款专为小红书内容创作者打造的一站式笔记生成工具，把「找对标 → 拆规律 → 写正文」串成一条完整流水线。 通过简单的一句话触发，你可以： - 📊 **自动抓取爆款样本**：按关键词从多维榜单中获取当下热门内容，数据真实、规律鲜活 - 🧠 **AI 深度拆解规律**：提炼标题结构、开头钩子、干货密度与互动话术等可复用爆款公式 - ✍️ **直出完整文案**：生成推荐标题 × 6 + 正文 + 标签 + 公式来源，可直接发布，无需二次加工 适用于个人博主、品牌运营、MCN 机构等有持续内容产出需求的用户。 --- ## 功能特性 ### 🎯 核心功能 - **📡 多维爆款数据采集**：覆盖新手友好爆款、当日点赞爆款、当日增长爆款、持续增长爆款四大榜单维度，样本更全面 - **🧩 爆款公式自动提炼**：从标题模式、开头结构、内容节奏、互动设计等多维度拆解规律，不暴露原始数据 - **✍️ 完整文案一键生成**：输出推荐标题（6个）、正文、标签列表、爆款公式说明，格式即发即用 - **🌐 热点资讯自动融入**：生成前联网补充最新行业动态与热点词汇，内容不过时 ### ✨ 特色亮点 - **🎨 风格自适应**：支持用户提供个人写作风格样本，生成内容更贴近本人语气 - **⚡ 无账号无 Key**：无需注册小红书账号或配置 API，安装依赖即可使用 - **📋 来源透明**：输出结果附带参考爆款笔记链接与数据来源说明，规律可溯源 --- ## 使用指南 ### 基础使用（3 步完成） #### 第 1 步：说出你的需求 直接用自然语言告诉我你想要什么，例如： > 「帮我写一篇关于夏日防晒的小红书笔记」 #### 第 2 步：（可选）提供风格参考 如果你有自己常用的写作风格，可以贴几段文字，文案会更贴近你的语气。没有也完全没关系，直接跳过即可。 #### 第 3 步：获取完整文案 工具自动完成数据抓取 → 规律拆解 → 内容生成，最终交付： - 📌 推荐标题（6 个，含数字型 / 痛点型 / 悬念型等多种风格） - 📝 完整正文（含开头钩子、正文模块、互动收尾） - 🏷️ 标签列表（10 个左右，覆盖核心词 + 长尾词） - 🔍 爆款公式来源（参考笔记链接 + 互动数据） ### 常用命令速查 | 命令示例 | 功能 | | ------------------------------------ | ---------------------------------- | | `帮我写一篇关于 [话题] 的小红书笔记` | 生成完整笔记文案 | | `帮我找 [话题] 的爆款笔记` | 查找并展示近期热门对标内容 | | `分析 [话题/领域] 的爆款规律` | 输出该领域标题、结构、话术规律总结 | | `生成 [话题] 方向的文案` | 同「写笔记」，支持模糊话题输入 | --- ## 使用场景 | 场景 | 角色 | 需求描述 | 使用方式 | | ------------------- | ------------------- | ---------------------------------------- | ------------------------------------------------ | | 新人博主找内容方向 | 0 粉新手创作者 | 不知道写什么内容，头部博主参考性太低 | 输入所在领域关键词，自动生成可模仿的爆款文案结构 | | 日常选题 + 快速产稿 | 个人博主 / 运营 | 每周需要持续输出，苦于选题和动笔慢 | 每次直接说话题，获取完整文案，节省构思和写作时间 | | 品牌内容跟热点 | 快消 / 美妆品牌运营 | 需要结合平台热点做符合用户喜好的推广内容 | 按产品卖点触发，生成融合近期热词的种草笔记 | | MCN 批量内容生产 | MCN 内容团队 | 多账号同时运营，需要高效产出差异化内容 | 多个话题连续触发，快速获取不同风格的文案素材 | | 爆款规律研究 | 内容策略研究者 | 想了解某领域什么样的内容容易出圈 | 发起「分析 XX 领域爆款规律」，获取结构化规律报告 | --- ## 注意事项 - **完整流程**：工具会自动走完「抓数据 → 拆规律 → 联网补热点 → 生成文案」全流程，无需手动干预 - **数据来源**：爆款数据来自实时榜单脚本抓取，非人工编造；内容规律基于真实数据提炼，非 AI 臆想 - **合规输出**：生成内容符合小红书社区规范，不含违规导流、虚假宣传等风险表述 - **原始数据不外露**：工具不会将原始数据表格直接甩给用户，只呈现提炼后的结论与可用文案

path: references/core_workflow.md
content: # 小红书爆款内容分析与文案生成

## 自我介绍

当用户询问"你的技能是什么"、"你能做什么"、"你的触发条件是什么"等问题时，请回答：

**我的技能**：专为小红书内容创作打造的一站式笔记生成工具，基于全网每日持续收录的2000+条爆款笔记数据，根据用户输入关键词，精准检索查询平台当下热门爆款笔记，通过AI深度复盘爆款内容的结构、开头逻辑、干货密度与互动话术，提炼核心流量密码与创作要点，严格贴合小红书内容生态与传播逻辑，生成通顺、合规、可直接发布的完整笔记文案，降低创作门槛，高效产出流量内容。

**触发条件**：当用户说"帮我写一篇关于XX的小红书笔记"、"生成XX相关的文案内容"等文案生成类需求时使用。

## 任务目标

1. **查询爆款**：根据用户关键词查询小红书热门笔记
2. **分析规律**：从爆款数据中提炼流量密码与创作要点
3. **生成文案**：基于爆款规律生成可发布的文案内容

## 触发条件

当用户询问以下场景时使用本 Skill：

- "帮我找 XXX 的爆款笔记"
- "分析 XXX 的爆款规律"
- "帮我写一篇 XXX 的文案"
- "生成 XXX 相关的笔记内容"

## 操作流程

### Step 1: 查询爆款笔记

调用脚本获取热门数据：

```bash
python scripts/fetch_xhs_trends.py \
  --keyword "<用户关键词>" \
  --max-items 10 \
  --output-format markdown
```

**参数说明**：

- `--keyword`：用户输入的搜索关键词
- `--max-items`：每类爆款数据展示数量，默认 10 条
- `--output-format`：固定使用 `markdown` 格式

**脚本输出**：

- 四类爆款数据：低粉高赞爆款、点赞靠前、爆发增长、持续增长
- 每条数据包含：标题、作者、互动数据（收藏/分享/评论/点赞）

**执行要求**：

- 执行脚本后，直接基于输出数据进行分析
- 禁止将原始爆款数据展示给用户
- 禁止仅发送文件路径或"已保存"等简短提示

### Step 2: 分析爆款规律

基于脚本输出的数据，提炼以下规律：

**标题规律**：

- 高互动标题的共同特征（数字使用、情绪词、人群标签）
- 标题长度与结构模式

**内容规律**：

- 热门笔记的主题方向与切入角度
- 内容结构与段落分布特点
- 互动引导方式

**标签策略**：

- 高频出现的标签类型
- 热门话题标签

**数据分析维度**：
| 指标 | 分析要点 |
|------|---------|
| 互动总量 | 高互动内容的核心特征 |
| 收藏率 | 内容实用价值高低 |
| 评论量 | 话题性与互动引导效果 |
| 点赞比 | 情绪共鸣程度 |

### Step 3: 引导用户上传个人风格参考

在生成文案前，**必须**主动询问用户是否提供个人风格参考：

> 想让我更懂你的风格？发几段平时的笔记、日记或随笔给我就行

**处理方式**：

- 若用户提供：分析其写作风格（用词习惯、语气特点、表达方式），融合到生成的文案中
- 若用户不提供：直接基于爆款规律生成文案

### Step 4: 生成文案

**必须严格按照爆款规律分析结果进行创作**，生成符合爆款规律的小红书笔记

**创作前的准备**

1. **回顾爆款规律分析结果**（必须执行）：
   - 提取的高频关键词和热词（必须融入正文）
   - 热门标题模式（必须严格遵循）
   - 爆款内容的结构特征（必须参考）
   - 热门内容风格（必须保持一致）

2. **搜索最新资讯/热点**（必须执行）：
   - 使用 `web_search` 工具搜索该领域的最新动态、热点事件、深度分析或行业趋势
   - 搜索关键词格式：`小红书 + <选题关键词> + 最新`
   - **必须获取至少1-2个最新热点或资讯作为创作素材**

**撰写小红书笔记内容**（必须遵循爆款规律）

1. **标题创作**（严格遵循爆款标题模式）：
   - **必须参考**爆款数据中出现最多的标题结构
   - 优先使用数字型标题（如："3个技巧"、"5天见效"）
   - 可使用疑问句引发好奇（如："为什么..."、"居然..."）
   - 可使用感叹句增强情绪（如："太绝了！"、"绝了！"）
   - 标题最多不超过 20 字（包含标点符号，超出必须精简）

2. **正文创作**（严格遵循爆款内容结构）：
   - **必须融入**爆款数据中出现的高频关键词
   - **必须参考**爆款内容的开头"钩子"手法（如：痛点共鸣、惊人数据、反差对比）
   - **必须保持**爆款内容的写作风格（如：口语化、亲切感、代入感）
   - **必须融入**最新搜索到的热点/资讯，增加时效性和话题度
   - **结构参考**：分点说明、步骤教程、对比展示等
   - **结尾引导**：添加引导互动的话术（如："你们觉得呢？"、"评论区告诉我"）
   - **排版**：良好分段，点缀少量 Emoji（每段 1-2 个），使用简短句子
   - **Tags**：结尾添加 5-10 个相关标签（**注意：Tags 仅用于笔记正文，不渲染到图片中**）

### Step 5: 输出前自检【必须执行】

在输出前,逐项检查输出格式的每一个字段是否完整:

- []**推荐标题**:是否包含3-6个推荐标题?
- []**正文内容**:是否包含完整可发布的正文?
- []**推荐标签**:是否包含5-10个相关标签?
- []**爆款公式来源-参考的爆款规律**:是否简述提炼的爆款公式?
- []**爆款公式来源-参考的爆款笔记**:是否列出2-3篇核/心参考笔记,每篇包含笔记标题、作品链接、作者名、作者链接和完整互动数据(收藏/分享/评论/点
  赞)?

如有任何字段遗漏或不完整,必须补齐后再输出。

## 输出格式

最终输出**纯文案内容**，包含：

```
### 推荐标题
1. [标题1]
2. [标题2]
3. [标题3]

### 正文内容
[完整可发布的正文]

### 推荐标签
#标签1 #标签2 #标签3 #标签4 #标签5

### 爆款公式来源
**参考的爆款规律**：[简述提炼的爆款公式，如"数字型标题 + 痛点开场 + 分点干货 + 互动收尾"]

**参考的爆款笔记**（列出 2-3 篇核心参考笔记）：
1. [笔记标题](作品链接) - [@作者名](作者链接) - 互动数据：收藏 X / 分享 X / 评论 X / 点赞 X
2. [笔记标题](作品链接) - [@作者名](作者链接) - 互动数据：收藏 X / 分享 X / 评论 X / 点赞 X
3. [笔记标题](作品链接) - [@作者名](作者链接) - 互动数据：收藏 X / 分享 X / 评论 X / 点赞 X
```

**输出格式要求（强制）**：

- 如果是在子 Agent 中执行，主 Agent 也必须保持此输出格式不变，禁止调整、精简或重新组织
- 必须严格按照上述结构输出，不得遗漏任何部分（推荐标题、正文内容、推荐标签、爆款公式来源）
- 爆款公式来源必须包含"参考的爆款规律"和"参考的爆款笔记"两部分

**禁止输出**：

- 卡片设计或图片
- 脚本执行命令
- 文件路径或下载链接
- 仅展示数据而不生成文案

## 注意事项

- 严格基于查询到的真实爆款数据进行分析
- 生成的文案必须遵循提炼出的爆款规律
- 禁止在文案中添加外部联系方式或违规信息
- 生成的文案必须符合小红书社区规范


path: references/xhs_trend_data_format.md
content: # 小红书热门数据格式说明

## 概览

本文档定义了小红书热门数据查询脚本 `fetch_xhs_trends.py` 的输入输出格式规范。

## 输入格式

### 脚本参数

```bash
python scripts/fetch_xhs_trends.py --keyword <关键词> [选项]
```

| 参数 | 必填 | 说明 | 默认值 |
|------|------|------|--------|
| `--keyword` | 是 | 搜索关键词（支持多个关键词，逗号分隔，最多5个，总长度不超过200字符） | - |
| `--start-date` | 否 | 开始日期，格式 yyyy-MM-dd | 最近30天 |
| `--max-items` | 否 | 每类内容最多展示数量 | 50 |
| `--output-format` | 否 | 输出格式：text、json 或 markdown | markdown |
| `--output-file` | 否 | 输出文件路径 | 关键词_爆款数据.md |
| `--debug` | 否 | 调试模式，打印原始API响应 | False |

## 输出格式

### 四类爆款内容

脚本返回**近30天**的小红书热门数据，包含以下四类爆款内容：

| 内容类型 |适用场景 |
|---------|---------|
| **新手友好爆款** |  适合模仿学习，发现低成本爆款 |
| **当日点赞爆款** |  了解当前最热门内容 |
| **爆发增长内容** | 发现快速增长的内容 |
| **持续增长内容** | 发现持续增长的内容 |

### 作品数据字段（完整）

每个作品包含以下字段：

#### 作品基本信息

| 字段名 | 类型 | 说明 |
|--------|------|------|
| `photoId` | string | 作品ID（唯一标识） |
| `title` | string | 作品标题 |
| `desc` | string | 作品描述/正文 |
| `publicTime` | string | 发布时间（格式：YYYY-MM-DD HH:MM:SS） |

#### 作者信息

| 字段名 | 类型 | 说明 |
|--------|------|------|
| `userId` | string | 作者ID |
| `userName` | string | 作者名称 |
| `userHeadUrl` | string | 作者头像URL |
| `fans` | int | 粉丝数 |


**作者主页链接拼接规则**：
```
https://www.xiaohongshu.com/user/profile/{userId}
```

#### 互动数据（非增量类）

| 字段名 | 类型 | 说明 |
|--------|------|------|
| `useLikeCount` | int | 点赞数 |
| `collectedCount` | int | 收藏数 |
| `useCommentCount` | int | 评论数 |
| `useShareCount` | int | 分享数 |
| `interactiveCount` | int | 互动总数 |

#### 互动数据（增量类）

增量类（单日增量、七日增量）的数据在 `anaAdd` 对象中：

| 字段名 | 类型 | 说明 |
|--------|------|------|
| `anaAdd.addLikeCount` | int | 新增点赞数 |
| `anaAdd.addCollectedCunt` | int | 新增收藏数（注意：API字段名有拼写错误） |
| `anaAdd.addCommentCount` | int | 新增评论数 |
| `anaAdd.addShareCount` | int | 新增分享数 |
| `anaAdd.addInteractiveount` | int | 新增互动总数（注意：API字段名有拼写错误） |
| `anaAdd.useLikeCount` | int | 总点赞数 |
| `anaAdd.collectedCount` | int | 总收藏数 |
| `anaAdd.useCommentCount` | int | 总评论数 |
| `anaAdd.useShareCount` | int | 总分享数 |
| `anaAdd.interactiveCount` | int | 总互动数 |
| `anaAdd.pred_readnum` | int | 预测阅读数 |

#### 图片链接

| 字段名 | 类型 | 说明 |
|--------|------|------|
| `coverUrl` | string | 封面图URL |
| `thumbnail` | string | 缩略图URL |

### JSON 输出示例

```json
{
  "keyword": "高考",
  "low_fan_explosive": [
    {
      "photoId": "69aa603c0000000015021b18",
      "title": "对于26高考女宝们的选大学建议",
      "desc": "一定要选人文关怀好以及男女平等的大学...",
      "publicTime": "2026-03-06 13:03:56",
      "userId": "68ebaae60000000037006191",
      "userName": "Alley",
      "userHeadUrl": "https://sns-avatar-qc.xhscdn.com/...",
      "fans": 2,
      "useLikeCount": 23439,
      "collectedCount": 15610,
      "useCommentCount": 846,
      "useShareCount": 697,
      "interactiveCount": 39895,
      "coverUrl": "http://sns-img-hw.xhscdn.com/...",
      "thumbnail": "http://sns-img-hw.xhscdn.com/..."
    }
  ],
  "daily_like_top": [...],
  "daily_increment": [
    {
      "photoId": "...",
      "title": "...",
      "anaAdd": {
        "addLikeCount": 12345,
        "addCollectedCunt": 2345,
        "addCommentCount": 567,
        "addShareCount": 89,
        "addInteractiveount": 15346,
        "useLikeCount": 50000,
        "collectedCount": 10000,
        "useCommentCount": 2000,
        "useShareCount": 500,
        "interactiveCount": 62500,
        "pred_readnum": 800000
      }
    }
  ],
  "weekly_increment": [...]
}
```

### 文本输出格式（表格形式）

文本输出采用 Markdown 表格格式，结构清晰，便于阅读：

```markdown
## 1、新手友好爆款
统计时间：近30天

| 序号 | 标题 | 作者 | 作品分类 | 收藏 | 分享 | 评论 | 点赞 | 互动总数 |
|------|------|------|----------|------|------|------|------|----------|
| 1 | 对于26高考女宝们的选大学建议 | [Alley](https://www.xiaohongshu.com/user/profile/68ebaae60000000037006191)（粉丝：2） | 教育 / 大学教育 | 15610 | 697 | 846 | 23439 | 39895 |
| 2 | 查出来个高考状元 | [泽陈泽陈](https://www.xiaohongshu.com/user/profile/684c0a9d000000001d00b1a1)（粉丝：4279） | 教育 / 中学教育 | 2860 | 221 | 44 | 23617 | 26521 |
...
| 10 | ... | ... | ... | ... | ... | ... | ... | ... |

## 2、当日点赞爆款
统计时间：近30天

| 序号 | 标题 | 作者 | 作品分类 | 收藏 | 分享 | 评论 | 点赞 |
|------|------|------|----------|------|------|------|------|
| 1 | 被1w人看过的地理书x厦门日光岩 | [深海里遇见你](https://www.xiaohongshu.com/user/profile/634a71b8000000001802d092)（粉丝：7404） | 教育 / 中学教育 | 57637 | 14733 | 2818 | 460237 |
...
| 10 | ... | ... | ... | ... | ... | ... | ... |

## 3、爆发增长内容
统计时间：近30天

| 序号 | 标题 | 作者 | 作品分类 | 新增收藏 | 新增分享 | 新增评论 | 新增点赞 | 互动增量 |
|------|------|------|----------|----------|----------|----------|----------|----------|
| 1 | 今年春晚预测的中高考考点❗ | [北大旺仔](https://www.xiaohongshu.com/user/profile/5de3e57c00000000010055f8)（粉丝：179984） | 教育 / 中学教育 | 14639 | 1485 | 102 | 19257 | 33998 |
...
| 10 | ... | ... | ... | ... | ... | ... | ... | ... |

## 4、持续增长内容
统计时间：近30天

| 序号 | 标题 | 作者 | 作品分类 | 新增收藏 | 新增分享 | 新增评论 | 新增点赞 | 互动增量 |
|------|------|------|----------|----------|----------|----------|----------|----------|
| 1 | 高考文言文词类活用-活用作状语 友好版！ | [我是摆子](https://www.xiaohongshu.com/user/profile/5d99de820000000001000015)（粉丝：52197） | 教育 / 中学教育 | 29937 | 1162 | 150 | 75196 | 105283 |
...
| 10 | ... | ... | ... | ... | ... | ... | ... | ... |
```

### 表格字段说明

#### 新手友好爆款
| 列名 | 说明 |
|------|------|
| 序号 | 排名|
| 标题 | 作品标题|
| 作者 | 作者名称（可点击跳转）+ 粉丝数 |
| 收藏 | 收藏数 |
| 分享 | 分享数 |
| 评论 | 评论数 |
| 点赞 | 点赞数 |
| 互动总数 | 互动总数 |

#### 当日点赞爆款
| 列名 | 说明 |
|------|------|
| 序号 | 排名|
| 标题 | 作品标题|
| 作者 | 作者名称（可点击跳转）+ 粉丝数 |
| 收藏 | 收藏数 |
| 分享 | 分享数 |
| 评论 | 评论数 |
| 点赞 | 点赞数 |

#### 增长内容
| 列名 | 说明 |
|------|------|
| 序号 | 排名 |
| 标题 | 作品标题 |
| 作者 | 作者名称（可点击跳转）+ 粉丝数 |
| 新增收藏 | 新增收藏数 |
| 新增分享 | 新增分享数 |
| 新增评论 | 新增评论数 |
| 新增点赞 | 新增点赞数 |
| 互动增量 | 互动增量总数 |

## 使用注意事项

### 数据获取原则

1. **必须调用脚本查询**：不能使用其他方式查询或直接搜索网络资讯
2. **必须等待脚本执行完成**：获取返回结果后才能进行后续步骤
3. **必须展示完整数据列表**：不能跳过或询问用户

### 数据展示原则

1. **展示所有字段**：每个作品展示完整信息，包括封面图、作者链接、分类、互动数据等
2. **四类爆款内容全部展示**：

### 字段说明

1. **增量内容的 anaAdd 字段**：仅爆发增长和持续增长内容包含此字段
2. **API字段拼写错误**：`addCollectedCunt` 和 `addInteractiveount` 是 API 的原始拼写，保持不变
3. **封面图和缩略图**：通常相同，但有时缩略图分辨率更低


path: scripts/fetch_xhs_trends.py
content: #!/usr/bin/env python3
"""
小红书热门数据查询脚本（最终版 - 禁用 SNI + 支持 chunked + gzip）
"""

import sys
import argparse
import json
import socket
import ssl
import gzip


def decode_chunked(data):
    """解码 chunked 传输编码"""
    chunks = []
    idx = 0

    while idx < len(data):
        # 读取 chunk 大小
        line_end = data.find(b'\r\n', idx)
        if line_end == -1:
            break

        chunk_size_line = data[idx:line_end]
        try:
            chunk_size = int(chunk_size_line, 16)
        except:
            break

        if chunk_size == 0:
            break

        # 读取 chunk 数据
        chunk_start = line_end + 2
        chunk_end = chunk_start + chunk_size

        if chunk_end > len(data):
            break

        chunk = data[chunk_start:chunk_end]
        chunks.append(chunk)

        # 移动到下一个 chunk
        idx = chunk_end + 2  # 跳过 \r\n

    return b''.join(chunks)


def fetch_via_no_sni(base_url: str, params: dict, headers: dict, timeout: int = 60):
    """
    使用原生 socket 实现 HTTPS 请求（不发送 SNI）

    关键：服务器在 SNI 扩展中检测到域名后主动断开连接
    解决：不发送 SNI 扩展
    """
    # 解析 URL
    if "://" in base_url:
        base_url = base_url.split("://", 1)[1]
    host, path = base_url.split("/", 1)

    # 构建 query string
    if params:
        from urllib.parse import quote
        query = "&".join(f"{quote(str(k))}={quote(str(v))}" for k, v in params.items())
        path = f"{path}?{query}"

    # 创建 TCP 连接
    sock = socket.create_connection((host, 443), timeout=timeout)

    # 创建 SSL 上下文（不使用 SNI）
    context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
    context.check_hostname = False
    context.verify_mode = ssl.CERT_NONE

    # 包装 socket（不传递 server_hostname，避免发送 SNI）
    ssl_sock = context.wrap_socket(sock)

    # 构建 HTTP 请求
    request_lines = [
        f"GET /{path} HTTP/1.1",
        f"Host: {host}",
    ]
    for k, v in headers.items():
        request_lines.append(f"{k}: {v}")
    request_lines.append("")
    request_lines.append("")

    request = "\r\n".join(request_lines)

    # 发送请求
    ssl_sock.send(request.encode())

    # 接收响应
    response_data = b""
    while True:
        try:
            chunk = ssl_sock.recv(8192)
            if not chunk:
                break
            response_data += chunk
        except:
            break

    ssl_sock.close()

    # 解析响应头
    response_str = response_data.decode('utf-8', errors='ignore')
    lines = response_str.split('\r\n')

    # 提取状态码
    status_code = int(lines[0].split()[1])

    # 提取响应头
    headers_dict = {}
    for i, line in enumerate(lines[1:]):
        if line == '':
            break
        if ':' in line:
            key, value = line.split(':', 1)
            headers_dict[key.strip().lower()] = value.strip()

    # 分离头部和正文
    header_end = response_data.find(b'\r\n\r\n')
    if header_end != -1:
        body_bytes = response_data[header_end + 4:]
    else:
        body_bytes = b""

    # 处理 chunked 编码
    if headers_dict.get('transfer-encoding', '').lower() == 'chunked':
        body_bytes = decode_chunked(body_bytes)

    # 处理 gzip 压缩
    if headers_dict.get('content-encoding', '').lower() == 'gzip':
        try:
            body_bytes = gzip.decompress(body_bytes)
        except:
            pass

    response_body = body_bytes.decode('utf-8', errors='ignore')

    return status_code, response_body


def fetch_xhs_trends(keyword: str, debug: bool = False, max_retries: int = 3, start_date: str = None):
    """
    调用新接口获取小红书热门数据

    Args:
        keyword: 搜索关键词（多个关键词用逗号分隔，最多5个，总长度不超过200）
        debug: 是否打印调试信息
        max_retries: 最大重试次数
        start_date: 开始日期，格式 yyyy-MM-dd，最长为最近30天

    Returns:
        dict: 包含4类爆款数据

    Raises:
        Exception: 当API调用失败时抛出异常
    """
    base_url = "https://onetotenvip.com/skill/cozeSkill/getXhsCozeSkillData"
    params = {
        "keyword": keyword,
        "source": "小红书笔记创作-ClawHub"
    }

    # 添加开始日期参数
    if start_date:
        params["startDate"] = start_date
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        "Accept": "application/json, text/plain, */*",
        "Accept-Language": "zh-CN,zh;q=0.9",
        "Accept-Encoding": "gzip, deflate, br",
        "Connection": "close",
    }

    last_error = None
    for attempt in range(max_retries):
        try:
            if debug:
                print(f"\n=== DEBUG: 第 {attempt + 1} 次尝试 ===", file=sys.stderr)

            status_code, body = fetch_via_no_sni(base_url, params, headers)

            if debug:
                print(f"状态码: {status_code}", file=sys.stderr)
                print(f"响应长度: {len(body)} 字节", file=sys.stderr)

            if status_code >= 400:
                raise Exception(f"HTTP请求失败: 状态码 {status_code}")

            data = json.loads(body)

            if "data" not in data:
                error_msg = data.get("msg", "未知错误")
                raise Exception(f"API 错误: {error_msg}")

            result_data = data.get("data", {})

            if debug:
                print("=== DEBUG: API 返回的 data 字段键 ===", file=sys.stderr)
                print(json.dumps(list(result_data.keys()), ensure_ascii=False, indent=2), file=sys.stderr)

            return {
                "keyword": keyword,
                "low_fan_explosive": result_data.get("lowPowderExplosiveArticle", []),
                "daily_like_top500": result_data.get("likeTheTop500", []),
                "daily_increment": result_data.get("singleDayIncrements", []),
                "weekly_increment": result_data.get("sevenDaysOfIncrements", [])
            }

        except Exception as e:
            last_error = str(e)
            if debug:
                print(f"  错误: {type(e).__name__}: {str(e)[:100]}", file=sys.stderr)
            import time
            if attempt < max_retries - 1:
                time.sleep(2 ** attempt)
            continue

    raise Exception(f"{last_error}（已尝试 {max_retries} 次）")


def format_output(data: dict, max_items: int = None):
    """
    格式化输出热门数据（表格形式）

    Args:
        data: 原始数据
        max_items: 每类爆款数据最多展示数量，None 表示展示所有数据
    """

    def process_title(item):
        """处理标题：转义特殊字符，空标题使用desc替代，并添加作品链接"""
        title = item.get('title', '')
        # 如果标题为空，尝试使用 desc 字段
        if not title or title.strip() == '':
            desc = item.get('desc', '')
            if desc:
                # 移除 desc 中的换行符并截取前30个字符
                title = desc.replace('\n', ' ').replace('\r', ' ').strip()[:30]
                if len(desc) > 30:
                    title = title + '...'

        if not title or title.strip() == '':
            title = '无标题'

        # 转义 Markdown 表格特殊字符（|）
        title = title.replace('|', '\\|')
        # 移除换行符
        title = title.replace('\n', ' ').replace('\r', ' ')
        # 移除多余空格
        title = ' '.join(title.split())

        # 截断过长标题
        if len(title) > 30:
            title = title[:30] + "..."

        # 添加作品链接
        photo_id = item.get('photoId', '')
        if photo_id:
            work_link = f"https://www.xiaohongshu.com/explore/{photo_id}"
            title = f"[{title}]({work_link})"

        return title

    output = []

    # 按 photoId 去重（API 返回数据可能有重复）
    def dedup_items(items):
        seen = set()
        result = []
        for item in items:
            photo_id = item.get('photoId', '')
            if photo_id and photo_id not in seen:
                seen.add(photo_id)
                result.append(item)
        return result

    # 检查是否有任何数据
    low_fan_items = dedup_items(data.get("low_fan_explosive", []))
    daily_like_items = dedup_items(data.get("daily_like_top500", []))
    daily_increment_items = dedup_items(data.get("daily_increment", []))
    weekly_increment_items = dedup_items(data.get("weekly_increment", []))

    total_count = len(low_fan_items) + len(daily_like_items) + len(daily_increment_items) + len(weekly_increment_items)

    # 如果所有类型都没有数据，输出友好提示
    if total_count == 0:
        keyword = data.get("keyword", "")
        output.append(f"# 小红书爆款数据分析报告\n\n**关键词**：{keyword}\n\n**爆款总数**：{total_count} 条\n\n")
        output.append("---\n\n")
        output.append("## 暂无相关爆款数据\n\n")
        output.append(f"很抱歉，当前关键词 **「{keyword}」** 尚未有足够的爆款笔记数据。\n\n")
        output.append("### 可能原因\n\n")
        output.append("- 该关键词相对小众或新兴，爆款内容积累较少\n")
        output.append("- 近期该赛道热度较低，暂无突出爆款笔记\n")
        output.append("- 关键词表述方式可以更加具体或热门\n\n")
        output.append("### 建议操作\n\n")
        output.append("- 更换为更热门的关键词，如：**\"早八穿搭\"**、**\"减脂餐\"**、**\"职场干货\"** 等\n")
        output.append("- 尝试更细分的长尾关键词，如：**\"小个子穿搭\"**、**\"学生党便当\"** 等\n")
        output.append("- 输入其他感兴趣的领域或赛道进行追踪\n\n")
        output.append("---\n\n")
        output.append("*数据来源：小红书爆款雷达，每日更新最新热门内容*\n")
        return "\n".join(output)

    # 1. 新手友好爆款
    items = low_fan_items
    if max_items is not None:
        items = items[:max_items]

    # 计算实际展示的总数
    display_low_fan = items
    display_daily_like = daily_like_items[:max_items] if max_items is not None else daily_like_items
    display_daily_increment = daily_increment_items[:max_items] if max_items is not None else daily_increment_items
    display_weekly_increment = weekly_increment_items[:max_items] if max_items is not None else weekly_increment_items
    display_total = len(display_low_fan) + len(display_daily_like) + len(display_daily_increment) + len(display_weekly_increment)

    # 输出标题和总数量
    keyword = data.get("keyword", "")
    output.append(f"# 小红书爆款数据分析报告\n\n**关键词**：{keyword}\n\n**爆款总数**：{display_total} 条\n\n---\n")

    output.append(f"\n## 爆款笔记（共 {len(items)} 条）")
    output.append(f"### - **新手友好爆款**（共 {len(items)} 条）")
    output.append(f"统计时间：近30天\n")

    if not items:
        output.append("(无数据)\n")
    else:
        output.append("| 序号 | 标题 | 作者 | **互动总数** | 收藏 | 分享 | 评论 | 点赞 |")
        output.append("|------|------|------|------------|------|------|------|------|")

        for idx, item in enumerate(items, 1):
            user_id = item.get('userId', '')
            user_name = item.get('userName', '未知')
            fans = item.get('fans', 0)

            if user_id:
                author_link = f"https://www.xiaohongshu.com/user/profile/{user_id}"
                author_str = f"[{user_name}]({author_link})（粉丝：{fans}）"
            else:
                author_str = f"{user_name}（粉丝：{fans}）"

            title = process_title(item)

            output.append(f"| {idx} | {title} | {author_str} | **{item.get('interactiveCount', 0)}** | {item.get('collectedCount', 0)} | {item.get('useShareCount', 0)} | {item.get('useCommentCount', 0)} | {item.get('useLikeCount', 0)} |")

    # 2. 当日点赞爆款
    items = data.get("daily_like_top500", [])
    if max_items is not None:
        items = items[:max_items]

    output.append(f"\n### - **当日点赞爆款**（共 {len(items)} 条）")
    output.append(f"统计时间：近30天\n")

    if not items:
        output.append("(无数据)\n")
    else:
        output.append("| 序号 | 标题 | 作者 | **点赞** | 收藏 | 分享 | 评论 |")
        output.append("|------|------|------|-------|------|------|------|")

        for idx, item in enumerate(items, 1):
            user_id = item.get('userId', '')
            user_name = item.get('userName', '未知')
            fans = item.get('fans', 0)

            if user_id:
                author_link = f"https://www.xiaohongshu.com/user/profile/{user_id}"
                author_str = f"[{user_name}]({author_link})（粉丝：{fans}）"
            else:
                author_str = f"{user_name}（粉丝：{fans}）"

            title = process_title(item)

            output.append(f"| {idx} | {title} | {author_str} | **{item.get('useLikeCount', 0)}** | {item.get('collectedCount', 0)} | {item.get('useShareCount', 0)} | {item.get('useCommentCount', 0)} |")

    # 3. 当日增长爆款
    items = data.get("daily_increment", [])
    if max_items is not None:
        items = items[:max_items]

    output.append(f"\n### - **当日增长爆款**（共 {len(items)} 条）")
    output.append(f"统计时间：近30天\n")

    if not items:
        output.append("(无数据)\n")
    else:
        output.append("| 序号 | 标题 | 作者 | 收藏 | 分享 | 评论 | 点赞 | **新增互动总量** |")
        output.append("|------|------|------|------|------|------|------|---------------|")

        for idx, item in enumerate(items, 1):
            user_id = item.get('userId', '')
            user_name = item.get('userName', '未知')
            fans = item.get('fans', 0)

            if user_id:
                author_link = f"https://www.xiaohongshu.com/user/profile/{user_id}"
                author_str = f"[{user_name}]({author_link})（粉丝：{fans}）"
            else:
                author_str = f"{user_name}（粉丝：{fans}）"

            title = process_title(item)

            # 从 anaAdd 对象获取新增互动数据
            ana_add = item.get('anaAdd', {})
            if ana_add:
                total = ana_add.get('addInteractiveount', 0)
                collected = ana_add.get('addCollectedCunt', 0)
                share = ana_add.get('addShareCount', 0)
                comment = ana_add.get('addCommentCount', 0)
                like = ana_add.get('addLikeCount', 0)
            else:
                total = 0
                collected = 0
                share = 0
                comment = 0
                like = 0

            output.append(f"| {idx} | {title} | {author_str} | {collected} | {share} | {comment} | {like} | **{total}** |")

    # 4. 持续增长爆款
    items = data.get("weekly_increment", [])
    if max_items is not None:
        items = items[:max_items]

    output.append(f"\n### - **持续增长爆款**（共 {len(items)} 条）")
    output.append(f"统计时间：近30天\n")

    if not items:
        output.append("(无数据)\n")
    else:
        output.append("| 序号 | 标题 | 作者 | 收藏 | 分享 | 评论 | 点赞 | **新增互动总量** |")
        output.append("|------|------|------|------|------|------|------|---------------|")

        for idx, item in enumerate(items, 1):
            user_id = item.get('userId', '')
            user_name = item.get('userName', '未知')
            fans = item.get('fans', 0)

            if user_id:
                author_link = f"https://www.xiaohongshu.com/user/profile/{user_id}"
                author_str = f"[{user_name}]({author_link})（粉丝：{fans}）"
            else:
                author_str = f"{user_name}（粉丝：{fans}）"

            title = process_title(item)

            # 从 anaAdd 对象获取新增互动数据
            ana_add = item.get('anaAdd', {})
            if ana_add:
                total = ana_add.get('addInteractiveount', 0)
                collected = ana_add.get('addCollectedCunt', 0)
                share = ana_add.get('addShareCount', 0)
                comment = ana_add.get('addCommentCount', 0)
                like = ana_add.get('addLikeCount', 0)
            else:
                total = 0
                collected = 0
                share = 0
                comment = 0
                like = 0

            output.append(f"| {idx} | {title} | {author_str} | {collected} | {share} | {comment} | {like} | **{total}** |")

    return "\n".join(output)


def main():
    """主函数"""
    parser = argparse.ArgumentParser(description='小红书热门数据查询工具')
    parser.add_argument('--keyword', required=True, help='搜索关键词')
    parser.add_argument('--max-items', type=int, default=10,
                       help='每类爆款内容最多展示数量（默认10条）')
    parser.add_argument('--output-format', choices=['text', 'json', 'markdown'],
                       default='markdown', help='输出格式：text（文本表格）、json（JSON格式）或 markdown（Markdown格式，默认）')
    parser.add_argument('--start-date', type=str, default=None,
                       help='开始日期，格式 yyyy-MM-dd（默认最近30天）')
    parser.add_argument('--debug', action='store_true', help='启用调试模式')
    parser.add_argument('--max-retries', type=int, default=3,
                       help='最大重试次数（默认3次）')

    args = parser.parse_args()

    try:
        data = fetch_xhs_trends(args.keyword, debug=args.debug, max_retries=args.max_retries, start_date=args.start_date)

        # 生成输出内容
        if args.output_format == 'json':
            output_content = json.dumps(data, ensure_ascii=False, indent=2)
        else:
            output_content = format_output(data, max_items=args.max_items)

        # 直接输出到控制台
        print(output_content)

    except Exception as e:
        print(f"❌ 错误: {str(e)}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: suspicious | static analysis: suspicious | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)