Skill: yuanyi-github/gzh-copywriter (version 1.0.1)

--- name: gzh-copywriter description: 为公众号内容创作打造的文章生成工具。基于全网爆款文章数据，自动分析热门内容的标题规律、开头钩子、结构套路、语言风格与互动技巧，一键产出符合平台调性、可直接发布的公众号爆款文案。 dependency: python: - requests>=2.28.0 system: --- # 公众号爆款文案生成器 --- ## 简介 **一句话说明**：你告诉我想写什么主题，我帮你查全网爆款 → 拆解规律 → 直接写出可发布的公众号文章。 这是一个专为公众号内容创作者打造的一站式 AI 写作助手。它不是凭空"编"文章，而是先去拉取平台上真实跑出来的爆款数据，从中提炼出标题怎么起、开头怎么写、结构怎么排、互动怎么做，然后基于这些真实规律为你定制原创内容。 **能做什么？** - 🔍 **爆款侦查**：按你给的主题/关键词，搜索近 30 天公众号真实的爆款文章（低粉高阅读、阅读靠前、原创靠前三类） - 🧬 **规律解剖**：从爆款数据中拆解出 6 大维度——标题公式、开头钩子、段落结构、语言风格、互动引导、高频热词 - ✍️ **文案生成**：基于提炼出的规律，结合你的个人写作风格（可选），生成通顺、合规、可直接发布的完整公众号文章 - 🏷️ **配套交付**：一次性输出 3-6 个备选标题 + 完整正文 + 推荐标签 + 爆款规律来源溯源 **适合谁用？** - **公众号号主 / 运营** —— 需要持续产出高质量文章、找选题灵感 - **自媒体撰稿人** —— 想了解什么类型的内容容易爆、模仿爆款套路 - **MCN / 内容团队** —— 批量产出某一垂类的合规文案 - **公众号新手** —— 不知道怎么起标题、怎么写开头、怎么排版 **运行依赖**：Python `requests>=2.28.0`。 --- ## 功能特性 ### 核心流程：5 步出一篇爆款文 ``` 你输入主题 → 提炼关键词 → 查询爆款数据 → 分析规律 → 询问风格(可选) → 生成文案 ``` | 步骤 | 做了什么 | 产出 | | ------------------- | --------------------------------------------------------------- | ------------------ | | **Step 0** 意图识别 | 理解你想写的主题、受众、风格，提炼 2+ 个关键词 | 关键词列表 | | **Step 1** 爆款查询 | 调用脚本拉取三类爆款数据（低粉高阅读 / 阅读靠前 / 原创靠前） | 真实爆款文章数据 | | **Step 2** 规律分析 | 从数据中提炼 6 维度规律（标题、开头、结构、语言、互动、高频词） | 爆款规律报告 | | **Step 3** 风格融合 | 询问你是否提供个人样例，融合你的写作习惯（可选） | 个性化风格画像 | | **Step 4** 文案生成 | 基于规律 + 可选风格，输出完整可发布文案 | 标题 + 正文 + 标签 | ### 输出长什么样？ 最终你会收到这样一份完整的文案包： 1. **3-6 个推荐标题** —— 参考爆款标题规律生成，数字型/疑问句优先 2. **完整正文** —— 约 1500 字，含开头钩子、核心论述、结尾互动引导 3. **核心观点** —— 一句话概括全文主旨 4. **5-10 个推荐标签** —— 便于分发时使用 5. **爆款规律来源** —— 标注参考了哪些真实爆款文章（含标题、链接、作者、阅读数），透明可追溯 ### 特色亮点 - **数据驱动，不瞎编**：每篇文章的规律都来自真实爆款数据分析，不是 AI 凭空想象 - **风格可个性化**：可以发几段你平时的文字给我，我会学习并融入生成的文案中 - **6 维度全拆解**：不只给一篇范文，还告诉你为什么这么写——标题怎么起、开头怎么抓人、结尾怎么引互动 - **禁止凑数**：宁少毋滥，参考文章如实标注，绝不编造数据 --- ## 使用场景 当用户提出下列需求时，触发本 Skill： | 你可能会说 | Skill 帮你做什么 | | -------------------------------------------------- | ----------------------------------------------------- | | 「帮我写一篇关于职场跳槽的公众号文章」 | 查职场类爆款 → 拆解规律 → 生成一篇职场主题的完整文案 | | 「最近情感领域什么标题容易爆？帮我写一篇」 | 查情感爆款标题规律 → 基于规律产出带多个备选标题的文案 | | 「我是做亲子教育的号，帮我找选题并写一篇文章」 | 查亲子教育赛道爆款 → 分析热门方向 → 生成对应文案 | | 「分析一下最近理财类文章的爆款规律，帮我仿写一篇」 | 重点拆解理财赛道的 6 维度规律 → 仿照规律生成新文案 | | 「帮我把这段素材写成公众号风格的文案」 | 结合素材内容匹配相关赛道爆款规律 → 融合生成 | ### 典型示例 **场景一：从零开始写一篇** > 你：帮我写一篇关于「年轻人租房」的公众号文章。 > > 我： > > 1. 提炼关键词：租房、年轻人、独居、合租、房租 > 2. 查询这三类爆款数据 → 拿到近期热门租房相关文章 > 3. 分析规律：发现「数字型标题」「反差感开头」「故事化叙述」是主流 > 4. 询问你要不要提供个人风格样例 > 5. 输出 3-6 个标题 + 完整正文 + 标签 + 规律来源 **场景二：指定风格 + 主题** > 你：我是写深度财经的，帮我写一篇关于基金定投的文章，风格偏专业但好懂。 > > 我：识别到你的风格偏好 → 查询财经类爆款 → 侧重「专业通俗结合」的规律 → 生成符合深度财经调性的文案 --- ## 重要规则 ### 泛化词处理（必须遵守） 当你只说一个大类词（如「职场」「情感」「育儿」）时，我不会直接写，而是： 1. 先给你推荐 10 个细分方向词 2. 等你选择「拓展某个方向」或「就用这个大类写」 3. 收到回复后才正式开始查询和写作 **为什么？** 因为大类词范围太广，直接写容易泛泛而谈。细分后才能精准匹配爆款规律。 ### 数据边界 | 你的要求 | 我的处理 | | ------------------ | -------------------------------------- | | 要今天的数据 | 告知今天数据暂未更新，展示最近可用数据 | | 要超过 30 天的数据 | 告知仅支持近 30 天，展示最接近的数据 | | 某个冷门词没结果 | 建议换更热门的关键词，不会编造 | --- ## 项目架构 ### 文件结构 ``` gzh-copywriter/ ├── SKILL.md # 本技能文档（你正在看的） ├── scripts/ │ └── fetch_gzh_trends.py # 爆款数据拉取脚本（核心引擎） └── references/ ├── usage_and_trigger.md # 自我介绍话术 & 触发场景 ├── workflow.md # 5 步完整执行流程详解 ├── output_template.md # 输出格式模板 & 禁止项 └── gzh_trend_data_format.md # 脚本接口字段说明（输入/输出） ``` ### 数据流 ``` 你说主题 → 提炼关键词 → 脚本拉取爆款数据 → 6维分析 → (可选)融合你的风格 → 生成文案 ↓ 三类爆款：低粉高阅读 / 阅读靠前 / 原创靠前 ``` --- ## 常见问答 **Q1：生成的内容可以直接发布吗？** A：是的，输出的文案已按照公众号社区规范校验，包含完整标题、正文、标签，复制即可使用。建议发布前再通读一遍做微调。 **Q2：爆款数据从哪来的？** A：通过脚本 `fetch_gzh_trends.py` 从公众号平台获取近 30 天的真实热门文章数据，包含低粉高阅读、阅读量靠前、原创靠前三个维度的爆款。 **Q3：可以让它学我的写作风格吗？** A：当然！在 Step 3 我会主动询问你是否提供个人样例（平时的笔记、日记或随笔都行），提供的话我会提炼你的风格特征并融入生成的文案中。 **Q4：如果我不想让它问东问西，直接出稿呢？** A：没问题。你可以在一开始就说「直接写，不用问我」，我会跳过风格询问环节，基于爆款规律直接生成。或者你也可以把要求一次性说清楚：「写一篇 XXX，XXX 字，XXX 风格」。 **Q5：生成的文章大概多长？** A：默认约 1500 字（开头 200-300 字 + 核心 800-1000 字 + 结尾 200-300 字）。如果你有特定字数要求，随时告诉我。 ---

