Claude Skill

wechat-top-account

公众号热门账号推荐快速获取微信公众号综合实力排行榜TOP50,支持按日榜/周榜/月榜及垂直领域筛选,展示综合评分以及详细数据。支持深度分析单个账号、导出HTML可视化报告、订阅日/周/月榜推送。当用户需要查询公众号实力排行、按时间或领域筛选、查看互动指标、获取榜单解读、导出图表或接收定期推送时使用。

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download redfox-data-redfox-community-skills_wechat-top-account-5e7b435.zip · 39 KB
Part of redfox-data/redfox-community — 66 skills

Install

skills CLI npx skills add https://github.com/redfox-data/redfox-community/tree/main/skills/wechat-top-account
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install redfox-data-redfox-community@llmmart
Git git clone https://github.com/redfox-data/redfox-community.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole redfox-data/redfox-community collection as a plugin from our marketplace. Git is the plain clone.

README

公众号热门账号推荐 / wechat-top-account


简介

快速获取微信公众号综合实力排行榜 TOP50,帮助运营者和品牌方掌握同赛道头部账号的阅读与互动数据,支撑运营对标与竞品跟踪。

核心价值

  • 覆盖 23 个垂直分类的 TOP50 榜单,支持日榜、周榜、月榜自由切换
  • 综合评分体系基于阅读、点赞、在看、转发等多项指标,满分 100 分
  • 自动输出内容生态变化、热点影响、蓝海赛道三类深度解读
  • 一键生成可视化页面报告,支持导出图片和 PDF
  • 支持订阅日榜、周榜、月榜,按更新时间自动推送

适用对象

  • 📱 公众号运营者 — 了解赛道头部账号表现,优化选题与发布节奏
  • 🏢 MCN 机构运营 — 统一监控旗下账号竞争格局,生成定期数据汇报
  • 🏷️ 品牌营销经理 — 跟踪竞品公众号排名与互动变化,调整内容策略
  • 🚀 内容创业者 — 评估候选赛道竞争度与阅读需求,选择蓝海方向

功能特性

核心功能

  • 多维排行查询:按日榜、周榜、月榜查询 23 个分类的 TOP50,输入关键词自动匹配分类
  • 综合评分体系:基于发布数、阅读量、点赞、在看、转发等多维度综合评分,满分 100
  • 深度分析解读:提供内容生态变化、热点影响力量化、蓝海赛道挖掘三种分析视角
  • 可视化报告导出:一键生成榜单可视化页面,支持导出图片和 PDF
  • 定时订阅推送:支持订阅日榜、周榜、月榜,按更新时间自动推送最新数据

特色亮点

  • 定时更新:日榜每日 17:30、周榜每周一 17:30、月榜每月 3 日 23:00 自动刷新
  • 有限回溯:日榜近 7 天、周榜近 3 周、月榜近 3 个月,超出范围自动调整并提示
  • 数据一致:榜单表格与可视化报告共用同一数据来源,顺序与内容完全一致
  • 按需扩展:榜单直接输出无需确认,分析、报告、订阅由用户自主选择