path: references/workflow.md
content: # 完整执行流程与创作规范

## Step 0: 意图识别与关键词提炼

在查询爆款之前，先理解用户真实需求并提炼关键词。

1. 理解用户意图：
   - 核心诉求（内容类型、主题、论点）
   - 目标受众
   - 创作目的
   - 创作风格
2. 提炼关键词（至少 2 个）：
   - 核心关键词
   - 场景关键词
   - 扩展关键词
3. 示例：
   - 职场跳槽 -> 职场、跳槽、辞职、加薪、职场发展
   - 情感文案 -> 情感、婚姻、夫妻、婆媳矛盾

强制要求：至少 2 个不同关键词，覆盖不同角度。

## Step 1: 查询爆款文章

调用脚本：

```bash
python scripts/fetch_gzh_trends.py \
  --keyword "<提炼出的关键词>" \
  --max-items 10 \
  --output-format markdown
```

参数说明：

- `--keyword`：Step 0 关键词，至少 2 个，逗号分隔
- `--max-items`：每类展示数量，默认 10
- `--output-format`：固定 `markdown`

执行要求：

- 执行后直接基于结果分析
- 禁止将原始爆款数据直接展示给用户
- 禁止只返回文件路径或“已保存”

## Step 2: 分析爆款规律

分析维度：

- 标题规律：结构、长度、词法特征
- 内容规律：切入角度、段落结构、互动方式
- 数据维度：阅读、在看、点赞、评论、分享

## Step 3: 询问个人风格参考

在生成文案前，主动询问用户是否提供个人风格样例：

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

处理方式：

- 用户提供：提炼写作风格并融合
- 用户不提供：按爆款规律直接生成

## Step 4: 生成文案

生成前准备：

1. 确定核心观点（必须执行）
   - 来自用户意图 + 爆款数据 + 可选时效信息
   - 全文围绕一个核心观点，避免发散
2. 回顾爆款规律（必须执行）
   - 高频关键词
   - 热门标题模式
   - 内容结构与风格
3. 最新资讯补充（可选）
   - 可用 `web_search` 仅做时效素材补充
   - 爆款规律和参考文章必须来自脚本数据

写作规范：

1. 标题创作
   - 参考高频标题结构
   - 优先数字型，可用疑问句/感叹句
   - 标题不超过 20 字
2. 正文创作
   - 融入高频关键词
   - 参考爆款开头钩子
   - 保持口语化、亲切、代入感
   - 结构清晰，结尾有互动引导
   - 添加 5-10 个标签

推荐篇幅（用户未指定时）：

- 总体约 1500 字
- 开头 200-300 字
- 核心论述 800-1000 字
- 结尾 200-300 字

## Step 5: 输出前自检

逐项确认：

- 推荐标题（3-6 个）
- 正文内容完整可发布
- 核心观点明确
- 推荐标签（5-10 个）
- 参考规律覆盖 6 维度（标题、开头、结构、语言、互动、高频词）
- 参考爆款文章 2-3 篇（含标题、文章链接、作者名、作者链接、阅读数）
- 已询问用户是否提供个人风格参考

若有遗漏，补齐后再输出。


path: references/usage_and_trigger.md
content: # 使用说明与触发规则

## 自我介绍（当用户询问技能能力时使用）

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