密钥获取与安全说明

  • 本技能需要使用环境变量:REDFOX_API_KEY。
  • REDFOX_API_KEY 由 红狐 hub (https://redfox.hk?source=github)提供。
  • 请前往 红狐 hub 注册账号,获取 REDFOX_API_KEY。
  • 配置设备环境变量 REDFOX_API_KEY 后使用本技能。
  • 在提供密钥前,请先确认密钥来源、可用范围、有效期及是否支持重置/撤销。
  • 禁止在代码、提示词、日志或输出文件中硬编码/明文暴露密钥。

使用指南

直接用自然语言描述需求,无需记忆命令。

常用说法速查

意图 示例话术 效果
查询分类日榜 查询科技类公众号日榜 输出科技数码分类日榜 TOP50,含综合评分与互动指标
查询分类周榜 本周美食类公众号排名 输出美食餐饮周榜 TOP50,标注数据时间周期
查询分类月榜 财经类公众号月榜 输出财经类月榜 TOP50
获取深度分析 (榜单输出后回复)1 基于 TOP50 输出内容生态、热点影响、蓝海赛道三类分析
生成可视化报告 (榜单输出后回复)2 生成可导出图片和 PDF 的榜单页面
订阅推送 订阅周榜 设置每周定时推送最新榜单
全部订阅 全部订阅 同时订阅日榜、周榜、月榜

输出示例

查询「科技类公众号日榜」后,你将收到:

  1. 榜单表格:排名、账号名称(可点击跳转)、综合评分、发布数、总阅读、头条阅读、最高阅读、总点赞、总在看、总转发
  2. 扩展功能提示:回复 1 获取深度分析、回复 2 生成可视化报告、回复 3 或说「订阅日/周/月榜」开启定时推送

使用场景

场景 角色 示例问法 收益
日常运营参考 公众号运营者 查询科技类公众号日榜 对比发布频率与互动率,明确对标账号,优化选题节奏
竞品追踪 品牌营销经理 本周快消类公众号排名 跟踪竞品排名波动与内容发力方向,调整自身策略
赛道研判 内容创业者 对比美食和旅游类月榜 评估候选赛道竞争度,选择蓝海方向切入
定期数据汇报 MCN 机构运营 订阅月榜 按月导出可视化报告,数据口径统一可追溯

重要数据说明

榜单 更新时间 数据范围
日榜 每日 17:30 昨日数据,近 7 天
周榜 每周一 17:30 上周数据,近 3 周
月榜 每月 3 日 23:00 上月数据,近 3 个月

共覆盖 23 个垂直分类,包括总排名、科技数码、美食餐饮、财经、教育考试、时尚潮流、健康养生、情感心理、职场发展等。输入关键词即可自动匹配对应分类。

Skill manifest

公众号热门账号推荐

简介

公众号热门账号推荐是一款微信公众号综合实力排行查询工具,帮助用户快速获取同赛道头部账号的阅读与互动数据,支撑运营对标与竞品跟踪。

通过对话或命令行,你可以:

  • 📊 查询 23 个垂直分类的 TOP50 榜单,支持日榜、周榜、月榜切换
  • 📈 查看综合评分(满分 100)及发布数、阅读分布、点赞、在看、转发等指标
  • 🔍 获取内容生态、热点影响、蓝海赛道三类数据解读
  • 📄 导出 HTML 可视化报告(支持图片/PDF)
  • 🔔 订阅日/周/月榜,按更新时间自动推送

适用于公众号运营者、MCN 机构、品牌营销人员及需跟踪公众号竞争格局的内容团队。

功能特性

🎯 核心功能

  • 📊 多维排行查询:按日榜、周榜、月榜查询 23 个分类 TOP50,输入关键词自动匹配分类
  • 📈 综合评分体系:基于阅读、点赞、在看、转发等指标计算综合评分,满分 100
  • 🔍 深度分析:提供内容生态变化、热点影响力量化、蓝海赛道挖掘三种解读视角
  • 📄 HTML 报告:生成可视化榜单页面,支持导出图片和 PDF
  • 🔔 定时订阅:支持订阅日榜、周榜、月榜,按更新时间自动推送

✨ 特色亮点

  • ⚡ 定时更新:日榜每日 17:30、周榜每周一 17:30、月榜每月 3 日 23:00 自动刷新
  • 📅 有限回溯:日榜近 7 天、周榜近 3 周、月榜近 3 个月,超出范围自动调整并提示
  • 🔗 数据一致:榜单表格与 HTML 报告共用同一 API,顺序与内容完全一致
  • 🔒 API 接入:通过红狐Hub API Key 调用

鉴权

获取 API Key

  1. 访问 红狐Hub 官网 了解服务详情
  2. 前往 注册页面 注册账号
  3. 新注册用户将获赠免费积分,可立即开始使用 API 服务
  4. 注册登录后,在个人中心获取 API Key,格式为 ak_xxxxxxxx

配置 API Key

REDFOX_API_KEY 从环境变量获取,格式 ak_xxxxxxxx。脚本采用三级认证回退:

优先级 来源 说明
1 环境变量 REDFOX_API_KEY 直接读取当前会话的环境变量
2 Shell 配置文件 自动读取 ~/.zshrc / ~/.bashrc / ~/.bash_profile / PowerShell Profile 中的配置
3 用户输入 若以上均未检测到,提示用户手动配置

macOS / Linux:

echo 'export REDFOX_API_KEY=<你的apikey>' >> ~/.zshrc
source ~/.zshrc

Windows(PowerShell):

[Environment]::SetEnvironmentVariable("REDFOX_API_KEY", "<你的apikey>", "User")

或将以下内容写入 PowerShell Profile:

$env:REDFOX_API_KEY = "<你的apikey>"

配置后需重启终端。验证:echo $REDFOX_API_KEY(macOS/Linux)或 $env:REDFOX_API_KEY(Windows PowerShell)。

一键安装

前置条件

  • Python 3.8 及以上版本
  • requests 库:pip install requests
  • 红狐Hub API Key,格式 ak_xxxxxxxx

安装方式

SkillHub

  1. 前往 SkillHub
  2. 搜索 wechat-top-account 或 公众号热门账号推荐
  3. 安装说明
  • 可通过「通过对话安装」方式安装
  • 如未安装 SkillHub 商店,先安装商店再安装 skill
  • 支持下载 Zip 包到本地,通过本地导入方式安装

ClawHub

  1. 前往 ClawHub
  2. 搜索 wechat-top-account 或 公众号热门账号推荐
  3. 安装说明
  • 支持使用 CLI 命令安装:openclaw skills install wechat-top-account 或 clawhub install wechat-top-account
  • 支持下载 skill 包到本地并导入安装
  • 安装成功后可在对应客户端直接使用

安装完成后,请先完成 鉴权 配置 API Key,重启终端即可在对话中发起查询。

方式二:命令行独立使用

无需安装技能包,配置好 API Key 后可直接运行 scripts/ 下脚本:

pip install requests
python scripts/gzh_growth_fetcher.py --list_categories

环境变量配置

变量名 必填 说明
REDFOX_API_KEY 是 红狐Hub API 访问密钥,格式 ak_xxxxxxxx

详细获取与配置步骤见上方 鉴权 章节。

使用指南

基础使用

1. 发起榜单查询

在对话中输入查询需求,系统自动识别榜单类型和分类:

用户:查询科技类公众号日榜

助手:输出科技数码分类日榜 TOP50 表格(含综合评分、阅读与互动指标),并附功能询问文案。

2. 查看榜单结果

榜单数据直接输出,无需确认。每条结果包含排名、账号名称(可跳转)、综合评分、发布数/文章数、总阅读、头条阅读、最高阅读、总点赞、总在看、总转发。

用户:本周美食类公众号排名

助手:输出美食餐饮周榜 TOP50,标注数据时间周期与更新时间。

3. 选择扩展功能

榜单输出后,可按需选择后续服务:

用户:1

助手:基于 TOP50 数据输出内容生态变化、热点影响、蓝海赛道三类分析。

回复 1 获取分析、2 生成 HTML 报告、3 或「订阅日/周/月榜」开启定时推送。

高级使用

1. 指定分类与日期

通过 --category 或 --keyword 精确匹配分类,--rank_date 指定查询日期(超出范围自动调整):

# 首次查询时必须同时保存到临时文件(唯一一次API调用)
python scripts/gzh_growth_fetcher.py --rank_type week --category "美食餐饮" --top_n 50 > /tmp/gzh_result.json 2>/dev/null
python scripts/gzh_growth_fetcher.py --rank_type month --keyword "财经" --rank_date 2026-04-01 --top_n 50 > /tmp/gzh_result.json 2>/dev/null

2. 生成 HTML 可视化报告

用户选择 HTML 功能后,使用已保存的数据生成可导出图片/PDF 的页面(不调用API):

python scripts/gen_gzh_html.py --input_json_file /tmp/gzh_result.json --top 50

3. 获取深度分析解读

用户确认后,基于当前 TOP50 全量数据输出三段式分析:内容生态变化、热点影响力量化、蓝海赛道挖掘。

4. 订阅定时推送

用户回复「订阅日榜」「订阅周榜」「订阅月榜」或「全部订阅」,系统将按对应更新时间推送最新榜单。

完整参数、输出模板与日期校验逻辑见 核心工作流程。

常用命令速查

命令 功能
python scripts/gzh_growth_fetcher.py --rank_type day --keyword "科技" --top_n 50 > /tmp/gzh_result.json 2>/dev/null 科技类日榜 TOP50(首次查询,同时保存文件)
python scripts/gzh_growth_fetcher.py --rank_type week --category "美食餐饮" --top_n 50 > /tmp/gzh_result.json 2>/dev/null 美食餐饮周榜 TOP50(首次查询,同时保存文件)
python scripts/gzh_growth_fetcher.py --rank_type month --keyword "财经" --top_n 50 > /tmp/gzh_result.json 2>/dev/null 财经类月榜 TOP50(首次查询,同时保存文件)
python scripts/gzh_growth_fetcher.py --list_categories 列出全部可用分类
python scripts/gen_gzh_html.py --input_json_file /tmp/gzh_result.json --top 50 用预取数据生成 HTML(不调用API)
python scripts/gzh_growth_fetcher.py --input_json_file /tmp/gzh_result.json 用预取数据重新分析(不调用API)

使用场景

场景一:日常运营参考

角色:公众号运营者

需求:了解所属赛道头部账号的内容产出与互动表现

使用方式:

  1. 查询所属分类日榜或周榜
  2. 对比发布频率、单篇阅读与互动率
  3. 选择分析功能提取内容策略参考

预期收益:明确对标账号,优化选题与发布节奏


场景二:竞品追踪

角色:品牌营销经理

需求:跟踪竞品公众号排名与互动变化

使用方式:

  1. 按周查询竞品所在分类榜单
  2. 关注排名波动与阅读/转发变化
  3. 订阅周榜定时接收更新

预期收益:及时掌握竞品内容发力方向,调整自身策略


场景三:赛道研判

角色:内容创业者

需求:评估候选赛道的竞争度与阅读需求

使用方式:

  1. 查询多个候选分类的月榜数据
  2. 选择蓝海赛道分析功能
  3. 对比上榜账号数量与平均阅读/评分

预期收益:选择竞争度低、需求旺盛的方向切入


场景四:定期数据汇报

角色:MCN 机构运营人员

需求:为内部复盘或客户汇报提供可视化数据

使用方式:

  1. 订阅日榜和周榜保持数据更新
  2. 按月导出 HTML 报告(图片/PDF)
  3. 结合分析功能撰写复盘结论

预期收益:提升汇报效率,数据口径统一可追溯

项目架构

目录结构

wechat-top-account/
├── SKILL.md                      # 产品说明文档
├── references/
│   └── core_workflow.md          # 核心工作流程(模板、逻辑、步骤)
├── scripts/
│   ├── gzh_growth_fetcher.py     # 榜单数据抓取
│   └── gen_gzh_html.py           # HTML 可视化报告生成
└── assets/                       # 静态资源

技术栈

项目 说明
运行环境 Python 3.8+
核心依赖 requests
数据来源 红狐Hub API
部署方式 SkillHub / ClawHub / 命令行脚本

核心模块

模块 职责
gzh_growth_fetcher.py 通过 红狐Hub API 获取榜单数据,支持关键词匹配分类、日期校验与三级认证回退
gen_gzh_html.py 将榜单数据渲染为可导出图片和 PDF 的 HTML 页面
core_workflow.md 输出格式模板、Checklist、日期计算逻辑、完整操作步骤

数据更新规则

榜单 更新时间 数据范围
日榜 每日 17:30 昨日数据,近 7 天
周榜 每周一 17:30 上周数据,近 3 周
月榜 每月 3 号 23:00 上月数据,近 3 个月

常见问答

安装相关问题

Q1:未检测到 API Key 怎么办?

按 鉴权 章节步骤注册 红狐Hub 并配置 REDFOX_API_KEY 环境变量。新注册用户有免费积分。

Q2:如何验证配置已生效?

执行 echo $REDFOX_API_KEY,确认输出格式为 ak_xxxxxxxx。若未生效,检查终端是否已重启。

Q3:支持哪些部署方式?

支持 SkillHub、ClawHub 平台安装,也可直接运行 scripts/ 下 Python 脚本独立调用。


使用相关问题

Q4:支持哪些分类?

共 23 个分类:总排名、乐活生活、人文资讯、企业品牌、体育娱乐、健康养生、创投商业、学术研究、情感心理、房产楼市、搞笑幽默、教育考试、文摘精选、旅游出行、时尚潮流、民生资讯、汽车交通、知识百科、科技数码、美容美体、美食餐饮、职场发展、财富理财。

Q5:数据可回溯多久?

日榜近 7 天、周榜近 3 周、月榜近 3 个月。超出范围时系统自动调整至最近可用数据。

Q6:榜单与 HTML 报告数据是否一致?

两者共用同一 API 接口,数据完全一致。

Q7:数据多久更新一次?

日榜每日 17:30、周榜每周一 17:30、月榜每月 3 日 23:00 更新。


故障排除

Q8:查询返回空数据?

确认分类名称准确(可用 --list_categories 列出全部分类),检查查询日期在有效范围内。

Q9:HTML 生成失败?

确认已安装 requests 库且 API Key 有效,查看脚本错误输出定位原因。


安全与许可

Q10:API Key 如何保管?

REDFOX_API_KEY 仅通过环境变量读取,请勿写入代码仓库或公开分享。Key 泄露可能导致积分被消耗。

Q11:数据来源与使用范围?

榜单数据由 红狐Hub 平台提供,仅供运营分析、竞品跟踪等合法商业用途,请遵守平台服务条款。

核心工作流程

输出格式模板、数据校验规则、日期计算逻辑、操作步骤及使用示例详见 references/core_workflow.md。

执行任务时须严格遵循 core_workflow.md 中的:

  • 格式优先级声明与分阶段输出规则
  • 输出前必检 Checklist
  • 榜单标准模板与按需输出模板
  • 榜单类型判断与分类匹配规则
  • 日榜 / 周榜 / 月榜日期校验逻辑
  • 数据获取与格式化输出流程
  • API 调用次数上限(同一轮查询仅允许 1 次 API 调用)

注意事项

  • 热门账号推荐榜直接输出,无需确认;分析、HTML、订阅须用户选择后方可生成
  • 榜单表格与 HTML 报告须使用同一数据源,禁止修改接口返回顺序
  • 同一轮查询(榜单+分析+HTML)全过程只允许调用 1 次 API 接口,首次获取数据时必须同时保存到临时文件,后续操作一律使用 --input_json_file 读取,禁止重复调用 API
  • 未收到明确查询指令时,禁止自动调用脚本或输出榜单内容
  • 禁止折叠输出
  • 脚本输出的 formatted_markdown 字段已包含完整的标准模板格式,直接原样输出即可,禁止自行拼装或修改格式
Files (redfox-community)
  • references
    • core_workflow.md 18.2 KB
      # 公众号热门账号推荐 · 核心工作流程
      
      本文档与根目录 `SKILL.md` 配套使用:`SKILL.md` 提供产品说明、安装配置与使用场景;本文档涵盖输出格式、日期逻辑、完整脚本操作步骤与示例。执行任务时须完整遵循本文档规范。
      
      ## 格式优先级声明(最高优先级)
      
      本 Skill 的输出格式优先级高于智能体默认输出行为。无论智能体默认风格如何,须严格按照本文档规定的格式模板重组输出。
      
      分阶段输出规则:热门账号推荐榜直接输出;分析、HTML 页面、订阅服务需先输出功能询问文案,用户确认后再生成。
      
      ## 执行环境(强制)
      
      本 Skill 禁止在子任务或子 Agent 中执行,仅限主 Agent 任务。若检测到子任务环境,立即停止并返回:公众号热门账号推荐查询请在主对话中直接提问。
      
      ---
      
      ## API 调用次数上限(强制,违反即为严重错误)
      
      **同一轮查询(榜单 + 分析 + HTML)全过程只允许调用 1 次 API 接口,禁止重复调用。**
      
      执行规则:
      1. 首次获取数据时,必须同时将输出保存到临时文件(步骤A)
      2. 后续所有操作(提取 formatted_markdown、生成 HTML、深度分析)必须使用 `--input_json_file` 读取已保存的文件,禁止再次运行脚本调用 API
      3. 若因执行方式导致临时文件未成功保存,仅允许补存操作(用已输出的 JSON 手动写入文件),不得重新调用 API
      
      **错误示例**:查询榜单调用1次 → 保存文件又调用1次 → 提取 markdown 再调用1次 = 3次调用(严重违规)
      
      **正确示例**:查询榜单并保存文件调用1次 → 提取 markdown 从文件读取(0次)→ 生成 HTML 从文件读取(0次)= 总计1次调用
      
      ---
      
      ## 输出前必检Checklist(每次输出前逐项打勾,全部通过才能输出)
      
      ```
      [ ] 标题格式是否使用二级标题:## 公众号热门账号推荐榜{日榜/周榜/月榜} - {分类名} (更新时间:{时间})
      [ ] 数据说明是否在标题后、表格前(斜体)
      [ ] 表格列顺序是否严格:排名→账号名称→综合评分(满分100)→发布数/文章数→总阅读数→头条阅读数→最高阅读数→总点赞→总在看→总转发
      [ ] 排名1-3是否用了🥇🥈🥉,其余用数字
      [ ] 账号名称是否加粗+跳转链接(https://open.weixin.qq.com/qr/code?username={accountId})
      [ ] 指定字段是否都加粗(综合评分、总阅读、头条阅读、最高阅读、总点赞、总转发)
      [ ] 数字>=1万是否用x.x万格式,>=1亿用x.x亿格式
      [ ] 榜单表格是否输出了全部数据(TOP50)
      [ ] 榜单表格后是否输出了功能询问文案
      [ ] 分析/HTML/订阅是否在用户确认后才输出(禁止未经询问直接输出)
      [ ] 本轮查询是否仅调用了1次API(首次获取数据时已保存临时文件,后续操作均使用--input_json_file)
      ```
      
      ## 输出标准模板(严格套用,禁止自由发挥)
      
      **输出顺序:热门账号推荐榜(直接输出)→ 功能询问文案(直接输出)→ 用户选择后按需输出分析/HTML/订阅**
      
      ### 热门账号推荐榜(直接输出)
      
      ## 公众号热门账号推荐榜{日榜/周榜/月榜} - {分类名} (更新时间:{更新时间})
      
      *公众号综合实力{日榜/周榜/月榜},基于阅读、点赞、转发、在看等多维数据综合排名*
      *数据统计时间周期:{时间范围}*
      
      > 时间周期格式:日榜="2026-05-05";周榜="2026-05-04至2026-05-10";月榜="2026-04-01至2026-04-30"
      
      | 排名 | 账号名称 | 综合评分(满分100) | 发布数/文章数 | 总阅读数 | 头条阅读数 | 最高阅读数 | 总点赞 | 总在看 | 总转发 |
      |:---:|:---|---:|:---:|---:|---:|---:|---:|:---:|---:|
      | 🥇 | **[公众号名](https://open.weixin.qq.com/qr/code?username={accountId})** | **94.7** | 21/73 | **226.9万** | **151.4万** | **10.0万** | **1.5万** | **5729** | **9.3万** |
      | 🥈 | **[公众号名](https://open.weixin.qq.com/qr/code?username={accountId})** | **93.8** | 21/46 | **179.7万** | **153.2万** | **10.0万** | **1.9万** | **6064** | **5.9万** |
      | 🥉 | **[公众号名](https://open.weixin.qq.com/qr/code?username={accountId})** | **93.2** | 7/51 | **209.3万** | **25.5万** | **10.0万** | **2736** | **821** | **5295** |
      | 4 | [公众号名](https://open.weixin.qq.com/qr/code?username={accountId}) | 92.5 | 7/43 | 155.8万 | 65.7万 | 10.0万 | 1.3万 | 1941 | 2.0万 |
      | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
      | 50 | [公众号名](https://open.weixin.qq.com/qr/code?username={accountId}) | 80.2 | 3/15 | 28.6万 | 9.1万 | 5.2万 | 892 | 413 | 3210 |
      
      ### 功能询问文案(榜单表格后直接输出,一字不改)
      
      技能还支持以下功能:
      
      1️⃣ 支持对以上数据进行分析。其中包含**内容生态变化分析**、**热点影响力量化**、**蓝海赛道挖掘**三种分析角度,方便您了解公众号阅读数据或获取运营参考时使用。
      
      2️⃣ 可生成公众号风格的html文件包。支持**导出图片**、**导出PDF**,方便阅读、保存和分享。
      
      3️⃣ 支持订阅每日/周/月的公众号账号最新排名,订阅后将定时推送给您(日榜将在每日17点30更新/周榜将在每周一17:30准时更新/月榜将在每月3号23:00准时推送)。直接回复"订阅日/周/月榜"即可订阅相关榜单,或者"全部订阅"将会为您推送全部类型的榜单。
      
      回复数字即可~
      
      ---
      
      ## 按需输出模板(用户确认后使用)
      
      ### 热门账号推荐榜分析(用户选择1️⃣后输出)
      
      ## 公众号热门账号推荐榜{日榜/周榜/月榜}分析
      
      以下内容分析基于获取的全部数据(TOP50)进行~
      
      ## 内容生态变化分析
      
      - 平均发布数趋势:统计TOP50账号的平均发布数(发布天数/总天数),如近半年平均发布数从XX降至XX,说明用户从「追内容数量」转向「追内容质量」
      - 平均单篇阅读趋势:统计TOP50账号的平均单篇阅读(总阅读/发布篇数),如从XX升至XX,验证内容质量提升带来的阅读效率增长
      - 平均互动率趋势:统计TOP50的平均互动率(点赞+在看+转发/总阅读),如转发率从XX%持续降至XX%,说明公众号公域传播能力减弱,私域留存价值提升;如点赞率上升,说明读者深度阅读意愿增强
      
      ### 热点影响力量化
      
      - 特殊事件节点前后对比:如618大促期间消费测评类账号排名集体上升XX位、政策出台后财经解读类账号入榜数增加XX个
      - 热点对内容流量的拉动效果:量化特殊事件期间相关分类账号的平均阅读增幅(如+XX%)、综合评分提升幅度(如+XX分)
      - 行业流量周期规律:总结热点事件的流量拉动持续周期(如促销类约X天、政策类约X天),以及非热点期间的流量回落规律
      
      ### 蓝海赛道挖掘
      
      - 细分赛道竞争度统计:统计TOP50里各细分赛道的上榜账号数量,如老年教育、宠物医疗这类细分赛道上榜账号仅X个
      - 蓝海判断标准:上榜账号少(<=X个)但对应账号综合评分高(>=XX)、平均阅读高(>=XX万),说明需求旺盛但供给不足
      - 切入建议:列出具体蓝海赛道及其核心指标(上榜数/平均评分/平均阅读),给出内容定位和运营策略建议
      
      ### HTML页面(用户选择2️⃣后生成)
      
      由 `python scripts/gen_gzh_html.py` 生成,直接输出页面内容,禁止只输出文件路径。
      
      ### 订阅响应(用户选择3️⃣或回复订阅指令时)
      
      用户回复"订阅日榜" → 回复:"您已成功订阅日榜,将在每日17:30准时推送🌟~"
      用户回复"订阅周榜" → 回复:"您已成功订阅周榜,将在每周一17:30准时推送🌟~"
      用户回复"订阅月榜" → 回复:"您已成功订阅月榜,将在每月3号23:00准时推送🌟~"
      用户回复"全部订阅" → 回复:"您已成功订阅全部榜单,将在每日17:30推送日榜、每周一17:30推送周榜、每月3号23:00推送月榜🌟~"
      
      ---
      
      ## 数据接口更新规则
      - **日榜**:每日17:30更新昨日数据
      - **周榜**:每周一17:30更新上周数据
      - **月榜**:每月3号23:00更新上月数据
      - **数据范围**:日榜/周榜/月榜的公众号热门账号推荐榜排名TOP50
      - **分类覆盖**:23个垂直领域分类
      - **日期查询范围**:日榜近7天、周榜近3周、月榜近3个月
      
      ## 数据一致性要求
      - 热门账号推荐榜表格与HTML页面数据必须完全一致(相同顺序、相同内容)
      - 两个脚本使用相同的API接口,禁止修改接口返回的顺序
      
      ## 操作步骤
      
      ### 第一步:确定榜单类型
      根据用户输入判断榜单类型:
      - 用户提到"日榜"、"今日"、"当天"、"昨日"等 → 日榜(day)
      - 用户提到"周榜"、"本周"、"这周"、"上周"等 → 周榜(week)
      - 用户提到"月榜"、"本月"、"这月"、"上月"等 → 月榜(month)
      - 未指定 → 默认日榜(day)
      
      ### 第二步:分类匹配
      根据用户输入自动匹配合适的分类:
      
      **可用分类列表(共23个)**:
      总排名、乐活生活、人文资讯、企业品牌、体育娱乐、健康养生、创投商业、学术研究、情感心理、房产楼市、搞笑幽默、教育考试、文摘精选、旅游出行、时尚潮流、民生资讯、汽车交通、知识百科、科技数码、美容美体、美食餐饮、职场发展、财富理财
      
      **匹配规则**:
      - 用户输入具体产品词(如"手机")→ 匹配对应分类("科技数码")
      - 用户输入领域词(如"穿搭"、"美食")→ 直接匹配分类
      - 未指定分类 → 使用"人文资讯"
      
      **关键词-分类映射示例**:
      ```
      手机/电脑/AI → 科技数码
      穿搭/时尚/潮流 → 时尚潮流
      美食/做饭/探店 → 美食餐饮
      职场/求职/跳槽 → 职场发展
      股票/基金/理财 → 财富理财
      健身/运动/减肥 → 体育娱乐
      护肤/化妆/美妆 → 美容美体
      ```
      
      ### 第三步:日期判断与校验
      
      **日期查询范围限制**:
      - 日榜:支持查询最近7天数据
      - 周榜:支持查询最近3周数据
      - 月榜:支持查询最近3个月数据
      
      **日榜日期逻辑**:
      - 更新时间:每日17:30更新昨日数据
      - 自动计算:
        - 当前时间 >= 17:30 → 查询昨日数据
        - 当前时间 < 17:30 → 查询前天数据
      - 用户输入"今日"、"今天":
        - 若17:30前 → 展示提醒"日榜数据暂未更新,将为您查询最接近您需求日期的榜单数据⭐~"并推荐查询昨日更新的最新榜单
      - 用户输入"昨日"、"昨天" → 查询前天的具体日期
      - 用户输入具体日期 → 仅支持最近7天,超出7天展示提醒:"非常抱歉🙏,目前公众号榜单最多支持回溯「近7天的日榜/近3周的周榜/近3个月的月榜」,我将为您查询最接近您需求时间的{日榜/周榜/月榜}数据⭐~"并自动查询最近可用的数据
      
      **周榜日期逻辑**:
      - 更新时间:每周一17:30更新上周数据
      - 自动计算:
        - 当前时间 >= 周一17:30 → 查询本周一日期
        - 当前时间 < 周一17:30 → 查询上周一日期
      - 用户输入具体日期 → 仅支持最近3周,超出范围同上提醒
      
      **月榜日期逻辑**:
      - 更新时间:每月3号23:00更新上月数据
      - 自动计算:
        - 当前已过本月3号23:00 → 查询上月1号日期(上月数据已更新)
        - 当前未过本月3号23:00 → 查询上上月1号日期
      - 用户输入"当月"、"本月" → 根据更新时间判断,若未到更新时间则展示提醒
      - 用户输入"上月"、"上个月" → 查询相应月份
      - 用户输入具体月份 → 仅支持最近3个月,超出范围展示提醒:"非常抱歉🙏,目前公众号榜单最多支持回溯「近7天的日榜/近3周的周榜/近3个月的月榜」,我将为您查询最接近您需求时间的月榜数据⭐~"并自动查询最近可用数据
      
      ### 第四步:获取热门账号推荐榜数据
      调用脚本获取榜单数据(默认TOP50),**首次查询必须同时将输出保存到临时文件(这是唯一一次API调用)**:
      
      **查询命令**:
      ```bash
      # 首次查询:必须同时保存到临时文件(唯一一次API调用)
      python scripts/gzh_growth_fetcher.py --rank_type day --keyword "科技" --top_n 50 > /tmp/gzh_result.json 2>/dev/null
      python scripts/gzh_growth_fetcher.py --rank_type week --category "科技数码" --top_n 50 > /tmp/gzh_result.json 2>/dev/null
      python scripts/gzh_growth_fetcher.py --rank_type month --keyword "美食" --top_n 50 > /tmp/gzh_result.json 2>/dev/null
      ```
      
      **参数说明**:
      - `--rank_type`:榜单类型 day/week/month(默认day)
      - `--category`:分类名称(可选)
      - `--keyword`:用户输入的关键词,用于自动匹配分类
      - `--rank_date`:查询日期(可选,自动判断;超出范围自动调整)
      - `--top_n`:返回条数(默认50)
      - `--start`:起始排名(默认1)
      - `--list_categories`:列出所有可用分类
      - `--input_json_file`:预取数据JSON文件路径(指定后不再调用API,复用已获取的数据)
      
      **数据字段说明(API返回字段)**:
      - `accountName`:公众号名称
      - `accountId`:公众号ID(用于构建搜索链接)
      - `accountAvatar`:公众号头像URL
      - `rankPosition`:排名位置
      - `category`:所属分类
      - `publishCount`:发布数/文章数(如"3/9",表示9天内发布了3天)
      - `totalReadCount`:总阅读数
      - `headlineReadCount`:头条阅读数
      - `maxReadCount`:最高阅读数
      - `totalLikeCount`:总点赞数
      - `totalInSeeCount`:总在看数
      - `totalForwardCount`:总转发数
      - `averageReadCount`:平均阅读数(API返回,但展示表格中不显示)
      - `comprehensiveScore`:综合评分(满分100,接口返回)
      
      **日期校验输出**:
      - 脚本返回的 `reminder` 字段包含日期提醒信息,必须输出给用户
      - 超出查询范围时脚本自动调整日期,需展示提醒消息
      
      ### 第五步:按标准模板输出
      
      **重要:脚本输出的 `formatted_markdown` 字段已包含完整的标准模板格式(标题+数据说明+reminder+表格+功能询问文案),直接原样输出该字段即可,禁止自行拼装或修改格式。**
      
      1. 直接原样输出 `formatted_markdown` 字段内容(已包含榜单表格+功能询问文案)
      2. 等待用户回复,按需输出:
         - 用户选择分析 → 输出热门账号推荐榜分析(三段式)
         - 用户选择HTML → 调用脚本生成HTML页面并输出
         - 用户选择订阅 → 按订阅响应模板回复
         - 用户可多选,按用户提到的顺序依次输出
      
      **数据复用规则(强制)**:同一轮查询全过程只允许调用 1 次 API。当用户先查看榜单再请求HTML/分析时,必须使用 `--input_json_file` 传入已获取的数据,禁止再次调用API。步骤如下:
      
      ```bash
      # 步骤A:首次获取数据时,必须同时将输出保存到临时文件(唯一一次API调用)
      python scripts/gzh_growth_fetcher.py --rank_type day --keyword "科技" --top_n 50 > /tmp/gzh_result.json 2>/dev/null
      
      # 步骤B:用预取数据生成 HTML(不调用API)
      python scripts/gen_gzh_html.py --input_json_file /tmp/gzh_result.json --top 50
      
      # 步骤C:用预取数据重新分析(不调用API)
      python scripts/gzh_growth_fetcher.py --input_json_file /tmp/gzh_result.json
      ```
      
      **HTML生成命令(必须复用数据,禁止直接调用API)**:
      ```bash
      python scripts/gen_gzh_html.py --input_json_file {预取数据文件路径} --top 50
      ```
      
      **HTML文件命名**:`公众号热门账号推荐榜【日/周/月】_【分类名】_【8位时间戳】.html`
      
      **输出后自检**:对照"输出前必检Checklist"逐项确认。
      
      ## 使用示例
      
      ### 示例1:日榜关键词查询
      - **场景/输入**:"帮我查一下科技类的公众号排行"
      - **预期产出**:匹配科技数码分类,日榜,输出热门账号推荐榜表格 + 功能询问文案,等待用户选择
      
      ### 示例2:周榜查询 + 用户选择分析
      - **场景/输入**:"这周公众号热门账号推荐榜怎么样" → 用户回复"1"
      - **预期产出**:使用周榜(week),输出热门账号推荐榜 + 询问文案 → 用户选1后输出三段式分析
      
      ### 示例3:月榜 + 订阅
      - **场景/输入**:"本月美食类公众号排行" → 用户回复"订阅月榜"
      - **预期产出**:使用月榜(month),输出热门账号推荐榜 + 询问文案 → 用户订阅月榜后回复"月榜每月3号23:00更新,将在对应时间为您推送~"
      
      ## 资源索引
      - **脚本**:见 [scripts/gzh_growth_fetcher.py](../scripts/gzh_growth_fetcher.py)
        - 用途:获取公众号热门账号推荐榜单数据
        - 参数:--rank_type day/week/month / --category 分类名 / --keyword 关键词 / --rank_date 日期 / --top_n 条数 / --start 起始排名 / --input_json_file 预取数据文件路径
      - **脚本**:见 [scripts/gen_gzh_html.py](../scripts/gen_gzh_html.py)
        - 用途:生成可视化HTML页面
        - 参数:--rank_type day/week/month / --category 分类名 / --keyword 关键词 / --rank_date 日期 / --top 条数 / --output 输出路径 / --input_json_file 预取数据文件路径
      
      ## 注意事项
      - **格式优先级(最高)**:本Skill的格式要求 > 智能体默认输出,必须100%按标准模板输出
      - **执行环境(强制)**:本Skill只能在主Agent任务中执行,禁止在子任务/子Agent中执行
      - **热门账号推荐榜**:直接输出,无需询问
      - **分析/HTML/订阅**:必须先输出功能询问文案,用户确认后才可输出,禁止未经询问直接输出
      - **禁止折叠输出**
      - **HTML页面**:用户选择后生成,直接输出页面内容,禁止只输出文件路径
      - **数据一致性**:热门账号推荐榜表格与HTML页面必须使用相同的数据源,禁止修改接口返回的顺序
      - **数据复用(强制)**:同一轮查询(榜单+分析+HTML)全过程只允许调用 1 次 API 接口。首次获取数据时必须同时保存到临时文件,后续操作一律使用 `--input_json_file` 读取,禁止重复调用 API
      - **更新时间**:日榜/周榜每日17:30更新,月榜每月3号23:00更新
      - **日期查询范围**:日榜近7天、周榜近3周、月榜近3个月,超出范围自动调整并展示提醒
      - **禁止自动查询**:未订阅且未收到用户明确查询输入时,禁止自动调用脚本或输出任何热榜信息
      
  • scripts
    • gen_gzh_html.py 39.6 KB
      #!/usr/bin/env python3
      # -*- coding: utf-8 -*-
      """公众号热门账号推荐HTML生成器
      
      从API获取公众号热门账号推荐榜单数据,生成可独立打开的HTML页面。
      用法:python gen_gzh_html.py [--rank_type day|week|month] [--rank_date YYYY-MM-DD] [--category 分类名] [--top N] [--output PATH]
      输出:gzh_growth.html(与脚本同目录)
      
      样式特性:
      - 公众号风格(绿色主题 #07c160)
      - 卡片式布局
      - 公众号头像+名称+数据指标
      - TOP3 奖牌徽章 + 左边框高亮
      - 导出 PDF/图片功能
      - 页面最大宽度 750px
      """
      
      import json
      import sys
      import os
      import pathlib
      from datetime import datetime, timedelta
      from urllib.parse import quote
      import requests
      
      
      # ===== 常量 =====
      API_URL = "https://redfox.hk/story/api/cozeSkill/getGzhCozeSkillDataIndex"
      SOURCE = "公众号综合实力账号榜-GitHub"
      
      CATEGORIES = [
          "总排名", "乐活生活", "人文资讯", "企业品牌", "体育娱乐", "健康养生",
          "创投商业", "学术研究", "情感心理", "房产楼市", "搞笑幽默",
          "教育考试", "文摘精选", "旅游出行", "时尚潮流", "民生资讯",
          "汽车交通", "知识百科", "科技数码", "美容美体", "美食餐饮",
          "职场发展", "财富理财"
      ]
      
      CATEGORY_KEYWORDS = {
          "总排名": ["总排名", "综合", "全部", "热门", "推荐", "随便", "总榜", "整体"],
          "乐活生活": ["乐活", "生活", "日常", "生活方式", "生活日常", "好物推荐"],
          "人文资讯": ["人文", "资讯", "文化", "历史", "哲学", "人文社科"],
          "企业品牌": ["企业", "品牌", "公司", "商业品牌", "品牌营销"],
          "体育娱乐": ["体育", "娱乐", "运动", "健身", "篮球", "足球", "综艺", "明星"],
          "健康养生": ["健康", "养生", "保健", "中医", "调理", "减肥", "瘦身"],
          "创投商业": ["创投", "商业", "投资", "创业", "融资", "商业模式"],
          "学术研究": ["学术", "研究", "论文", "科研", "学报"],
          "情感心理": ["情感", "心理", "恋爱", "婚姻", "情绪", "心理咨询"],
          "房产楼市": ["房产", "楼市", "买房", "房价", "地产", "租房", "装修"],
          "搞笑幽默": ["搞笑", "幽默", "段子", "吐槽", "沙雕", "表情包"],
          "教育考试": ["教育", "考试", "培训", "考研", "考公", "留学", "英语", "学习"],
          "文摘精选": ["文摘", "精选", "美文", "散文", "故事", "文章精选"],
          "旅游出行": ["旅游", "出行", "旅行", "攻略", "景点", "酒店", "度假"],
          "时尚潮流": ["时尚", "潮流", "穿搭", "服饰", "OOTD", "ootd", "搭配"],
          "民生资讯": ["民生", "社会", "热点", "时事", "政策", "民生新闻"],
          "汽车交通": ["汽车", "交通", "车", "新能源", "电动车", "买车"],
          "知识百科": ["知识", "百科", "科普", "常识", "冷知识"],
          "科技数码": ["科技", "数码", "手机", "电脑", "智能", "AI", "互联网", "软件", "硬件"],
          "美容美体": ["美容", "美体", "护肤", "化妆", "美妆", "彩妆"],
          "美食餐饮": ["美食", "餐饮", "做饭", "烹饪", "餐厅", "探店", "食谱"],
          "职场发展": ["职场", "工作", "求职", "面试", "跳槽", "升职", "加薪", "简历"],
          "财富理财": ["财富", "理财", "财经", "投资", "基金", "股票", "保险", "财务", "赚钱"],
      }
      
      # 榜单更新时间规则
      DAY_UPDATE_HOUR = 17
      DAY_UPDATE_MINUTE = 30
      MONTH_UPDATE_DAY = 3
      MONTH_UPDATE_HOUR = 23
      MONTH_UPDATE_MINUTE = 0
      
      DAY_MAX_DAYS_BACK = 7
      WEEK_MAX_WEEKS_BACK = 3
      MONTH_MAX_MONTHS_BACK = 3
      
      
      # ===== 日期计算 =====
      def _is_after_day_update(now=None):
          """判断当前时间是否已过日榜/周榜更新时间(17:30)"""
          if now is None:
              now = datetime.now()
          cutoff = now.replace(hour=DAY_UPDATE_HOUR, minute=DAY_UPDATE_MINUTE, second=0, microsecond=0)
          return now >= cutoff
      
      
      def _is_after_month_update(now=None):
          """判断当前时间是否已过月榜更新时间(当月3号23:00)"""
          if now is None:
              now = datetime.now()
          try:
              cutoff = now.replace(day=MONTH_UPDATE_DAY, hour=MONTH_UPDATE_HOUR,
                                   minute=MONTH_UPDATE_MINUTE, second=0, microsecond=0)
          except ValueError:
              cutoff = now.replace(day=MONTH_UPDATE_DAY, hour=MONTH_UPDATE_HOUR,
                                   minute=MONTH_UPDATE_MINUTE, second=0, microsecond=0)
          return now >= cutoff
      
      
      def get_latest_query_date(rank_type="day"):
          """获取最新可查询的榜单日期"""
          now = datetime.now()
      
          if rank_type == "day":
              if _is_after_day_update(now):
                  target = now - timedelta(days=1)
              else:
                  target = now - timedelta(days=2)
              return target.strftime("%Y-%m-%d")
      
          elif rank_type == "week":
              weekday = now.weekday()
              this_monday = now - timedelta(days=weekday)
              if _is_after_day_update(now):
                  return this_monday.strftime("%Y-%m-%d")
              else:
                  last_monday = this_monday - timedelta(weeks=1)
                  return last_monday.strftime("%Y-%m-%d")
      
          elif rank_type == "month":
              # 月榜: 每月3号23:00更新上月数据
              # rankDate传上月1号 = 上月数据
              # 3号23:00后 → 上月1号(=上月数据已更新); 3号23:00前 → 上上月1号
              if _is_after_month_update(now):
                  if now.month == 1:
                      return datetime(now.year - 1, 12, 1).strftime("%Y-%m-%d")
                  else:
                      return datetime(now.year, now.month - 1, 1).strftime("%Y-%m-%d")
              else:
                  if now.month == 1:
                      return datetime(now.year - 1, 11, 1).strftime("%Y-%m-%d")
                  elif now.month == 2:
                      return datetime(now.year - 1, 12, 1).strftime("%Y-%m-%d")
                  else:
                      return datetime(now.year, now.month - 2, 1).strftime("%Y-%m-%d")
      
          return now.strftime("%Y-%m-%d")
      
      
      def get_earliest_query_date(rank_type="day"):
          """获取可查询的最早日期"""
          now = datetime.now()
      
          if rank_type == "day":
              earliest = now - timedelta(days=DAY_MAX_DAYS_BACK)
              return earliest.strftime("%Y-%m-%d")
          elif rank_type == "week":
              weekday = now.weekday()
              this_monday = now - timedelta(days=weekday)
              earliest_monday = this_monday - timedelta(weeks=WEEK_MAX_WEEKS_BACK)
              return earliest_monday.strftime("%Y-%m-%d")
          elif rank_type == "month":
              m = now.month
              y = now.year
              m -= MONTH_MAX_MONTHS_BACK
              while m <= 0:
                  m += 12
                  y -= 1
              return datetime(y, m, 1).strftime("%Y-%m-%d")
          return now.strftime("%Y-%m-%d")
      
      
      def validate_and_adjust_date(rank_type, user_date_str):
          """验证用户指定日期是否在可查询范围"""
          user_date = datetime.strptime(user_date_str, "%Y-%m-%d")
          latest = datetime.strptime(get_latest_query_date(rank_type), "%Y-%m-%d")
          earliest = datetime.strptime(get_earliest_query_date(rank_type), "%Y-%m-%d")
      
          if earliest <= user_date <= latest:
              return {
                  "original_date": user_date_str,
                  "adjusted_date": user_date_str,
                  "is_adjusted": False,
                  "reminder": ""
              }
      
          if user_date > latest:
              adjusted = latest.strftime("%Y-%m-%d")
          else:
              adjusted = earliest.strftime("%Y-%m-%d")
      
          rank_label = {"day": "日榜", "week": "周榜", "month": "月榜"}.get(rank_type, "日榜")
          reminder = (
              '非常抱歉🙏,目前公众号榜单最多支持回溯「近7天的日榜/近3周的周榜/近3个月的月榜」,'
              '我将为您查询最接近您需求时间的{}数据⭐~'
          ).format(rank_label)
      
          return {
              "original_date": user_date_str,
              "adjusted_date": adjusted,
              "is_adjusted": True,
              "reminder": reminder
          }
      
      
      def get_query_date(rank_type="day", user_date=None):
          """根据榜单类型和当前时间确定查询日期
      
          Args:
              rank_type: day/week/month
              user_date: 用户指定日期
      
          Returns:
              (查询日期字符串, 是否自动推断, 提醒消息)
          """
          if user_date:
              validation = validate_and_adjust_date(rank_type, user_date)
              return validation["adjusted_date"], False, validation["reminder"]
      
          rank_date = get_latest_query_date(rank_type)
      
          # 检查今日日榜是否已更新
          reminder = ""
          if rank_type == "day" and not _is_after_day_update():
              yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
              reminder = (
                  '日榜数据暂未更新,将为您查询最接近您需求日期的榜单数据⭐~\n'
                  f'推荐查询昨日更新的最新榜单({yesterday}日榜)'
              )
      
          now = datetime.now()
          if rank_type == "day":
              if _is_after_day_update(now):
                  pass  # 已过17:30,查询昨日数据
              else:
                  pass  # 未过17:30,查询前天数据
          elif rank_type == "week":
              if _is_after_day_update(now):
                  pass
              else:
                  pass
          elif rank_type == "month":
              if _is_after_month_update(now):
                  pass
              else:
                  pass
      
          return rank_date, True, reminder
      
      
      # ===== 分类匹配 =====
      def match_category(user_input):
          """根据用户输入匹配分类"""
          if not user_input:
              return "人文资讯"
          if user_input in CATEGORIES:
              return user_input
          user_lower = user_input.lower().strip()
          for category, keywords in CATEGORY_KEYWORDS.items():
              for keyword in keywords:
                  if keyword.lower() in user_lower:
                      return category
          return "人文资讯"
      
      
      # ===== 认证 =====
      def _get_redfox_api_key():
          """三级认证回退获取 红狐Hub API Key
      
          优先级:
          1. 环境变量 REDFOX_API_KEY
          2. Shell 配置文件(.zshrc / .bashrc / PowerShell Profile)
          3. 返回 None,由上层提示用户配置
      
          Returns:
              str or None: API Key
          """
          # 第一级:环境变量
          api_key = os.getenv("REDFOX_API_KEY")
          if api_key and api_key.startswith("ak_"):
              return api_key
      
          # 第二级:Shell 配置文件
          home = pathlib.Path.home()
          config_files = []
      
          if sys.platform == "win32":
              # PowerShell Profile
              ps_dir = home / "Documents" / "WindowsPowerShell"
              if ps_dir.exists():
                  for f in ps_dir.glob("Microsoft.PowerShell_profile.ps1"):
                      config_files.append(f)
              ps7_dir = home / "Documents" / "PowerShell"
              if ps7_dir.exists():
                  for f in ps7_dir.glob("Microsoft.PowerShell_profile.ps1"):
                      config_files.append(f)
              # Git Bash / MSYS2
              for name in [".bashrc", ".bash_profile", ".profile"]:
                  p = home / name
                  if p.exists():
                      config_files.append(p)
          else:
              for name in [".zshrc", ".bashrc", ".bash_profile", ".profile"]:
                  p = home / name
                  if p.exists():
                      config_files.append(p)
      
          for config in config_files:
              try:
                  content = config.read_text(encoding="utf-8", errors="ignore")
                  for line in content.splitlines():
                      line = line.strip()
                      if line.startswith("export REDFOX_API_KEY=") or line.startswith("set REDFOX_API_KEY="):
                          parts = line.split("=", 1)
                          if len(parts) == 2:
                              key = parts[1].strip().strip('"').strip("'")
                              if key and key.startswith("ak_"):
                                  return key
                      # 匹配 PowerShell: $env:REDFOX_API_KEY = "xxx"
                      if "$env:REDFOX_API_KEY" in line:
                          parts = line.split("=", 1)
                          if len(parts) == 2:
                              key = parts[1].strip().strip('"').strip("'")
                              if key and key.startswith("ak_"):
                                  return key
              except Exception:
                  continue
      
          return None
      
      
      # ===== HTTP请求 =====
      def fetch_gzh_growth(rank_type="day", rank_date=None, category="人文资讯"):
          """获取公众号热门账号推荐榜单数据"""
          if not rank_date:
              rank_date = get_latest_query_date(rank_type)
      
          params = {
              "rankType": rank_type,
              "rankDate": rank_date,
              "category": category,
              "source": SOURCE,
          }
      
          credential = _get_redfox_api_key()
          if not credential:
              print(
                  "未检测到 红狐Hub API Key。请按以下步骤配置:\n"
                  "1. 访问 https://redfox.hk/?source=github 了解服务详情\n"
                  "2. 前往 https://redfox.hk/login?source=github 注册账号(新用户获赠免费积分)\n"
                  "3. 注册登录后在个人中心获取 API Key(格式 ak_xxxxxxxx)\n"
                  "4. 设置环境变量:\n"
                  "   macOS/Linux: export REDFOX_API_KEY=<你的apikey>\n"
                  "   Windows PowerShell: $env:REDFOX_API_KEY = \"<你的apikey>\"\n"
                  "   或告知我帮你自动配置到 Shell 配置文件中",
                  file=sys.stderr
              )
              return {
                  "fetch_time": _get_update_time_label(rank_type, rank_date),
                  "rank_type": rank_type,
                  "rank_date": rank_date,
                  "category": category,
                  "time_range": _get_data_time_range(rank_type, rank_date),
                  "account_list": [],
                  "error": "MISSING_API_KEY"
              }
          headers = {
              "Content-Type": "application/json",
              "X-API-KEY": credential,
          }
      
          try:
              response = requests.get(API_URL, params=params, headers=headers, timeout=30)
              if response.status_code != 200:
                  return {
                      "fetch_time": _get_update_time_label(rank_type, rank_date),
                      "rank_type": rank_type,
                      "rank_date": rank_date,
                      "category": category,
                      "time_range": _get_data_time_range(rank_type, rank_date),
                      "account_list": []
                  }
              api_response = response.json()
          except Exception:
              return {
                  "fetch_time": _get_update_time_label(rank_type, rank_date),
                  "rank_type": rank_type,
                  "rank_date": rank_date,
                  "category": category,
                  "time_range": _get_data_time_range(rank_type, rank_date),
                  "account_list": []
              }
      
          if isinstance(api_response, dict):
              data = api_response.get("data", [])
          elif isinstance(api_response, list):
              data = api_response
          else:
              data = []
      
          if not data:
              return {
                  "fetch_time": _get_update_time_label(rank_type, rank_date),
                  "rank_type": rank_type,
                  "rank_date": rank_date,
                  "category": category,
                  "time_range": _get_data_time_range(rank_type, rank_date),
                  "account_list": []
              }
      
          # 保持接口原始顺序,禁止重新排序
          # 综合评分直接采用接口返回的comprehensiveScore字段
      
          account_list = []
          for idx, item in enumerate(data):
              account_list.append({
                  "index": idx + 1,
                  "rankPosition": item.get("rankPosition", idx + 1),
                  "accountName": item.get("accountName", ""),
                  "accountId": item.get("accountId", ""),
                  "accountAvatar": item.get("accountAvatar", ""),
                  "category": item.get("category", ""),
                  "compositeScore": round(float(item.get("comprehensiveScore", 0)), 1) if item.get("comprehensiveScore") else 0,
                  "totalReadCount": item.get("totalReadCount", 0),
                  "headlineReadCount": item.get("headlineReadCount", 0),
                  "maxReadCount": item.get("maxReadCount", 0),
                  "totalLikeCount": item.get("totalLikeCount", 0),
                  "totalForwardCount": item.get("totalForwardCount", 0),
                  "totalInSeeCount": item.get("totalInSeeCount", 0),
                  "publishCount": item.get("publishCount", "-"),
              })
      
          return {
              "fetch_time": _get_update_time_label(rank_type, rank_date),
              "rank_type": rank_type,
              "rank_date": rank_date,
              "category": category,
              "time_range": _get_data_time_range(rank_type, rank_date),
              "account_list": account_list
          }
      
      
      def _get_data_time_range(rank_type, rank_date):
          """根据榜单类型和查询日期生成数据统计时间周期描述"""
          import calendar
          date_obj = datetime.strptime(rank_date, "%Y-%m-%d")
          if rank_type == "day":
              return f"{rank_date}"
          elif rank_type == "week":
              end_date = date_obj + timedelta(days=6)
              return f"{date_obj.strftime('%Y-%m-%d')}至{end_date.strftime('%Y-%m-%d')}"
          elif rank_type == "month":
              last_day = calendar.monthrange(date_obj.year, date_obj.month)[1]
              return f"{date_obj.strftime('%Y-%m')}-01至{date_obj.strftime('%Y-%m')}-{last_day:02d}"
          return rank_date
      
      
      def _get_update_time_label(rank_type, rank_date):
          """获取榜单更新时间标签"""
          if rank_type == "day":
              d = datetime.strptime(rank_date, "%Y-%m-%d")
              update_time = d + timedelta(days=1)
              return f"{update_time.strftime('%Y-%m-%d')} 17:30"
          elif rank_type == "week":
              d = datetime.strptime(rank_date, "%Y-%m-%d")
              next_monday = d + timedelta(weeks=1)
              return f"{next_monday.strftime('%Y-%m-%d')} 17:30"
          elif rank_type == "month":
              d = datetime.strptime(rank_date, "%Y-%m-%d")
              if d.month == 12:
                  update_day = datetime(d.year + 1, 1, MONTH_UPDATE_DAY)
              else:
                  update_day = datetime(d.year, d.month + 1, MONTH_UPDATE_DAY)
              return f"{update_day.strftime('%Y-%m-%d')} 23:00"
          return rank_date
      
      
      # ===== 数据转换 =====
      def raw_data_to_account_list(raw_data):
          """将 gzh_growth_fetcher 的 raw_data 转换为 generate_html 所需的 account_list 格式
      
          Args:
              raw_data: gzh_growth_fetcher 输出 JSON 中的 raw_data 数组
      
          Returns:
              list: generate_html 所需的 account_list 格式
          """
          account_list = []
          for idx, item in enumerate(raw_data):
              account_list.append({
                  "index": idx + 1,
                  "rankPosition": item.get("rankPosition", idx + 1),
                  "accountName": item.get("accountName", ""),
                  "accountId": item.get("accountId", ""),
                  "accountAvatar": item.get("accountAvatar", ""),
                  "category": item.get("category", ""),
                  "compositeScore": item.get("compositeScore", 0),
                  "totalReadCount": item.get("totalReadCount", 0),
                  "headlineReadCount": item.get("headlineReadCount", 0),
                  "maxReadCount": item.get("maxReadCount", 0),
                  "totalLikeCount": item.get("totalLikeCount", 0),
                  "totalForwardCount": item.get("totalForwardCount", 0),
                  "totalInSeeCount": item.get("totalInSeeCount", 0),
                  "publishCount": item.get("publishCount", "-"),
              })
          return account_list
      
      
      # ===== HTML生成 =====
      def generate_html(result, top_n=50):
          """生成HTML页面 - 公众号热门账号推荐榜
      
          Args:
              result: 榜单数据结果
              top_n: 显示条数,默认50
          """
          account_list = result["account_list"]
          fetch_time = result["fetch_time"]
          rank_date = result.get("rank_date", "")
          category = result.get("category", "人文资讯")
          rank_type = result.get("rank_type", "day")
      
          rank_type_label = {"day": "日榜", "week": "周榜", "month": "月榜"}.get(rank_type, "日榜")
          time_range = result.get("time_range", _get_data_time_range(rank_type, rank_date))
      
          top_n = min(top_n, len(account_list), 100)
          account_list = account_list[:top_n]
      
          js_data = json.dumps(account_list, ensure_ascii=False, indent=2)
      
          page_title = f"公众号综合实力{rank_type_label} - {category}"
      
          html = f'''<!DOCTYPE html>
      <html lang="zh-CN">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>{page_title}</title>
          <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
          <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
          <style>
              * {{ margin: 0; padding: 0; box-sizing: border-box; }}
              body {{
                  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', sans-serif;
                  background: #f5f5f5;
                  color: #333;
                  line-height: 1.5;
              }}
      
              .page-wrap {{
                  max-width: 700px;
                  margin: 0 auto;
                  padding: 12px 12px 24px;
              }}
      
              .export-bar {{
                  position: sticky;
                  top: 0;
                  z-index: 100;
                  display: flex;
                  justify-content: flex-end;
                  gap: 10px;
                  padding: 8px 0 10px;
              }}
              .btn-export-pdf, .btn-export-img {{
                  background: #fff;
                  color: #07c160;
                  border: 1.5px solid #07c160;
                  border-radius: 20px;
                  padding: 6px 18px;
                  font-size: 13px;
                  font-weight: 600;
                  cursor: pointer;
                  transition: all 0.2s ease;
              }}
              .btn-export-pdf:hover, .btn-export-img:hover {{ background: #f0faf4; }}
      
              .pdf-content {{ background: transparent; }}
      
              .header-wrap {{
                  background: linear-gradient(135deg, #07c160 0%, #2dc84d 100%);
                  border-radius: 12px;
                  padding: 20px 16px;
                  margin-bottom: 12px;
                  color: #fff;
              }}
              .header-wrap h1 {{
                  font-size: 22px;
                  font-weight: 700;
                  margin-bottom: 4px;
              }}
              .header-meta {{
                  font-size: 13px;
                  opacity: 0.9;
              }}
              .category-badge {{
                  display: inline-block;
                  background: rgba(255,255,255,0.25);
                  border-radius: 10px;
                  padding: 2px 10px;
                  font-size: 13px;
                  margin-left: 8px;
              }}
      
              .stats-row {{
                  display: flex;
                  justify-content: space-around;
                  margin-top: 14px;
                  padding-top: 14px;
                  border-top: 1px solid rgba(255,255,255,0.2);
              }}
              .stat-item {{ text-align: center; }}
              .stat-num {{ font-size: 20px; font-weight: 700; }}
              .stat-label {{ font-size: 12px; opacity: 0.85; margin-top: 2px; }}
      
              .account-list {{
                  display: flex;
                  flex-direction: column;
                  gap: 10px;
              }}
      
              .account-card {{
                  background: #fff;
                  border-radius: 12px;
                  padding: 14px;
                  transition: all 0.2s ease;
                  border: 1px solid #f0f0f0;
              }}
              .account-card:hover {{
                  box-shadow: 0 4px 12px rgba(0,0,0,0.08);
              }}
              .account-card.top1 {{ border-left: 3px solid #07c160; }}
              .account-card.top2 {{ border-left: 3px solid #2dc84d; }}
              .account-card.top3 {{ border-left: 3px solid #7ed89a; }}
      
              .row1 {{
                  display: flex;
                  align-items: center;
                  margin-bottom: 10px;
              }}
              .rank-num {{
                  min-width: 36px;
                  height: 36px;
                  display: flex;
                  align-items: center;
                  justify-content: center;
                  font-size: 18px;
                  font-weight: 700;
                  color: #999;
                  margin-right: 12px;
                  flex-shrink: 0;
              }}
              .account-avatar {{
                  width: 40px;
                  height: 40px;
                  border-radius: 50%;
                  object-fit: cover;
                  margin-right: 10px;
                  flex-shrink: 0;
              }}
              .account-info {{
                  flex: 1;
                  min-width: 0;
              }}
              .account-name {{
                  font-size: 16px;
                  font-weight: 600;
                  color: #333;
                  line-height: 1.4;
              }}
              .account-name a {{
                  color: #333;
                  text-decoration: none;
              }}
              .account-name a:hover {{
                  color: #07c160;
              }}
              .account-id {{
                  font-size: 12px;
                  color: #999;
                  margin-top: 2px;
              }}
              .publish-tag {{
                  display: inline-block;
                  background: #f0faf4;
                  color: #07c160;
                  border-radius: 4px;
                  padding: 1px 6px;
                  font-size: 12px;
                  font-weight: 500;
                  margin-left: 8px;
              }}
      
              .row2 {{
                  display: flex;
                  flex-wrap: wrap;
                  gap: 14px;
                  padding-left: 48px;
                  margin-top: 8px;
                  padding-top: 8px;
                  border-top: 1px solid #f5f5f5;
              }}
              .data-item {{
                  display: flex;
                  align-items: center;
                  gap: 4px;
                  font-size: 13px;
              }}
              .data-label {{ color: #999; }}
              .data-value {{ color: #333; font-weight: 600; }}
      
              .footer-note {{
                  text-align: center;
                  font-size: 12px;
                  color: #bbb;
                  margin-top: 16px;
                  padding: 10px 0;
              }}
      
              @media (max-width: 600px) {{
                  .page-wrap {{ padding: 8px 8px 20px; }}
                  .header-wrap {{ padding: 16px 12px; }}
                  .header-wrap h1 {{ font-size: 20px; }}
                  .account-card {{ padding: 12px; }}
                  .account-name {{ font-size: 15px; }}
                  .rank-num {{ min-width: 32px; height: 32px; font-size: 16px; }}
                  .row2 {{ gap: 10px; padding-left: 44px; margin-top: 8px; }}
                  .data-item {{ font-size: 12px; }}
              }}
          </style>
      </head>
      <body>
      
      <div class="page-wrap">
          <div class="export-bar">
              <button class="btn-export-img" onclick="exportImage()">导出图片</button>
              <button class="btn-export-pdf" onclick="exportPdf()">导出 PDF</button>
          </div>
      
          <div class="pdf-content" id="pdfContent">
              <div class="header-wrap">
                  <h1>公众号综合实力{rank_type_label}<span class="category-badge">{category}</span></h1>
                  <div class="header-meta">更新时间:{fetch_time}</div>
                  <div style="font-size:12px;color:rgba(255,255,255,0.75);margin-top:4px;">*公众号综合实力{rank_type_label},基于阅读、点赞、转发、在看等多维数据综合排名*</div><div style="font-size:12px;color:rgba(255,255,255,0.75);">*数据统计时间周期:{time_range}*</div>
                  <div class="stats-row">
                      <div class="stat-item">
                          <div class="stat-num" id="totalCount">--</div>
                          <div class="stat-label">公众号数</div>
                      </div>
                      <div class="stat-item">
                          <div class="stat-num" id="maxRead">--</div>
                          <div class="stat-label">最高总阅读</div>
                      </div>
                      <div class="stat-item">
                          <div class="stat-num" id="avgRead">--</div>
                          <div class="stat-label">平均总阅读</div>
                      </div>
                  </div>
              </div>
      
              <div class="account-list" id="accountList"></div>
      
              <div class="footer-note">数据来源:公众号平台 · {category} · 公众号综合实力{rank_type_label}</div>
          </div>
      </div>
      
      <script>
      (function() {{
          function bindCardClick() {{
              var cards = document.querySelectorAll('.account-card[data-href]');
              cards.forEach(function(card) {{
                  card.addEventListener('click', function(e) {{
                      if (e.target.closest('a, button')) return;
                      var url = this.getAttribute('data-href');
                      if (url && url !== '#') window.open(url, '_blank');
                  }});
              }});
          }}
          if (document.readyState === 'loading') {{
              document.addEventListener('DOMContentLoaded', bindCardClick);
          }} else {{
              bindCardClick();
          }}
      }})();
      
      (function() {{
          var RAW = {js_data};
      
          var totalCount = RAW.length;
          var maxRead = 0, sumRead = 0;
      
          function fmtNum(val) {{
              var n = parseInt(val) || 0;
              if (n >= 100000000) return (n / 100000000).toFixed(1) + '亿';
              if (n >= 10000) return (n / 10000).toFixed(1) + '万';
              return n.toString();
          }}
      
          for (var i = 0; i < RAW.length; i++) {{
              var totalRead = parseInt(RAW[i].totalReadCount) || 0;
              if (totalRead > maxRead) maxRead = totalRead;
              sumRead += totalRead;
          }}
          var avgRead = totalCount > 0 ? sumRead / totalCount : 0;
      
          document.getElementById('totalCount').textContent = totalCount;
          document.getElementById('maxRead').textContent = fmtNum(maxRead);
          document.getElementById('avgRead').textContent = fmtNum(Math.round(avgRead));
      
          var html = '';
          for (var i = 0; i < RAW.length && i < {top_n}; i++) {{
              var d = RAW[i];
              var rank = i + 1;
              var cardCls = 'account-card';
      
              if (rank === 1) cardCls += ' top1';
              else if (rank === 2) cardCls += ' top2';
              else if (rank === 3) cardCls += ' top3';
      
              var searchUrl = 'https://open.weixin.qq.com/qr/code?username=' + encodeURIComponent(d.accountId || d.accountName);
              var avatar = d.accountAvatar || '';
              var accountName = d.accountName || '--';
              var accountId = d.accountId || '';
      
              var totalReadCount = fmtNum(d.totalReadCount);
              var headlineReadCount = fmtNum(d.headlineReadCount);
              var maxReadCount = fmtNum(d.maxReadCount);
              var totalLikeCount = fmtNum(d.totalLikeCount);
              var totalForwardCount = fmtNum(d.totalForwardCount);
              var totalInSeeCount = fmtNum(d.totalInSeeCount);
              var publishCount = d.publishCount || '-';
              var compositeScore = d.compositeScore || 0;
      
              var rankHtml = '';
              if (rank === 1) {{
                  rankHtml = '<div class="rank-num">🥇</div>';
              }} else if (rank === 2) {{
                  rankHtml = '<div class="rank-num">🥈</div>';
              }} else if (rank === 3) {{
                  rankHtml = '<div class="rank-num">🥉</div>';
              }} else {{
                  rankHtml = '<div class="rank-num">' + rank + '</div>';
              }}
      
              html += '<div class="' + cardCls + '" data-href="' + searchUrl + '">'
                  + '<div class="row1">'
                  + rankHtml
                  + (avatar ? '<img class="account-avatar export-hide" src="' + avatar + '" referrerPolicy="no-referrer" onerror="this.style.display=\\'none\\'">' : '')
                  + '<div class="account-info">'
                  + '<div class="account-name"><a href="' + searchUrl + '" target="_blank" onclick="event.stopPropagation()">' + accountName + '</a>'
                  + '<span class="publish-tag">发布' + publishCount + '</span>'
                  + '</div>'
                  + '<div class="account-id">ID: ' + accountId + ' | 综合评分: <b style="color:#07c160">' + compositeScore + '</b></div>'
                  + '</div>'
                  + '</div>'
                  + '<div class="row2">'
                  + '<div class="data-item"><span class="data-label">总阅读</span><span class="data-value">' + totalReadCount + '</span></div>'
                  + '<div class="data-item"><span class="data-label">头条阅读</span><span class="data-value">' + headlineReadCount + '</span></div>'
                  + '<div class="data-item"><span class="data-label">最高阅读</span><span class="data-value">' + maxReadCount + '</span></div>'
                  + '<div class="data-item"><span class="data-label">点赞</span><span class="data-value">' + totalLikeCount + '</span></div>'
                  + '<div class="data-item"><span class="data-label">在看</span><span class="data-value">' + totalInSeeCount + '</span></div>'
                  + '<div class="data-item"><span class="data-label">转发</span><span class="data-value">' + totalForwardCount + '</span></div>'
                  + '</div>'
                  + '</div>';
          }}
      
          document.getElementById('accountList').innerHTML = html;
      }})();
      
      function _hideAvatarsForExport() {{
          var imgs = document.querySelectorAll('img.export-hide');
          imgs.forEach(function(img) {{
              img.setAttribute('data-original-src', img.src);
              img.style.display = 'none';
          }});
      }}
      
      function _restoreAvatarsAfterExport() {{
          var imgs = document.querySelectorAll('img.export-hide');
          imgs.forEach(function(img) {{
              var origSrc = img.getAttribute('data-original-src');
              if (origSrc) {{
                  img.src = origSrc;
                  img.removeAttribute('data-original-src');
              }}
              img.style.display = '';
          }});
      }}
      
      function exportImage() {{
          var btn = document.querySelector('.btn-export-img');
          btn.textContent = '生成中...';
          btn.style.pointerEvents = 'none';
          var target = document.getElementById('pdfContent');
          _hideAvatarsForExport();
          html2canvas(target, {{
              scale: 2,
              useCORS: true,
              allowTaint: true,
              backgroundColor: '#f5f5f5',
              logging: false,
              windowWidth: target.scrollWidth,
              windowHeight: target.scrollHeight
          }}).then(function(canvas) {{
              _restoreAvatarsAfterExport();
              var link = document.createElement('a');
              link.download = 'gzh_power_account_' + new Date().toISOString().slice(0,10) + '.png';
              link.href = canvas.toDataURL('image/png');
              link.click();
              btn.textContent = '导出图片';
              btn.style.pointerEvents = '';
          }}).catch(function(err) {{
              _restoreAvatarsAfterExport();
              alert('图片生成失败:' + err.message);
              btn.textContent = '导出图片';
              btn.style.pointerEvents = '';
          }});
      }}
      
      function exportPdf() {{
          var btn = document.querySelector('.btn-export-pdf');
          btn.textContent = '生成中...';
          btn.style.pointerEvents = 'none';
          var target = document.getElementById('pdfContent');
          _hideAvatarsForExport();
          html2canvas(target, {{
              scale: 2,
              useCORS: true,
              allowTaint: true,
                  backgroundColor: '#f5f5f5',
                  logging: false,
                  windowWidth: target.scrollWidth,
                  windowHeight: target.scrollHeight
              }}).then(function(canvas) {{
                  _restoreAvatarsAfterExport();
                  var imgData = canvas.toDataURL('image/png', 1.0);
                  var pdf = new jspdf.jsPDF('l', 'mm', 'a4');
                  var pdfW = pdf.internal.pageSize.getWidth();
                  var pdfH = pdf.internal.pageSize.getHeight();
                  var margin = 8;
                  var contentW = pdfW - margin * 2;
                  var contentH = pdfH - margin * 2;
                  var imgW = contentW;
                  var imgH = (canvas.height * imgW) / canvas.width;
                  if (imgH > contentH) {{
                      imgH = contentH;
                      imgW = (canvas.width * imgH) / canvas.height;
                  }}
                  var imgX = (pdfW - imgW) / 2;
                  var imgY = (pdfH - imgH) / 2;
                  pdf.addImage(imgData, 'PNG', imgX, imgY, imgW, imgH);
                  var now = new Date();
                  var dateStr = now.getFullYear() +
                      String(now.getMonth()+1).padStart(2,'0') +
                      String(now.getDate()).padStart(2,'0') +
                      '_' +
                      String(now.getHours()).padStart(2,'0') +
                      String(now.getMinutes()).padStart(2,'0');
                  pdf.save('gzh_power_account_' + dateStr + '.pdf');
                  btn.textContent = '导出 PDF';
                  btn.style.pointerEvents = '';
              }}).catch(function(err) {{
                  _restoreAvatarsAfterExport();
                  alert('PDF 生成失败:' + err.message);
                  btn.textContent = '导出 PDF';
                  btn.style.pointerEvents = '';
              }});
      }}
      </script>
      
      </body>
      </html>'''
      
          # Replace js_data placeholder
          html = html.replace("{js_data}", js_data)
      
          return html
      
      
      if __name__ == "__main__":
          import argparse
      
          parser = argparse.ArgumentParser(description='生成公众号热门账号推荐榜HTML页面')
          parser.add_argument('--rank_type', type=str, default='day', choices=['day', 'week', 'month'], help='榜单类型')
          parser.add_argument('--rank_date', type=str, help='查询日期,格式 YYYY-MM-DD')
          parser.add_argument('--category', type=str, default=None, help='分类名称')
          parser.add_argument('--keyword', type=str, help='用户输入的关键词,用于自动匹配分类')
          parser.add_argument('--output', type=str, help='输出文件路径')
          parser.add_argument('--top', type=int, default=50, help='显示条数,默认50')
          parser.add_argument('--input_json_file', type=str, default=None,
                              help='预取数据JSON文件路径(来自gzh_growth_fetcher输出),指定后不再调用API')
      
          args = parser.parse_args()
      
          if args.input_json_file:
              # 使用预取数据,不调用 API
              if not os.path.isfile(args.input_json_file):
                  print(f"错误: 文件不存在: {args.input_json_file}", file=sys.stderr)
                  sys.exit(1)
              try:
                  with open(args.input_json_file, 'r', encoding='utf-8') as f:
                      fetched = json.load(f)
              except Exception as e:
                  print(f"错误: 读取文件失败: {e}", file=sys.stderr)
                  sys.exit(1)
      
              raw_data = fetched.get("raw_data", [])
              if not raw_data:
                  print("错误: 输入文件中无 raw_data 数据", file=sys.stderr)
                  sys.exit(1)
      
              account_list = raw_data_to_account_list(raw_data)
              rank_type = fetched.get("rank_type", "day")
              rank_date = fetched.get("rank_date", "")
              category = fetched.get("category", "人文资讯")
              rank_type_label = {"day": "日榜", "week": "周榜", "month": "月榜"}.get(rank_type, "日榜")
      
              if args.keyword or args.category:
                  print("注意: 已指定 --input_json_file,忽略 --keyword/--category 参数", file=sys.stderr)
      
              result = {
                  "fetch_time": fetched.get("update_time", ""),
                  "rank_type": rank_type,
                  "rank_date": rank_date,
                  "category": category,
                  "time_range": _get_data_time_range(rank_type, rank_date),
                  "account_list": account_list
              }
      
              print(f"使用预取数据生成HTML(不调用API)...", file=sys.stderr)
              print(f"榜单类型: {rank_type_label}", file=sys.stderr)
              print(f"查询日期: {rank_date}", file=sys.stderr)
              print(f"分类:{category}", file=sys.stderr)
          else:
              # 原有逻辑:调用 API 获取数据
              # 处理分类
              if args.keyword and not args.category:
                  category = match_category(args.keyword)
                  print(f"根据关键词【{args.keyword}】匹配到分类:【{category}】", file=sys.stderr)
              elif args.category:
                  category = args.category
              else:
                  category = "人文资讯"
      
              # 处理日期
              rank_date, is_auto, reminder = get_query_date(args.rank_type, args.rank_date)
      
              rank_type_label = {"day": "日榜", "week": "周榜", "month": "月榜"}.get(args.rank_type, "日榜")
      
              print(f"正在获取公众号综合实力{rank_type_label}数据...", file=sys.stderr)
              print(f"当前时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", file=sys.stderr)
              if reminder:
                  print(f"提醒: {reminder}", file=sys.stderr)
              print(f"榜单类型: {rank_type_label}", file=sys.stderr)
              print(f"查询日期: {rank_date}", file=sys.stderr)
              print(f"分类:{category}", file=sys.stderr)
      
              result = fetch_gzh_growth(rank_type=args.rank_type, rank_date=rank_date, category=category)
      
          html = generate_html(result, top_n=args.top)
      
          if args.output:
              output_path = args.output
          else:
              rank_short = {"day": "日", "week": "周", "month": "月"}.get(result.get("rank_type", args.rank_type), "日")
              timestamp_str = datetime.now().strftime("%Y%m%d")[-8:] + f"{datetime.now().strftime('%H%M%S')}"
              filename = f"公众号综合实力{rank_short}_{result.get('category', category)}_{timestamp_str}.html"
              output_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), filename)
      
          with open(output_path, 'w', encoding='utf-8') as f:
              f.write(html)
      
          print(f"已生成:{output_path}", file=sys.stderr)
          print(f"共 {len(result['account_list'])} 条{rank_type_label}数据,展示TOP{args.top}", file=sys.stderr)
      
    • gzh_growth_fetcher.py 33.1 KB
      #!/usr/bin/env python3
      # -*- coding: utf-8 -*-
      """公众号热门账号推荐榜数据获取脚本
      
      通过HTTP请求获取公众号热门账号推荐榜单数据,支持日榜/周榜/月榜,22个分类查询。
      
      用法:
          python scripts/gzh_growth_fetcher.py --rank_type day --keyword 科技 --top_n 50
          python scripts/gzh_growth_fetcher.py --rank_type week --category 科技数码
          python scripts/gzh_growth_fetcher.py --rank_type month --category 总排名 --top_n 50
          python scripts/gzh_growth_fetcher.py --list_categories
      """
      
      import argparse
      import json
      import os
      import sys
      import pathlib
      from datetime import datetime, timedelta
      from urllib.parse import quote
      import requests
      
      
      # ===== 常量 =====
      API_URL = "https://redfox.hk/story/api/cozeSkill/getGzhCozeSkillDataIndex"
      SOURCE = "公众号综合实力账号榜-GitHub"
      
      CATEGORIES = [
          "乐活生活", "人文资讯", "企业品牌", "体育娱乐", "健康养生",
          "创投商业", "学术研究", "情感心理", "房产楼市", "搞笑幽默",
          "教育考试", "文摘精选", "旅游出行", "时尚潮流", "民生资讯",
          "汽车交通", "知识百科", "科技数码", "美容美体", "美食餐饮",
          "职场发展", "财富理财", "总排名"
      ]
      
      RANK_TYPE_MAP = {
          "day": "日榜",
          "week": "周榜",
          "month": "月榜"
      }
      
      # 榜单更新时间规则
      # 日榜:每日17:30更新昨日数据
      # 周榜:每周一17:30更新上周数据
      # 月榜:每月3号23:00更新上月数据
      
      DAY_UPDATE_HOUR = 17
      DAY_UPDATE_MINUTE = 30
      MONTH_UPDATE_DAY = 3
      MONTH_UPDATE_HOUR = 23
      MONTH_UPDATE_MINUTE = 0
      
      DATA_DESCRIPTION_TEMPLATE = "公众号综合实力{rank_label},基于阅读、点赞、转发、在看等多维数据综合排名\n数据统计时间周期:{time_range}"
      
      def get_data_time_range(rank_type, rank_date):
          """根据榜单类型和查询日期生成数据统计时间周期描述"""
          from datetime import datetime, timedelta
          import calendar
          date_obj = datetime.strptime(rank_date, "%Y-%m-%d")
          if rank_type == "day":
              return f"{rank_date}"
          elif rank_type == "week":
              end_date = date_obj + timedelta(days=6)
              return f"{date_obj.strftime('%Y-%m-%d')}至{end_date.strftime('%Y-%m-%d')}"
          elif rank_type == "month":
              last_day = calendar.monthrange(date_obj.year, date_obj.month)[1]
              return f"{date_obj.strftime('%Y-%m')}-01至{date_obj.strftime('%Y-%m')}-{last_day:02d}"
          return rank_date
      
      # 日期查询范围限制
      DAY_MAX_DAYS_BACK = 7
      WEEK_MAX_WEEKS_BACK = 3
      MONTH_MAX_MONTHS_BACK = 3
      
      
      # ===== 日期计算 =====
      def _is_after_day_update(now=None):
          """判断当前时间是否已过日榜/周榜更新时间(17:30)"""
          if now is None:
              now = datetime.now()
          cutoff = now.replace(hour=DAY_UPDATE_HOUR, minute=DAY_UPDATE_MINUTE, second=0, microsecond=0)
          return now >= cutoff
      
      
      def _is_after_month_update(now=None):
          """判断当前时间是否已过月榜更新时间(当月3号23:00)"""
          if now is None:
              now = datetime.now()
          # 本月3号23:00
          try:
              cutoff = now.replace(day=MONTH_UPDATE_DAY, hour=MONTH_UPDATE_HOUR,
                                   minute=MONTH_UPDATE_MINUTE, second=0, microsecond=0)
          except ValueError:
              # 2月没有30号等情况不会出现(3号一定存在)
              cutoff = now.replace(day=MONTH_UPDATE_DAY, hour=MONTH_UPDATE_HOUR,
                                   minute=MONTH_UPDATE_MINUTE, second=0, microsecond=0)
          return now >= cutoff
      
      
      def get_latest_query_date(rank_type="day"):
          """获取最新可查询的榜单日期
      
          Args:
              rank_type: day/week/month
      
          Returns:
              str: 查询日期字符串 (yyyy-MM-dd)
          """
          now = datetime.now()
      
          if rank_type == "day":
              # 日榜: 每日17:30更新昨日数据
              # 17:30后 → 查昨天; 17:30前 → 查前天
              if _is_after_day_update(now):
                  target = now - timedelta(days=1)
              else:
                  target = now - timedelta(days=2)
              return target.strftime("%Y-%m-%d")
      
          elif rank_type == "week":
              # 周榜: 每周一17:30更新上周数据
              # 周一17:30后 → 查本周一; 周一17:30前 → 查上周一
              weekday = now.weekday()  # 0=Monday
              this_monday = now - timedelta(days=weekday)
              if _is_after_day_update(now):
                  # 17:30后 → 本周一
                  return this_monday.strftime("%Y-%m-%d")
              else:
                  # 17:30前 → 上周一
                  last_monday = this_monday - timedelta(weeks=1)
                  return last_monday.strftime("%Y-%m-%d")
      
          elif rank_type == "month":
              # 月榜: 每月3号23:00更新上月数据
              # rankDate传上月1号 = 上月数据
              # 3号23:00后 → 上月1号(=上月数据已更新); 3号23:00前 → 上上月1号(=上上月数据)
              if _is_after_month_update(now):
                  # 已过3号23:00 → 上月1号
                  if now.month == 1:
                      return datetime(now.year - 1, 12, 1).strftime("%Y-%m-%d")
                  else:
                      return datetime(now.year, now.month - 1, 1).strftime("%Y-%m-%d")
              else:
                  # 未过3号23:00 → 上上月1号
                  if now.month == 1:
                      return datetime(now.year - 1, 11, 1).strftime("%Y-%m-%d")
                  elif now.month == 2:
                      return datetime(now.year - 1, 12, 1).strftime("%Y-%m-%d")
                  else:
                      return datetime(now.year, now.month - 2, 1).strftime("%Y-%m-%d")
      
          return now.strftime("%Y-%m-%d")
      
      
      def get_earliest_query_date(rank_type="day"):
          """获取可查询的最早日期(限制范围)
      
          Args:
              rank_type: day/week/month
      
          Returns:
              str: 最早可查询日期字符串 (yyyy-MM-dd)
          """
          now = datetime.now()
      
          if rank_type == "day":
              # 最近7天
              earliest = now - timedelta(days=DAY_MAX_DAYS_BACK)
              return earliest.strftime("%Y-%m-%d")
      
          elif rank_type == "week":
              # 最近3周
              weekday = now.weekday()
              this_monday = now - timedelta(days=weekday)
              earliest_monday = this_monday - timedelta(weeks=WEEK_MAX_WEEKS_BACK)
              return earliest_monday.strftime("%Y-%m-%d")
      
          elif rank_type == "month":
              # 最近3个月
              m = now.month
              y = now.year
              m -= MONTH_MAX_MONTHS_BACK
              while m <= 0:
                  m += 12
                  y -= 1
              return datetime(y, m, 1).strftime("%Y-%m-%d")
      
          return now.strftime("%Y-%m-%d")
      
      
      def validate_and_adjust_date(rank_type, user_date_str):
          """验证用户指定的日期是否在可查询范围内,超出范围则自动调整
      
          Args:
              rank_type: day/week/month
              user_date_str: 用户指定的日期 (yyyy-MM-dd)
      
          Returns:
              dict: {
                  "original_date": 原始日期,
                  "adjusted_date": 调整后的日期(可能不变),
                  "is_adjusted": 是否被调整,
                  "reminder": 提醒消息(无调整时为空)
              }
          """
          user_date = datetime.strptime(user_date_str, "%Y-%m-%d")
          latest = datetime.strptime(get_latest_query_date(rank_type), "%Y-%m-%d")
          earliest = datetime.strptime(get_earliest_query_date(rank_type), "%Y-%m-%d")
      
          if earliest <= user_date <= latest:
              return {
                  "original_date": user_date_str,
                  "adjusted_date": user_date_str,
                  "is_adjusted": False,
                  "reminder": ""
              }
      
          # 超出范围,自动调整到最近可查询日期
          if user_date > latest:
              adjusted = latest.strftime("%Y-%m-%d")
          else:
              adjusted = earliest.strftime("%Y-%m-%d")
      
          reminder = (
              '非常抱歉🙏,目前公众号榜单最多支持回溯「近7天的日榜/近3周的周榜/近3个月的月榜」,'
              '我将为您查询最接近您需求时间的{}数据⭐~'
          ).format(RANK_TYPE_MAP.get(rank_type, "日榜"))
      
          return {
              "original_date": user_date_str,
              "adjusted_date": adjusted,
              "is_adjusted": True,
              "reminder": reminder
          }
      
      
      def get_today_query_reminder(rank_type="day"):
          """当用户查询今日日榜但数据未更新时,生成提醒
      
          Args:
              rank_type: day/week/month
      
          Returns:
              str: 提醒消息,不需要提醒时返回空字符串
          """
          if rank_type == "day" and not _is_after_day_update():
              yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
              return (
                  '日榜数据暂未更新,将为您查询最接近您需求日期的榜单数据⭐~\n'
                  f'推荐查询昨日更新的最新榜单({yesterday}日榜)'
              )
          return ""
      
      
      # ===== 认证 =====
      def _get_redfox_api_key():
          """三级认证回退获取 红狐Hub API Key
      
          优先级:
          1. 环境变量 REDFOX_API_KEY
          2. Shell 配置文件(.zshrc / .bashrc / PowerShell Profile)
          3. 返回 None,由上层提示用户配置
      
          Returns:
              str or None: API Key
          """
          # 第一级:环境变量
          api_key = os.getenv("REDFOX_API_KEY")
          if api_key and api_key.startswith("ak_"):
              return api_key
      
          # 第二级:Shell 配置文件
          home = pathlib.Path.home()
          config_files = []
      
          if sys.platform == "win32":
              # PowerShell Profile
              ps_dir = home / "Documents" / "WindowsPowerShell"
              if ps_dir.exists():
                  for f in ps_dir.glob("Microsoft.PowerShell_profile.ps1"):
                      config_files.append(f)
              ps7_dir = home / "Documents" / "PowerShell"
              if ps7_dir.exists():
                  for f in ps7_dir.glob("Microsoft.PowerShell_profile.ps1"):
                      config_files.append(f)
              # Git Bash / MSYS2
              for name in [".bashrc", ".bash_profile", ".profile"]:
                  p = home / name
                  if p.exists():
                      config_files.append(p)
          else:
              for name in [".zshrc", ".bashrc", ".bash_profile", ".profile"]:
                  p = home / name
                  if p.exists():
                      config_files.append(p)
      
          for config in config_files:
              try:
                  content = config.read_text(encoding="utf-8", errors="ignore")
                  for line in content.splitlines():
                      line = line.strip()
                      # 匹配 export REDFOX_API_KEY=xxx 或 set REDFOX_API_KEY=xxx
                      if line.startswith("export REDFOX_API_KEY=") or line.startswith("set REDFOX_API_KEY="):
                          parts = line.split("=", 1)
                          if len(parts) == 2:
                              key = parts[1].strip().strip('"').strip("'")
                              if key and key.startswith("ak_"):
                                  return key
                      # 匹配 PowerShell: $env:REDFOX_API_KEY = "xxx"
                      if "$env:REDFOX_API_KEY" in line:
                          parts = line.split("=", 1)
                          if len(parts) == 2:
                              key = parts[1].strip().strip('"').strip("'")
                              if key and key.startswith("ak_"):
                                  return key
              except Exception:
                  continue
      
          return None
      
      
      def _get_api_headers():
          """获取API请求头,含X-API-KEY凭证
      
          三级回退:环境变量 → Shell配置 → 报错提示
          """
          api_key = _get_redfox_api_key()
          if not api_key:
              raise ValueError(
                  "未检测到 红狐Hub API Key。请按以下步骤配置:\n"
                  "1. 访问 https://redfox.hk/?source=github 了解服务详情\n"
                  "2. 前往 https://redfox.hk/login?source=github 注册账号(新用户获赠免费积分)\n"
                  "3. 注册登录后在个人中心获取 API Key(格式 ak_xxxxxxxx)\n"
                  "4. 设置环境变量:\n"
                  "   macOS/Linux: export REDFOX_API_KEY=<你的apikey>\n"
                  "   Windows PowerShell: $env:REDFOX_API_KEY = \"<你的apikey>\"\n"
                  "   或告知我帮你自动配置到 Shell 配置文件中"
              )
          return {
              "Content-Type": "application/json",
              "X-API-KEY": api_key,
          }
      
      
      # ===== API调用 =====
      def fetch_ranking_data(rank_type="day", rank_date=None, category="人文资讯"):
          """获取公众号热门账号推荐榜数据
      
          Args:
              rank_type: day/week/month
              rank_date: 查询日期 (yyyy-MM-dd),None则自动计算
              category: 分类名称
      
          Returns:
              dict: API返回的完整数据
          """
          if rank_date is None:
              rank_date = get_latest_query_date(rank_type)
      
          params = {
              "rankType": rank_type,
              "rankDate": rank_date,
              "category": category,
              "source": SOURCE,
          }
      
          headers = _get_api_headers()
      
          try:
              response = requests.get(API_URL, params=params, headers=headers, timeout=30)
              if response.status_code >= 400:
                  raise ValueError(f"HTTP请求失败: {response.status_code}, {response.text}")
              result = response.json()
          except requests.exceptions.RequestException as e:
              raise ValueError(f"请求失败: {str(e)}")
      
          if result.get("code") != 2000:
              raise ValueError(f"API返回错误: code={result.get('code')}, msg={result.get('msg', '未知错误')}")
      
          return result
      
      
      # ===== 分类匹配 =====
      CATEGORY_KEYWORDS = {
          "总排名": ["总排名", "综合", "全部", "热门", "推荐", "随便", "总榜", "整体"],
          "乐活生活": ["乐活", "生活", "日常", "生活方式", "生活日常", "好物推荐"],
          "人文资讯": ["人文", "资讯", "文化", "历史", "哲学", "人文社科"],
          "企业品牌": ["企业", "品牌", "公司", "商业品牌", "品牌营销"],
          "体育娱乐": ["体育", "娱乐", "运动", "健身", "篮球", "足球", "综艺", "明星"],
          "健康养生": ["健康", "养生", "保健", "中医", "调理", "减肥", "瘦身"],
          "创投商业": ["创投", "商业", "投资", "创业", "融资", "商业模式"],
          "学术研究": ["学术", "研究", "论文", "科研", "学报"],
          "情感心理": ["情感", "心理", "恋爱", "婚姻", "情绪", "心理咨询"],
          "房产楼市": ["房产", "楼市", "买房", "房价", "地产", "租房", "装修"],
          "搞笑幽默": ["搞笑", "幽默", "段子", "吐槽", "沙雕", "表情包"],
          "教育考试": ["教育", "考试", "培训", "考研", "考公", "留学", "英语", "学习"],
          "文摘精选": ["文摘", "精选", "美文", "散文", "故事", "文章精选"],
          "旅游出行": ["旅游", "出行", "旅行", "攻略", "景点", "酒店", "度假"],
          "时尚潮流": ["时尚", "潮流", "穿搭", "服饰", "OOTD", "ootd", "搭配"],
          "民生资讯": ["民生", "社会", "热点", "时事", "政策", "民生新闻"],
          "汽车交通": ["汽车", "交通", "车", "新能源", "电动车", "买车"],
          "知识百科": ["知识", "百科", "科普", "常识", "冷知识"],
          "科技数码": ["科技", "数码", "手机", "电脑", "智能", "AI", "互联网", "软件", "硬件"],
          "美容美体": ["美容", "美体", "护肤", "化妆", "美妆", "彩妆"],
          "美食餐饮": ["美食", "餐饮", "做饭", "烹饪", "餐厅", "探店", "食谱"],
          "职场发展": ["职场", "工作", "求职", "面试", "跳槽", "升职", "加薪", "简历"],
          "财富理财": ["财富", "理财", "财经", "基金", "股票", "保险", "财务", "赚钱"],
      }
      
      
      def match_category(keyword):
          """根据关键词匹配分类
      
          Args:
              keyword: 搜索关键词
      
          Returns:
              str or None: 匹配到的分类名,多个匹配时返回列表
          """
          # 精确匹配分类名
          if keyword in CATEGORIES:
              return keyword
          # 子串匹配分类名
          matches = [c for c in CATEGORIES if keyword in c]
          if len(matches) == 1:
              return matches[0]
          if len(matches) > 1:
              return matches
          # 关键词映射匹配
          keyword_lower = keyword.lower().strip()
          for category, keywords in CATEGORY_KEYWORDS.items():
              for kw in keywords:
                  if kw.lower() in keyword_lower or keyword_lower in kw.lower():
                      return category
          return None
      
      
      # ===== 数据格式化 =====
      def format_number(num):
          """格式化数字:超过1万显示x.x万,超过1亿显示x.x亿"""
          try:
              num = int(num)
          except (ValueError, TypeError):
              return str(num)
          if num >= 100000000:
              return f"{num / 100000000:.1f}亿"
          if num >= 10000:
              return f"{num / 10000:.1f}万"
          return str(num)
      
      
      def get_comprehensive_score(item):
          """获取综合评分(满分100),直接采用接口返回的comprehensiveScore字段
      
          Args:
              item: 单条数据
      
          Returns:
              float: 综合评分(0-100)
          """
          try:
              return round(float(item.get("comprehensiveScore", 0)), 1)
          except (ValueError, TypeError):
              return 0.0
      
      
      def format_ranking_table(data_list, top_n=50, start=1):
          """格式化榜单为Markdown表格(严格遵循core_workflow.md标准模板)
      
          表格字段: 排名 | 账号名称 | 综合评分(满分100) | 发布数/文章数 | 总阅读数 | 头条阅读数 | 最高阅读数 | 总点赞 | 总在看 | 总转发
      
          Args:
              data_list: API返回的data数组
              top_n: 显示前N条
              start: 起始排名(1-based),用于"查看更多"时从TOP21开始
      
          Returns:
              str: Markdown表格字符串
          """
          # start为1时从第1条开始,start为21时从第21条开始
          items = data_list[start-1:start-1+top_n]
      
          header = "| 排名 | 账号名称 | 综合评分(满分100) | 发布数/文章数 | 总阅读数 | 头条阅读数 | 最高阅读数 | 总点赞 | 总在看 | 总转发 |"
          separator = "|:---:|:---|---:|:---:|---:|---:|---:|---:|:---:|---:|"
      
          rows = []
          for idx, item in enumerate(items):
              # 使用实际索引位置判断奖牌,避免API返回的rankPosition重复
              display_rank = start + idx
              name = item.get("accountName", "-")
              account_id = item.get("accountId", "")
              name_link = f"[{name}](https://open.weixin.qq.com/qr/code?username={quote(account_id)})"
              score = get_comprehensive_score(item)
              publish = item.get("publishCount", "-")
              total_read = format_number(item.get("totalReadCount", 0))
              headline_read = format_number(item.get("headlineReadCount", 0))
              max_read = format_number(item.get("maxReadCount", 0))
              like_count = format_number(item.get("totalLikeCount", 0))
              in_see_count = format_number(item.get("totalInSeeCount", 0))
              forward_count = format_number(item.get("totalForwardCount", 0))
      
              rank_medals = {1: "🥇", 2: "🥈", 3: "🥉"}
              if display_rank in rank_medals:
                  # TOP3: 账号名称加粗,综合评分/总阅读/头条阅读/最高阅读/总点赞/总转发加粗
                  row = f"| {rank_medals[display_rank]} | **{name_link}** | **{score}** | {publish} | **{total_read}** | **{headline_read}** | **{max_read}** | **{like_count}** | **{in_see_count}** | **{forward_count}** |"
              else:
                  # 非TOP3: 账号名称不加粗,但综合评分/总阅读/头条阅读/最高阅读/总点赞/总转发仍需加粗
                  row = f"| {display_rank} | {name_link} | **{score}** | {publish} | **{total_read}** | **{headline_read}** | **{max_read}** | **{like_count}** | **{in_see_count}** | **{forward_count}** |"
              rows.append(row)
      
          return "\n".join([header, separator] + rows)
      
      
      # 功能询问文案模板(一字不改,遵循core_workflow.md)
      FEATURE_PROMPT = """技能还支持以下功能:
      
      1️⃣ 支持对以上数据进行分析。其中包含**内容生态变化分析**、**热点影响力量化**、**蓝海赛道挖掘**三种分析角度,方便您了解公众号阅读数据或获取运营参考时使用。
      
      2️⃣ 可生成公众号风格的html文件包。支持**导出图片**、**导出PDF**,方便阅读、保存和分享。
      
      3️⃣ 支持订阅每日/周/月的公众号账号最新排名,订阅后将定时推送给您(日榜将在每日17点30更新/周榜将在每周一17:30准时更新/月榜将在每月3号23:00准时推送)。直接回复"订阅日/周/月榜"即可订阅相关榜单,或者"全部订阅"将会为您推送全部类型的榜单。
      
      回复数字即可~"""
      
      
      def format_full_output(rank_label, category, update_time, data_description,
                             time_range, reminder, data_list, top_n=50, start=1):
          """格式化完整的榜单输出(严格遵循core_workflow.md标准模板)
      
          输出顺序:标题 → 数据说明 → reminder → 表格 → 功能询问文案
      
          Args:
              rank_label: 榜单标签(日榜/周榜/月榜)
              category: 分类名
              update_time: 更新时间
              data_description: 数据说明文本
              time_range: 数据统计时间周期
              reminder: 日期提醒(可为空)
              data_list: API返回的data数组
              top_n: 显示前N条
              start: 起始排名
      
          Returns:
              str: 完整的Markdown输出
          """
          parts = []
      
          # 标题(二级标题)
          parts.append(f"## 公众号热门账号推荐榜{rank_label} - {category} (更新时间:{update_time})")
          parts.append("")
      
          # 数据说明(斜体,在标题后、表格前)
          parts.append(f"*公众号综合实力{rank_label},基于阅读、点赞、转发、在看等多维数据综合排名*")
          parts.append(f"*数据统计时间周期:{time_range}*")
          parts.append("")
      
          # reminder(如有)
          if reminder:
              parts.append(reminder)
              parts.append("")
      
          # 榜单表格
          parts.append(format_ranking_table(data_list, top_n, start))
          parts.append("")
      
          # 功能询问文案(一字不改)
          parts.append(FEATURE_PROMPT)
          parts.append("")
          parts.append("另外红狐配套全量数据库可提供完整详实数据,如需了解采购方案,可发送邮件至 redfoxdata@proton.me 对接咨洵")
      
          return "\n".join(parts)
      
      
      def format_analysis(data_list, rank_type="day", top_n=3):
          """格式化综合实力分析
      
          Args:
              data_list: API返回的data数组
              rank_type: day/week/month
              top_n: 分析前N个公众号
      
          Returns:
              str: Markdown分析文本
          """
          rank_label = RANK_TYPE_MAP.get(rank_type, "日榜")
          items = data_list[:top_n]
      
          if not items:
              return "暂无数据可供分析。"
      
          lines = [f"**{rank_label}综合实力分析**\n"]
      
          for i, item in enumerate(items):
              name = item.get("accountName", "未知")
              score = get_comprehensive_score(item)
              total_read = item.get("totalReadCount", 0)
              headline_read = item.get("headlineReadCount", 0)
              max_read = item.get("maxReadCount", 0)
              like_count = item.get("totalLikeCount", 0)
              forward_count = item.get("totalForwardCount", 0)
              in_see_count = item.get("totalInSeeCount", 0)
              publish = item.get("publishCount", "-")
      
              lines.append(f"**第{i+1}名:{name}(综合评分:{score})**")
              lines.append(f"- 风格:总阅读{format_number(total_read)},头条阅读{format_number(headline_read)},最高阅读{format_number(max_read)}")
              lines.append(f"- 特征:发布{publish},获赞{format_number(like_count)}次,在看{format_number(in_see_count)}次,转发{format_number(forward_count)}次")
      
              # 分析建议
              try:
                  total_int = int(total_read) if total_read else 0
              except (ValueError, TypeError):
                  total_int = 0
              try:
                  like_int = int(like_count) if like_count else 0
              except (ValueError, TypeError):
                  like_int = 0
      
              interaction_rate = f"{like_int / total_int * 100:.2f}%" if total_int and total_int > 0 else "N/A"
              lines.append(f"- 建议:互动率{interaction_rate},{'内容传播力强,可作为竞品研究标杆' if score >= 80 else '内容稳定产出,关注互动提升空间'}")
              lines.append("")
      
          return "\n".join(lines)
      
      
      def get_update_time_label(rank_type, rank_date):
          """获取榜单更新时间标签
      
          Args:
              rank_type: day/week/month
              rank_date: 查询日期
      
          Returns:
              str: 更新时间标签
          """
          if rank_type == "day":
              # 日榜:查询日期的次日17:30
              d = datetime.strptime(rank_date, "%Y-%m-%d")
              update_time = d + timedelta(days=1)
              return f"{update_time.strftime('%Y-%m-%d')} 17:30"
          elif rank_type == "week":
              # 周榜:查询周一的下一个周一17:30
              d = datetime.strptime(rank_date, "%Y-%m-%d")
              next_monday = d + timedelta(weeks=1)
              return f"{next_monday.strftime('%Y-%m-%d')} 17:30"
          elif rank_type == "month":
              # 月榜:查询月份的次月3号23:00
              d = datetime.strptime(rank_date, "%Y-%m-%d")
              if d.month == 12:
                  update_day = datetime(d.year + 1, 1, MONTH_UPDATE_DAY)
              else:
                  update_day = datetime(d.year, d.month + 1, MONTH_UPDATE_DAY)
              return f"{update_day.strftime('%Y-%m-%d')} 23:00"
          return rank_date
      
      
      # ===== 主函数 =====
      def main():
          parser = argparse.ArgumentParser(description="公众号热门账号推荐榜数据获取工具")
          parser.add_argument("--rank_type", choices=["day", "week", "month"], default="day",
                              help="榜单类型: day=日榜, week=周榜, month=月榜 (默认: day)")
          parser.add_argument("--rank_date", type=str, default=None,
                              help="查询日期(yyyy-MM-dd),不指定则自动计算")
          parser.add_argument("--category", type=str, default=None,
                              help="分类名称,如'科技数码'")
          parser.add_argument("--keyword", type=str, default=None,
                              help="分类关键词,模糊匹配分类名")
          parser.add_argument("--top_n", type=int, default=50,
                              help="显示前N条 (默认: 50)")
          parser.add_argument("--start", type=int, default=1,
                              help="起始排名 (默认: 1,用于查看更多时从TOP21开始)")
          parser.add_argument("--list_categories", action="store_true",
                              help="列出所有可用分类")
          parser.add_argument("--raw", action="store_true",
                              help="输出原始JSON数据")
          parser.add_argument("--input_json_file", type=str, default=None,
                              help="预取数据JSON文件路径(来自本脚本之前的输出),指定后不再调用API")
      
          args = parser.parse_args()
      
          # 使用预取数据模式
          if args.input_json_file:
              if not os.path.isfile(args.input_json_file):
                  print(f"错误: 文件不存在: {args.input_json_file}", file=sys.stderr)
                  sys.exit(1)
              try:
                  with open(args.input_json_file, 'r', encoding='utf-8') as f:
                      fetched = json.load(f)
              except Exception as e:
                  print(f"错误: 读取文件失败: {e}", file=sys.stderr)
                  sys.exit(1)
      
              raw_data = fetched.get("raw_data", [])
              if not raw_data:
                  print("错误: 输入文件中无 raw_data 数据", file=sys.stderr)
                  sys.exit(1)
      
              rank_type = fetched.get("rank_type", "day")
              rank_date = fetched.get("rank_date", "")
              category = fetched.get("category", "人文资讯")
              rank_label = fetched.get("rank_label", RANK_TYPE_MAP.get(rank_type, "日榜"))
              update_time = fetched.get("update_time", "")
              reminder = fetched.get("reminder", "")
      
              print(f"使用预取数据重新处理(不调用API)...", file=sys.stderr)
              print(f"榜单类型: {rank_label}", file=sys.stderr)
              print(f"查询日期: {rank_date}", file=sys.stderr)
              print(f"分类: {category}", file=sys.stderr)
      
              # 从预取数据重新格式化
              data_list = raw_data
              top_n = min(args.top_n, len(data_list))
      
              time_range = get_data_time_range(rank_type, rank_date)
      
              output = {
                  "status": "success",
                  "rank_type": rank_type,
                  "rank_label": rank_label,
                  "rank_date": rank_date,
                  "category": category,
                  "data_description": fetched.get("data_description", ""),
                  "total_count": fetched.get("total_count", len(data_list)),
                  "top_n": top_n,
                  "update_time": update_time,
                  "reminder": reminder,
                  "ranking_table": format_ranking_table(data_list, args.top_n, args.start),
                  "analysis": format_analysis(data_list, rank_type),
                  "raw_data": data_list,
                  "formatted_markdown": format_full_output(
                      rank_label, category, update_time,
                      fetched.get("data_description", ""), time_range,
                      reminder, data_list, args.top_n, args.start
                  ),
              }
      
              # 输出JSON到stdout,确保Windows下GBK终端也能正确输出
              json_str = json.dumps(output, ensure_ascii=False)
              try:
                  sys.stdout.write(json_str + "\n")
                  sys.stdout.flush()
              except UnicodeEncodeError:
                  # Windows GBK终端无法编码emoji等字符时,回退到UTF-8编码
                  sys.stdout.buffer.write(json_str.encode('utf-8', errors='replace') + b"\n")
                  sys.stdout.buffer.flush()
              return
      
          # 列出分类
          if args.list_categories:
              print("可用分类列表:", file=sys.stderr)
              for i, cat in enumerate(CATEGORIES, 1):
                  print(f"  {i}. {cat}", file=sys.stderr)
              return
      
          # 确定分类
          category = "人文资讯"
          if args.category:
              category = args.category
          elif args.keyword:
              matched = match_category(args.keyword)
              if matched is None:
                  print(f"未找到匹配'{args.keyword}'的分类,可用分类:{', '.join(CATEGORIES)}", file=sys.stderr)
                  return
              elif isinstance(matched, list):
                  print(f"关键词'{args.keyword}'匹配到多个分类:{', '.join(matched)},请指定更精确的分类名", file=sys.stderr)
                  return
              else:
                  category = matched
                  print(f"根据关键词【{args.keyword}】匹配到分类:【{category}】", file=sys.stderr)
      
          # 确定查询日期
          rank_label = RANK_TYPE_MAP.get(args.rank_type, "日榜")
          print(f"正在获取公众号综合实力{rank_label}数据...", file=sys.stderr)
          print(f"当前时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", file=sys.stderr)
      
          reminder = ""
      
          if args.rank_date:
              # 用户指定了日期,验证范围
              validation = validate_and_adjust_date(args.rank_type, args.rank_date)
              rank_date = validation["adjusted_date"]
              if validation["is_adjusted"]:
                  reminder = validation["reminder"]
                  print(f"用户指定日期: {args.rank_date} → 调整为: {rank_date}", file=sys.stderr)
                  print(f"提醒: {reminder}", file=sys.stderr)
          else:
              # 自动计算最新可查询日期
              rank_date = get_latest_query_date(args.rank_type)
              # 检查今日日榜是否已更新
              today_reminder = get_today_query_reminder(args.rank_type)
              if today_reminder:
                  reminder = today_reminder
                  print(f"提醒: {reminder}", file=sys.stderr)
      
          print(f"榜单类型: {rank_label}", file=sys.stderr)
          print(f"查询日期: {rank_date}", file=sys.stderr)
          print(f"分类: {category}", file=sys.stderr)
          print("-" * 60, file=sys.stderr)
      
          try:
              result = fetch_ranking_data(
                  rank_type=args.rank_type,
                  rank_date=rank_date,
                  category=category,
              )
          except Exception as e:
              print(f"错误: {e}", file=sys.stderr)
              return
      
          data_list = result.get("data", [])
          if not data_list:
              print("未获取到数据", file=sys.stderr)
              return
      
          # 原始输出
          if args.raw:
              print(json.dumps(result, ensure_ascii=False, indent=2))
              return
      
          # 格式化输出
          # 综合评分直接采用接口返回的comprehensiveScore字段
          scored_data = []
          for item in data_list[:args.top_n]:
              item_copy = dict(item)
              item_copy["compositeScore"] = get_comprehensive_score(item)
              scored_data.append(item_copy)
      
          update_time = get_update_time_label(args.rank_type, rank_date)
          time_range = get_data_time_range(args.rank_type, rank_date)
      
          output = {
              "status": "success",
              "rank_type": args.rank_type,
              "rank_label": rank_label,
              "rank_date": rank_date,
              "category": category,
              "data_description": DATA_DESCRIPTION_TEMPLATE.format(rank_label=rank_label, time_range=time_range),
              "total_count": len(data_list),
              "top_n": min(args.top_n, len(data_list)),
              "update_time": update_time,
              "reminder": reminder,
              "ranking_table": format_ranking_table(data_list, args.top_n, args.start),
              "analysis": format_analysis(data_list, args.rank_type),
              "raw_data": scored_data,
              "formatted_markdown": format_full_output(
                  rank_label, category, update_time,
                  DATA_DESCRIPTION_TEMPLATE.format(rank_label=rank_label, time_range=time_range),
                  time_range, reminder, data_list, args.top_n, args.start
              ),
          }
      
          # 输出JSON到stdout,确保Windows下GBK终端也能正确输出
          json_str = json.dumps(output, ensure_ascii=False)
          try:
              sys.stdout.write(json_str + "\n")
              sys.stdout.flush()
          except UnicodeEncodeError:
              # Windows GBK终端无法编码emoji等字符时,回退到ASCII编码
              sys.stdout.buffer.write(json_str.encode('utf-8', errors='replace') + b"\n")
              sys.stdout.buffer.flush()
      
      
      if __name__ == "__main__":
          main()
      
  • README.en.md 7 KB
    # WeChat Top Accounts / wechat-top-account
    
    ---
    
    ## Introduction
    
    Quickly access the WeChat Official Account comprehensive ranking TOP 50, helping operators and brands understand the reading and engagement data of top accounts in their niche — supporting operational benchmarking and competitor tracking.
    
    **Core Value**
    
    - Covers TOP 50 rankings across 23 vertical categories, with flexible switching between daily, weekly, and monthly charts
    - A comprehensive scoring system based on multiple metrics including reads, likes, in-views, and shares, with a maximum score of 100
    - Automatically outputs three types of in-depth analysis: content ecosystem shifts, hot topic impact, and blue-ocean niche discovery
    - One-click generation of a visual report page with image and PDF export support
    - Subscribe to daily, weekly, or monthly rankings for automated delivery on each update
    
    **Who It's For**
    
    - 📱 **WeChat Official Account Operators** — Understand top account performance in your niche to optimize topic selection and publishing cadence
    - 🏢 **MCN Operations Teams** — Monitor the competitive landscape across managed accounts and generate regular data reports
    - 🏷️ **Brand Marketing Managers** — Track competitor account rankings and engagement shifts to adjust content strategy
    - 🚀 **Content Entrepreneurs** — Evaluate competition levels and reading demand across candidate niches to identify blue-ocean opportunities
    
    ---
    
    ## Features
    
    ### Core Functions
    
    - **Multi-Dimensional Ranking Query**: Query TOP 50 across 23 categories by daily, weekly, or monthly chart — keywords automatically match the right category
    - **Comprehensive Scoring System**: A composite score (max 100) based on post count, reads, likes, in-views, shares, and more
    - **In-Depth Analysis**: Three analytical perspectives: content ecosystem changes, hot topic impact quantification, and blue-ocean niche discovery
    - **Visual Report Export**: One-click generation of a visual ranking page with image and PDF export support
    - **Scheduled Subscription Push**: Subscribe to daily, weekly, or monthly rankings for automatic delivery on each update cycle
    
    ### Highlights
    
    - **Scheduled Updates**: Daily chart refreshes at 17:30, weekly chart every Monday at 17:30, monthly chart on the 3rd at 23:00
    - **Controlled Lookback**: Daily charts span the past 7 days, weekly the past 3 weeks, monthly the past 3 months — out-of-range requests auto-adjust with a prompt
    - **Data Consistency**: The ranking table and visual report share the same data source, with identical order and content
    - **On-Demand Expansion**: The ranking table outputs directly without confirmation; analysis, reports, and subscriptions are user-initiated
    
    ---
    
    ## API Key Acquisition & Security
    
    - This skill requires the environment variable: `REDFOX_API_KEY`.
    - `REDFOX_API_KEY` is provided by [RedFoxHub](https://redfox.hk/settings/api-keys?source=github) (`https://redfox.hk`).
    - Register an account at [RedFoxHub](https://redfox.hk?source=github) to obtain your `REDFOX_API_KEY`.
    - Configure the device environment variable `REDFOX_API_KEY` before using this skill.
    - Before providing a key, confirm its source, scope, expiration, and whether it supports reset/revocation.
    - Never hardcode or expose the key in plaintext in code, prompts, logs, or output files.
    
    ---
    
    ## Usage Guide
    
    Just describe your need in plain language — no commands to memorize.
    
    ### Quick Reference
    
    | Intent                          | Example Phrase                         | Result                                                                                   |
    | ------------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------- |
    | Query daily chart by category   | "Show the tech category daily ranking" | Outputs the Tech & Digital daily TOP 50 with composite scores and engagement metrics     |
    | Query weekly chart by category  | "This week's food category ranking"    | Outputs the Food & Dining weekly TOP 50 with time range noted                            |
    | Query monthly chart by category | "Finance category monthly ranking"     | Outputs the Finance monthly TOP 50                                                       |
    | Get in-depth analysis           | (After chart output) reply "1"         | Outputs three analysis types: content ecosystem, hot topic impact, and blue-ocean niches |
    | Generate visual report          | (After chart output) reply "2"         | Generates a visual page exportable as image or PDF                                       |
    | Subscribe to updates            | "Subscribe to weekly ranking"          | Sets up automated weekly delivery of the latest chart                                    |
    | Subscribe to all                | "Subscribe to all"                     | Enables daily, weekly, and monthly chart subscriptions at once                           |
    
    ### Sample Output
    
    After querying "Tech category daily ranking", you will receive:
    
    1. **Ranking Table**: Rank, account name (clickable), composite score, post count, total reads, headline reads, peak reads, total likes, total in-views, total shares
    2. **Extension Prompt**: Reply "1" for in-depth analysis, "2" for a visual report, "3" or say "Subscribe to daily/weekly/monthly" to enable scheduled delivery
    
    ---
    
    ## Use Cases
    
    | Scenario                   | Role                    | Example Query                              | Benefit                                                                                |
    | -------------------------- | ----------------------- | ------------------------------------------ | -------------------------------------------------------------------------------------- |
    | Daily operations reference | Account Operator        | "Show tech category daily ranking"         | Compare posting frequency and engagement rates to benchmark and optimize topic cadence |
    | Competitor tracking        | Brand Marketing Manager | "This week's FMCG category ranking"        | Track competitor ranking shifts and content direction to adjust your own strategy      |
    | Niche evaluation           | Content Entrepreneur    | "Compare food and travel monthly rankings" | Assess competition levels across candidate niches and pick a blue-ocean direction      |
    | Regular data reporting     | MCN Operations          | "Subscribe to monthly ranking"             | Export monthly visual reports with consistent and traceable data                       |
    
    ---
    
    ## Key Data Notes
    
    | Chart   | Update Time                | Data Range                       |
    | ------- | -------------------------- | -------------------------------- |
    | Daily   | Every day at 17:30         | Yesterday's data, past 7 days    |
    | Weekly  | Every Monday at 17:30      | Last week's data, past 3 weeks   |
    | Monthly | 3rd of each month at 23:00 | Last month's data, past 3 months |
    
    Covers 23 vertical categories, including Overall, Tech & Digital, Food & Dining, Finance, Education, Fashion, Health & Wellness, Emotional & Psychology, Career Development, and more. Just enter a keyword to automatically match the right category.
    
  • README.md 5.5 KB
    # 公众号热门账号推荐 / wechat-top-account
    
    ---
    
    ## 简介
    
    快速获取微信公众号综合实力排行榜 TOP50,帮助运营者和品牌方掌握同赛道头部账号的阅读与互动数据,支撑运营对标与竞品跟踪。
    
    **核心价值**
    
    - 覆盖 23 个垂直分类的 TOP50 榜单,支持日榜、周榜、月榜自由切换
    - 综合评分体系基于阅读、点赞、在看、转发等多项指标,满分 100 分
    - 自动输出内容生态变化、热点影响、蓝海赛道三类深度解读
    - 一键生成可视化页面报告,支持导出图片和 PDF
    - 支持订阅日榜、周榜、月榜,按更新时间自动推送
    
    **适用对象**
    
    - 📱 **公众号运营者** — 了解赛道头部账号表现,优化选题与发布节奏
    - 🏢 **MCN 机构运营** — 统一监控旗下账号竞争格局,生成定期数据汇报
    - 🏷️ **品牌营销经理** — 跟踪竞品公众号排名与互动变化,调整内容策略
    - 🚀 **内容创业者** — 评估候选赛道竞争度与阅读需求,选择蓝海方向
    
    ---
    
    ## 功能特性
    
    ### 核心功能
    
    - **多维排行查询**:按日榜、周榜、月榜查询 23 个分类的 TOP50,输入关键词自动匹配分类
    - **综合评分体系**:基于发布数、阅读量、点赞、在看、转发等多维度综合评分,满分 100
    - **深度分析解读**:提供内容生态变化、热点影响力量化、蓝海赛道挖掘三种分析视角
    - **可视化报告导出**:一键生成榜单可视化页面,支持导出图片和 PDF
    - **定时订阅推送**:支持订阅日榜、周榜、月榜,按更新时间自动推送最新数据
    
    ### 特色亮点
    
    - **定时更新**:日榜每日 17:30、周榜每周一 17:30、月榜每月 3 日 23:00 自动刷新
    - **有限回溯**:日榜近 7 天、周榜近 3 周、月榜近 3 个月,超出范围自动调整并提示
    - **数据一致**:榜单表格与可视化报告共用同一数据来源,顺序与内容完全一致
    - **按需扩展**:榜单直接输出无需确认,分析、报告、订阅由用户自主选择
    
    ---
    
    ## 密钥获取与安全说明
    
    - 本技能需要使用环境变量:`REDFOX_API_KEY`。
    - `REDFOX_API_KEY` 由 [红狐 hub](https://redfox.hk/settings/api-keys?source=github) (`https://redfox.hk?source=github`)提供。
    - 请前往 [红狐 hub](https://redfox.hk?source=github) 注册账号,获取 `REDFOX_API_KEY`。
    - 配置设备环境变量 `REDFOX_API_KEY` 后使用本技能。
    - 在提供密钥前,请先确认密钥来源、可用范围、有效期及是否支持重置/撤销。
    - 禁止在代码、提示词、日志或输出文件中硬编码/明文暴露密钥。
    
    ---
    
    ## 使用指南
    
    直接用自然语言描述需求,无需记忆命令。
    
    ### 常用说法速查
    
    | 意图           | 示例话术             | 效果                                                |
    | -------------- | -------------------- | --------------------------------------------------- |
    | 查询分类日榜   | 查询科技类公众号日榜 | 输出科技数码分类日榜 TOP50,含综合评分与互动指标    |
    | 查询分类周榜   | 本周美食类公众号排名 | 输出美食餐饮周榜 TOP50,标注数据时间周期            |
    | 查询分类月榜   | 财经类公众号月榜     | 输出财经类月榜 TOP50                                |
    | 获取深度分析   | (榜单输出后回复)1  | 基于 TOP50 输出内容生态、热点影响、蓝海赛道三类分析 |
    | 生成可视化报告 | (榜单输出后回复)2  | 生成可导出图片和 PDF 的榜单页面                     |
    | 订阅推送       | 订阅周榜             | 设置每周定时推送最新榜单                            |
    | 全部订阅       | 全部订阅             | 同时订阅日榜、周榜、月榜                            |
    
    ### 输出示例
    
    查询「科技类公众号日榜」后,你将收到:
    
    1. **榜单表格**:排名、账号名称(可点击跳转)、综合评分、发布数、总阅读、头条阅读、最高阅读、总点赞、总在看、总转发
    2. **扩展功能提示**:回复 1 获取深度分析、回复 2 生成可视化报告、回复 3 或说「订阅日/周/月榜」开启定时推送
    
    ---
    
    ## 使用场景
    
    | 场景         | 角色         | 示例问法             | 收益                                             |
    | ------------ | ------------ | -------------------- | ------------------------------------------------ |
    | 日常运营参考 | 公众号运营者 | 查询科技类公众号日榜 | 对比发布频率与互动率,明确对标账号,优化选题节奏 |
    | 竞品追踪     | 品牌营销经理 | 本周快消类公众号排名 | 跟踪竞品排名波动与内容发力方向,调整自身策略     |
    | 赛道研判     | 内容创业者   | 对比美食和旅游类月榜 | 评估候选赛道竞争度,选择蓝海方向切入             |
    | 定期数据汇报 | MCN 机构运营 | 订阅月榜             | 按月导出可视化报告,数据口径统一可追溯           |
    
    ---
    
    ## 重要数据说明
    
    | 榜单 | 更新时间        | 数据范围            |
    | ---- | --------------- | ------------------- |
    | 日榜 | 每日 17:30      | 昨日数据,近 7 天   |
    | 周榜 | 每周一 17:30    | 上周数据,近 3 周   |
    | 月榜 | 每月 3 日 23:00 | 上月数据,近 3 个月 |
    
    共覆盖 23 个垂直分类,包括总排名、科技数码、美食餐饮、财经、教育考试、时尚潮流、健康养生、情感心理、职场发展等。输入关键词即可自动匹配对应分类。
    
  • SKILL.md 13.7 KB
    ---
    name: wechat-top-account
    description: 公众号热门账号推荐快速获取微信公众号综合实力排行榜TOP50,支持按日榜/周榜/月榜及垂直领域筛选,展示综合评分以及详细数据。支持深度分析单个账号、导出HTML可视化报告、订阅日/周/月榜推送。当用户需要查询公众号实力排行、按时间或领域筛选、查看互动指标、获取榜单解读、导出图表或接收定期推送时使用。
    ---
    
    # 公众号热门账号推荐
    
    ## 简介
    
    公众号热门账号推荐是一款微信公众号综合实力排行查询工具,帮助用户快速获取同赛道头部账号的阅读与互动数据,支撑运营对标与竞品跟踪。
    
    通过对话或命令行,你可以:
    
    - 📊 查询 23 个垂直分类的 TOP50 榜单,支持日榜、周榜、月榜切换
    - 📈 查看综合评分(满分 100)及发布数、阅读分布、点赞、在看、转发等指标
    - 🔍 获取内容生态、热点影响、蓝海赛道三类数据解读
    - 📄 导出 HTML 可视化报告(支持图片/PDF)
    - 🔔 订阅日/周/月榜,按更新时间自动推送
    
    适用于公众号运营者、MCN 机构、品牌营销人员及需跟踪公众号竞争格局的内容团队。
    
    ## 功能特性
    
    ### 🎯 核心功能
    
    - **📊 多维排行查询**:按日榜、周榜、月榜查询 23 个分类 TOP50,输入关键词自动匹配分类
    - **📈 综合评分体系**:基于阅读、点赞、在看、转发等指标计算综合评分,满分 100
    - **🔍 深度分析**:提供内容生态变化、热点影响力量化、蓝海赛道挖掘三种解读视角
    - **📄 HTML 报告**:生成可视化榜单页面,支持导出图片和 PDF
    - **🔔 定时订阅**:支持订阅日榜、周榜、月榜,按更新时间自动推送
    
    ### ✨ 特色亮点
    
    - **⚡ 定时更新**:日榜每日 17:30、周榜每周一 17:30、月榜每月 3 日 23:00 自动刷新
    - **📅 有限回溯**:日榜近 7 天、周榜近 3 周、月榜近 3 个月,超出范围自动调整并提示
    - **🔗 数据一致**:榜单表格与 HTML 报告共用同一 API,顺序与内容完全一致
    - **🔒 API 接入**:通过红狐Hub API Key 调用
    
    ## 鉴权
    
    ### 获取 API Key
    
    1. 访问 [红狐Hub 官网](https://redfox.hk/?source=github) 了解服务详情
    2. 前往 [注册页面](https://redfox.hk/login?source=github) 注册账号
    3. **新注册用户将获赠免费积分**,可立即开始使用 API 服务
    4. 注册登录后,在个人中心获取 API Key,格式为 `ak_xxxxxxxx`
    
    ### 配置 API Key
    
    `REDFOX_API_KEY` 从环境变量获取,格式 `ak_xxxxxxxx`。脚本采用三级认证回退:
    
    | 优先级 | 来源 | 说明 |
    |---|---|---|
    | 1 | 环境变量 `REDFOX_API_KEY` | 直接读取当前会话的环境变量 |
    | 2 | Shell 配置文件 | 自动读取 `~/.zshrc` / `~/.bashrc` / `~/.bash_profile` / PowerShell Profile 中的配置 |
    | 3 | 用户输入 | 若以上均未检测到,提示用户手动配置 |
    
    **macOS / Linux**:
    
    ```bash
    echo 'export REDFOX_API_KEY=<你的apikey>' >> ~/.zshrc
    source ~/.zshrc
    ```
    
    **Windows**(PowerShell):
    
    ```powershell
    [Environment]::SetEnvironmentVariable("REDFOX_API_KEY", "<你的apikey>", "User")
    ```
    
    或将以下内容写入 PowerShell Profile:
    
    ```powershell
    $env:REDFOX_API_KEY = "<你的apikey>"
    ```
    
    配置后需重启终端。验证:`echo $REDFOX_API_KEY`(macOS/Linux)或 `$env:REDFOX_API_KEY`(Windows PowerShell)。
    
    ## 一键安装
    
    ### 前置条件
    
    - Python 3.8 及以上版本
    - `requests` 库:`pip install requests`
    - 红狐Hub API Key,格式 `ak_xxxxxxxx`
    
    ### 安装方式
    
    #### SkillHub
    
    1. 前往 [SkillHub](https://skillhub.cn)
    2. 搜索 **wechat-top-account** 或 **公众号热门账号推荐**
    3. 安装说明
    
    - 可通过「通过对话安装」方式安装
    - 如未安装 SkillHub 商店,先安装商店再安装 skill
    - 支持下载 Zip 包到本地,通过本地导入方式安装
    
    #### ClawHub
    
    1. 前往 [ClawHub](https://clawhub.ai)
    2. 搜索 **wechat-top-account** 或 **公众号热门账号推荐**
    3. 安装说明
    
    - 支持使用 CLI 命令安装:`openclaw skills install wechat-top-account` 或 `clawhub install wechat-top-account`
    - 支持下载 skill 包到本地并导入安装
    - 安装成功后可在对应客户端直接使用
    
    安装完成后,请先完成 [鉴权](#鉴权) 配置 API Key,重启终端即可在对话中发起查询。
    
    #### 方式二:命令行独立使用
    
    无需安装技能包,配置好 API Key 后可直接运行 `scripts/` 下脚本:
    
    ```bash
    pip install requests
    python scripts/gzh_growth_fetcher.py --list_categories
    ```
    
    ### 环境变量配置
    
    | 变量名 | 必填 | 说明 |
    |---|---|---|
    | `REDFOX_API_KEY` | 是 | 红狐Hub API 访问密钥,格式 `ak_xxxxxxxx` |
    
    详细获取与配置步骤见上方 [鉴权](#鉴权) 章节。
    
    ## 使用指南
    
    ### 基础使用
    
    #### 1. 发起榜单查询
    
    在对话中输入查询需求,系统自动识别榜单类型和分类:
    
    > 用户:查询科技类公众号日榜
    >
    > 助手:输出科技数码分类日榜 TOP50 表格(含综合评分、阅读与互动指标),并附功能询问文案。
    
    #### 2. 查看榜单结果
    
    榜单数据直接输出,无需确认。每条结果包含排名、账号名称(可跳转)、综合评分、发布数/文章数、总阅读、头条阅读、最高阅读、总点赞、总在看、总转发。
    
    > 用户:本周美食类公众号排名
    >
    > 助手:输出美食餐饮周榜 TOP50,标注数据时间周期与更新时间。
    
    #### 3. 选择扩展功能
    
    榜单输出后,可按需选择后续服务:
    
    > 用户:1
    >
    > 助手:基于 TOP50 数据输出内容生态变化、热点影响、蓝海赛道三类分析。
    
    回复 `1` 获取分析、`2` 生成 HTML 报告、`3` 或「订阅日/周/月榜」开启定时推送。
    
    ### 高级使用
    
    #### 1. 指定分类与日期
    
    通过 `--category` 或 `--keyword` 精确匹配分类,`--rank_date` 指定查询日期(超出范围自动调整):
    
    ```bash
    # 首次查询时必须同时保存到临时文件(唯一一次API调用)
    python scripts/gzh_growth_fetcher.py --rank_type week --category "美食餐饮" --top_n 50 > /tmp/gzh_result.json 2>/dev/null
    python scripts/gzh_growth_fetcher.py --rank_type month --keyword "财经" --rank_date 2026-04-01 --top_n 50 > /tmp/gzh_result.json 2>/dev/null
    ```
    
    #### 2. 生成 HTML 可视化报告
    
    用户选择 HTML 功能后,使用已保存的数据生成可导出图片/PDF 的页面(不调用API):
    
    ```bash
    python scripts/gen_gzh_html.py --input_json_file /tmp/gzh_result.json --top 50
    ```
    
    #### 3. 获取深度分析解读
    
    用户确认后,基于当前 TOP50 全量数据输出三段式分析:内容生态变化、热点影响力量化、蓝海赛道挖掘。
    
    #### 4. 订阅定时推送
    
    用户回复「订阅日榜」「订阅周榜」「订阅月榜」或「全部订阅」,系统将按对应更新时间推送最新榜单。
    
    完整参数、输出模板与日期校验逻辑见 [核心工作流程](references/core_workflow.md)。
    
    ### 常用命令速查
    
    | 命令 | 功能 |
    |---|---|
    | `python scripts/gzh_growth_fetcher.py --rank_type day --keyword "科技" --top_n 50 > /tmp/gzh_result.json 2>/dev/null` | 科技类日榜 TOP50(首次查询,同时保存文件) |
    | `python scripts/gzh_growth_fetcher.py --rank_type week --category "美食餐饮" --top_n 50 > /tmp/gzh_result.json 2>/dev/null` | 美食餐饮周榜 TOP50(首次查询,同时保存文件) |
    | `python scripts/gzh_growth_fetcher.py --rank_type month --keyword "财经" --top_n 50 > /tmp/gzh_result.json 2>/dev/null` | 财经类月榜 TOP50(首次查询,同时保存文件) |
    | `python scripts/gzh_growth_fetcher.py --list_categories` | 列出全部可用分类 |
    | `python scripts/gen_gzh_html.py --input_json_file /tmp/gzh_result.json --top 50` | 用预取数据生成 HTML(不调用API) |
    | `python scripts/gzh_growth_fetcher.py --input_json_file /tmp/gzh_result.json` | 用预取数据重新分析(不调用API) |
    
    ## 使用场景
    
    ### 场景一:日常运营参考
    
    **角色**:公众号运营者
    
    **需求**:了解所属赛道头部账号的内容产出与互动表现
    
    **使用方式**:
    
    1. 查询所属分类日榜或周榜
    2. 对比发布频率、单篇阅读与互动率
    3. 选择分析功能提取内容策略参考
    
    **预期收益**:明确对标账号,优化选题与发布节奏
    
    ---
    
    ### 场景二:竞品追踪
    
    **角色**:品牌营销经理
    
    **需求**:跟踪竞品公众号排名与互动变化
    
    **使用方式**:
    
    1. 按周查询竞品所在分类榜单
    2. 关注排名波动与阅读/转发变化
    3. 订阅周榜定时接收更新
    
    **预期收益**:及时掌握竞品内容发力方向,调整自身策略
    
    ---
    
    ### 场景三:赛道研判
    
    **角色**:内容创业者
    
    **需求**:评估候选赛道的竞争度与阅读需求
    
    **使用方式**:
    
    1. 查询多个候选分类的月榜数据
    2. 选择蓝海赛道分析功能
    3. 对比上榜账号数量与平均阅读/评分
    
    **预期收益**:选择竞争度低、需求旺盛的方向切入
    
    ---
    
    ### 场景四:定期数据汇报
    
    **角色**:MCN 机构运营人员
    
    **需求**:为内部复盘或客户汇报提供可视化数据
    
    **使用方式**:
    
    1. 订阅日榜和周榜保持数据更新
    2. 按月导出 HTML 报告(图片/PDF)
    3. 结合分析功能撰写复盘结论
    
    **预期收益**:提升汇报效率,数据口径统一可追溯
    
    ## 项目架构
    
    ### 目录结构
    
    ```
    wechat-top-account/
    ├── SKILL.md                      # 产品说明文档
    ├── references/
    │   └── core_workflow.md          # 核心工作流程(模板、逻辑、步骤)
    ├── scripts/
    │   ├── gzh_growth_fetcher.py     # 榜单数据抓取
    │   └── gen_gzh_html.py           # HTML 可视化报告生成
    └── assets/                       # 静态资源
    ```
    
    ### 技术栈
    
    | 项目 | 说明 |
    |---|---|
    | 运行环境 | Python 3.8+ |
    | 核心依赖 | `requests` |
    | 数据来源 | 红狐Hub API |
    | 部署方式 | SkillHub / ClawHub / 命令行脚本 |
    
    ### 核心模块
    
    | 模块 | 职责 |
    |---|---|
    | `gzh_growth_fetcher.py` | 通过 红狐Hub API 获取榜单数据,支持关键词匹配分类、日期校验与三级认证回退 |
    | `gen_gzh_html.py` | 将榜单数据渲染为可导出图片和 PDF 的 HTML 页面 |
    | `core_workflow.md` | 输出格式模板、Checklist、日期计算逻辑、完整操作步骤 |
    
    ### 数据更新规则
    
    | 榜单 | 更新时间 | 数据范围 |
    |---|---|---|
    | 日榜 | 每日 17:30 | 昨日数据,近 7 天 |
    | 周榜 | 每周一 17:30 | 上周数据,近 3 周 |
    | 月榜 | 每月 3 号 23:00 | 上月数据,近 3 个月 |
    
    ## 常见问答
    
    ### 安装相关问题
    
    **Q1:未检测到 API Key 怎么办?**
    
    按 [鉴权](#鉴权) 章节步骤注册 红狐Hub 并配置 `REDFOX_API_KEY` 环境变量。新注册用户有免费积分。
    
    **Q2:如何验证配置已生效?**
    
    执行 `echo $REDFOX_API_KEY`,确认输出格式为 `ak_xxxxxxxx`。若未生效,检查终端是否已重启。
    
    **Q3:支持哪些部署方式?**
    
    支持 SkillHub、ClawHub 平台安装,也可直接运行 `scripts/` 下 Python 脚本独立调用。
    
    ---
    
    ### 使用相关问题
    
    **Q4:支持哪些分类?**
    
    共 23 个分类:总排名、乐活生活、人文资讯、企业品牌、体育娱乐、健康养生、创投商业、学术研究、情感心理、房产楼市、搞笑幽默、教育考试、文摘精选、旅游出行、时尚潮流、民生资讯、汽车交通、知识百科、科技数码、美容美体、美食餐饮、职场发展、财富理财。
    
    **Q5:数据可回溯多久?**
    
    日榜近 7 天、周榜近 3 周、月榜近 3 个月。超出范围时系统自动调整至最近可用数据。
    
    **Q6:榜单与 HTML 报告数据是否一致?**
    
    两者共用同一 API 接口,数据完全一致。
    
    **Q7:数据多久更新一次?**
    
    日榜每日 17:30、周榜每周一 17:30、月榜每月 3 日 23:00 更新。
    
    ---
    
    ### 故障排除
    
    **Q8:查询返回空数据?**
    
    确认分类名称准确(可用 `--list_categories` 列出全部分类),检查查询日期在有效范围内。
    
    **Q9:HTML 生成失败?**
    
    确认已安装 `requests` 库且 API Key 有效,查看脚本错误输出定位原因。
    
    ---
    
    ### 安全与许可
    
    **Q10:API Key 如何保管?**
    
    `REDFOX_API_KEY` 仅通过环境变量读取,请勿写入代码仓库或公开分享。Key 泄露可能导致积分被消耗。
    
    **Q11:数据来源与使用范围?**
    
    榜单数据由 红狐Hub 平台提供,仅供运营分析、竞品跟踪等合法商业用途,请遵守平台服务条款。
    
    ## 核心工作流程
    
    输出格式模板、数据校验规则、日期计算逻辑、操作步骤及使用示例详见 [references/core_workflow.md](references/core_workflow.md)。
    
    执行任务时须严格遵循 core_workflow.md 中的:
    
    - 格式优先级声明与分阶段输出规则
    - 输出前必检 Checklist
    - 榜单标准模板与按需输出模板
    - 榜单类型判断与分类匹配规则
    - 日榜 / 周榜 / 月榜日期校验逻辑
    - 数据获取与格式化输出流程
    - API 调用次数上限(同一轮查询仅允许 1 次 API 调用)
    
    ## 注意事项
    
    - 热门账号推荐榜直接输出,无需确认;分析、HTML、订阅须用户选择后方可生成
    - 榜单表格与 HTML 报告须使用同一数据源,禁止修改接口返回顺序
    - **同一轮查询(榜单+分析+HTML)全过程只允许调用 1 次 API 接口**,首次获取数据时必须同时保存到临时文件,后续操作一律使用 `--input_json_file` 读取,禁止重复调用 API
    - 未收到明确查询指令时,禁止自动调用脚本或输出榜单内容
    - 禁止折叠输出
    - 脚本输出的 `formatted_markdown` 字段已包含完整的标准模板格式,直接原样输出即可,禁止自行拼装或修改格式
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related