**我的技能**：专为公众号内容创作打造的一站式文章生成工具，基于平台持续收录的爆款文章数据，根据用户输入关键词检索热门内容，并结合结构、开头、互动等维度进行复盘分析，生成通顺、合规、可直接发布的公众号文案。

**触发条件**：当用户提出“帮我写一篇关于 XX 的公众号文章”“生成 XX 相关文案”“分析 XX 爆款规律”等文案生成类需求时使用。

## 触发场景

当用户出现以下意图时触发本 Skill：

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

## 任务目标

1. 查询爆款：根据用户关键词查询公众号热门文章
2. 分析规律：从爆款数据中提炼创作要点
3. 生成文案：基于规律输出可发布内容


path: references/gzh_trend_data_format.md
content: # 公众号热门数据格式说明

## 概览

本文档定义了公众号热门数据查询脚本 `fetch_gzh_trends.py` 的输入输出格式规范。

## 输入格式

### 脚本参数

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

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

## 输出格式

### 三类爆款内容

脚本返回**近30天**的公众号热门数据，包含以下三类爆款内容：

| 内容类型 | 适用场景 |
|---------|---------|
| **低粉高阅读爆款** | 适合模仿学习，发现低粉丝高阅读的优质内容 |
| **阅读靠前爆款** | 了解当前阅读量最高的热门内容 |
| **原创靠前爆款** | 发现高质量原创内容 |

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

每个作品包含以下字段：

#### 作品基本信息

| 字段名 | 类型 | 说明 |
|--------|------|------|
| `photoId` | string | 作品ID（唯一标识） |
| `title` | string | 作品标题 |
| `summary` | string | 作品摘要/描述 |
| `oriUrl` | string | 作品链接（公众号文章地址） |
| `publicTime` | string | 发布时间（格式：YYYY-MM-DD HH:MM:SS） |
| `type` | string | 文章类型（如：文摘、原创等） |
| `originalFlag` | int | 是否原创（1=原创，0=非原创） |
| `orderNum` | int | 文章位置（0=头条，1=次条） |

#### 作者信息

| 字段名 | 类型 | 说明 |
|--------|------|------|
| `accountId` | string | 公众号账号ID |
| `userName` | string | 公众号名称 |
| `userHeadUrl` | string | 作者头像URL（需拼接：https://open.weixin.qq.com/qr/code?username={accountId}） |
| `fans` | string | 粉丝数（如："100w+"、"48w+"） |

**作者主页链接拼接规则**：
```
https://open.weixin.qq.com/qr/code?username={accountId}
```

#### 互动数据

| 字段名 | 类型 | 说明 |
|--------|------|------|
| `clicksCount` | string | 阅读数（如："10w+"、"8w+"） |
| `likeCount` | int | 点赞数 |
| `commentCount` | int | 评论数 |
| `shareCount` | int | 分享数 |
| `watchCount` | int | 在看数 |
| `interactiveCount` | int | 互动总数 |

#### 图片链接

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

### JSON 输出示例

```json
{
  "keyword": "职场",
  "low_fan_explosive": [
    {
      "photoId": "ACF0E1227424ABAA91722B8CB9A61596",
      "title": "我找老板加薪。老板把全公司1万人的薪资表摊给我看，我排第10...",
      "summary": "哈马斯呼吁伊朗避免袭击邻国...",
      "oriUrl": "https://mp.weixin.qq.com/s?__biz=Mzg2Njc1MjkwMA==&mid=2247508072&idx=1&sn=46aadeb917574b98c38e1d70c068d1f5#rd",
      "publicTime": "2026-03-15 07:26:13",
      "type": "文摘",
      "originalFlag": 0,
      "orderNum": 0,
      "accountId": "RiixDesign",
      "userName": "大熊聊设计",
      "fans": "100w+",
      "clicksCount": "10w+",
      "likeCount": 234,
      "commentCount": 29,
      "shareCount": 381,
      "watchCount": 67,
      "interactiveCount": 711,
      "coverUrl": "https://mmbiz.qpic.cn/sz_mmbiz_jpg/...",
      "thumbnail": "https://mmbiz.qpic.cn/sz_mmbiz_jpg/..."
    }
  ],
  "read_top": [...],
  "original_top": [...]
}
```

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

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

```markdown
## 爆款文章（共 3 条）
### - **低粉高阅读爆款**（共 3 条）
统计时间：近30天

| 序号 | 标题 | 作者 | **阅读数** | 在看 | 点赞 | 评论 | 分享 |
|------|------|------|---------|------|------|------|------|
| 1 | [我找老板加薪。老板把全公司1万人的薪资表摊给我看，我排第10...](https://mp.weixin.qq.com/s?__biz=...) | [大熊聊设计](https://open.weixin.qq.com/qr/code?username=RiixDesign)（粉丝：100w+） | **10w+** | 67 | 234 | 29 | 381 |
| 2 | [张雪峰走后才懂：北京户口，真的值得拿命换吗？](https://mp.weixin.qq.com/s?__biz=...) | [记录生活边角](https://open.weixin.qq.com/qr/code?username=gh_620c50aa531c)（粉丝：100w+） | **8w+** | 112 | 236 | 182 | 1247 |
| 3 | ... | ... | ... | ... | ... | ... | ... |

### - **阅读靠前爆款**（共 3 条）
统计时间：近30天

| 序号 | 标题 | 作者 | **阅读数** | 在看 | 点赞 | 评论 | 分享 |
|------|------|------|---------|------|------|------|------|
| 1 | [上班的意义是什么](https://mp.weixin.qq.com/s?__biz=...) | [日常感集铺](https://open.weixin.qq.com/qr/code?username=itinghyy)（粉丝：100w+） | **10w+** | 86 | 254 | 109 | 1018 |
| 2 | ... | ... | ... | ... | ... | ... | ... |

### - **原创靠前爆款**（共 3 条）
统计时间：近30天

| 序号 | 标题 | 作者 | **阅读数** | 在看 | 点赞 | 评论 | 分享 |
|------|------|---------|------|------|------|------|------|
| 1 | [我一年抄底了8套老破小 \| 谷雨](https://mp.weixin.qq.com/s?__biz=...) | [谷雨实验室](https://open.weixin.qq.com/qr/code?username=guyulab)（粉丝：48w+） | **10w+** | 448 | 1453 | 44 | 1w+ |
| 2 | ... | ... | ... | ... | ... | ... | ... |
```

### 表格字段说明

| 列名 | 说明 |
|------|------|
| 序号 | 排名 |
| 标题 | 作品标题（可点击跳转到文章） |
| 作者 | 作者名称（可点击跳转到公众号二维码页面）+ 粉丝数 |
| 阅读数 | 阅读数（加粗显示） |
| 在看 | 在看数 |
| 点赞 | 点赞数 |
| 评论 | 评论数 |
| 分享 | 分享数 |

## 使用注意事项

### 数据获取原则

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

### 数据展示原则

1. **展示所有字段**：每个作品展示完整信息，包括标题链接、作者链接、互动数据等
2. **三类爆款内容全部展示**：低粉高阅读、阅读靠前、原创靠前

### 字段说明

1. **阅读数格式**：API 返回的阅读数可能是字符串（如："10w+"、"8w+"），不是纯数字
2. **粉丝数格式**：粉丝数也可能是字符串（如："100w+"、"48w+"）
3. **封面图和缩略图**：通常相同，但有时缩略图分辨率更低


path: references/output_template.md
content: # 输出模板

最终输出纯文案内容，使用以下结构：

```markdown
### 推荐标题
1. [标题1]
2. [标题2]
3. [标题3]
4. [标题4]（可选）
5. [标题5]（可选）
6. [标题6]（可选）

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

**核心观点**：[一句话概括本篇文章的核心观点，整篇文章围绕此观点展开]

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

### 爆款公式来源
**数据来源说明**：以下爆款规律和参考文章均来自脚本获取的真实爆款数据，非网络搜索结果。

**参考的爆款规律**：
1. **标题规律**：[数字型/疑问句占比、字数区间等]
2. **开头规律**：[痛点共鸣、反差开场、数据开场等]
3. **结构规律**：[总分总、三段式、对比论证等]
4. **语言风格**：[口语化、短句、金句、节奏]
5. **互动引导**：[结尾提问、引导评论、制造讨论]
6. **高频关键词**：[列出 3-5 个及出现频次]

**参考的爆款文章**（以下文章均来自脚本获取的爆款数据）：
1. [文章标题](作品链接) - [@作者名](作者链接) - 阅读数：X
2. [文章标题](作品链接) - [@作者名](作者链接) - 阅读数：X
3. [文章标题](作品链接) - [@作者名](作者链接) - 阅读数：X
```

## 禁止输出

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

## 注意事项

- 严格基于真实爆款数据分析
- 生成文案需遵循提炼出的规律
- 禁止输出外部联系方式或违规信息
- 文案需符合公众号社区规范


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

import sys
import argparse
import json
import requests


def fetch_via_no_sni(base_url: str, params: dict, headers: dict, timeout: int = 60):
    """
    使用 requests 发起 HTTPS 请求（保持证书与主机名校验）
    """
    response = requests.get(
        base_url,
        params=params,
        headers=headers,
        timeout=timeout,
    )
    return response.status_code, response.text


def fetch_gzh_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: 包含3类爆款数据

    Raises:
        Exception: 当API调用失败时抛出异常
    """
    base_url = "https://onetotenvip.com/skill/cozeSkill/getWxCozeSkillData"
    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", []),
                "read_top": result_data.get("tenWReadingRank", []),
                "original_top": result_data.get("originalRank", [])
            }

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

        # 添加作品链接（公众号使用 oriUrl）
        ori_url = item.get('oriUrl', '')
        if ori_url:
            title = f"[{title}]({ori_url})"

        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", []))
    read_top_items = dedup_items(data.get("read_top", []))
    original_top_items = dedup_items(data.get("original_top", []))

    total_count = len(low_fan_items) + len(read_top_items) + len(original_top_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_read_top = read_top_items[:max_items] if max_items is not None else read_top_items
    display_original_top = original_top_items[:max_items] if max_items is not None else original_top_items
    display_total = len(display_low_fan) + len(display_read_top) + len(display_original_top)

    # 输出标题和总数量
    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):
            account_id = item.get('accountId', '')
            user_name = item.get('userName', '未知')
            fans = item.get('fans', '0')

            # 公众号作者链接使用二维码页面
            if account_id:
                author_link = f"https://open.weixin.qq.com/qr/code?username={account_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('clicksCount', '0')}** | {item.get('watchCount', '0')} | {item.get('likeCount', '0')} | {item.get('commentCount', '0')} | {item.get('shareCount', '0')} |")

    # 2. 阅读靠前爆款
    items = read_top_items[:max_items] if max_items is not None else read_top_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):
            account_id = item.get('accountId', '')
            user_name = item.get('userName', '未知')
            fans = item.get('fans', '0')

            if account_id:
                author_link = f"https://open.weixin.qq.com/qr/code?username={account_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('clicksCount', '0')}** | {item.get('watchCount', '0')} | {item.get('likeCount', '0')} | {item.get('commentCount', '0')} | {item.get('shareCount', '0')} |")

    # 3. 原创靠前爆款
    items = original_top_items[:max_items] if max_items is not None else original_top_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):
            account_id = item.get('accountId', '')
            user_name = item.get('userName', '未知')
            fans = item.get('fans', '0')

            if account_id:
                author_link = f"https://open.weixin.qq.com/qr/code?username={account_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('clicksCount', '0')}** | {item.get('watchCount', '0')} | {item.get('likeCount', '0')} | {item.get('commentCount', '0')} | {item.get('shareCount', '0')} |")

    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_gzh_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: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)