
Wechat Original Hot
- 233 installs
- 316 repo stars
- Updated August 4, 2026
- redfox-data/redfox-community
Use wechat-original-hot for development tasks
About
wechat-original-hot: A skill for development. This provides functionality for development workflows.
- wechat-original-hot
Wechat Original Hot by the numbers
- 233 all-time installs (skills.sh)
- +15 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #1,692 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/redfox-data/redfox-community --skill wechat-original-hotAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 233 |
|---|---|
| repo stars | ★ 316 |
| Last updated | August 4, 2026 |
| Repository | redfox-data/redfox-community ↗ |
What it does
Use wechat-original-hot for development tasks
Files
公众号热门原创文章
1. 简介
一句话定位:公众号原创爆文推荐工具,全网持续收录每日公众号原创热门文章,支持按分类和日期筛选,提供HTML可视化报告和每日订阅推送。
核心价值:
- 聚焦原创:专注收录公众号原创爆款文章,区别于转载/洗稿内容
- 灵活查询:支持分类查询、日期查询、时间范围查询等多种方式
- 完整数据输出:对话中逐条展示所有文章,禁止截断省略
适用对象:公众号内容创作者、原创写手、爆文研究者、自媒体运营。
重要提示:本Skill必须调用脚本获取数据,禁止AI自行生成任何内容。
2. 功能特性
核心功能
| 功能模块 | 能力描述 | 核心价值 |
|---|---|---|
| 📊 原创爆文榜单 | 获取公众号原创热门文章TOP50 | 专注原创内容,洞察创作趋势 |
| 🏷️ 分类筛选 | 23个标准分类独立查询 | 精准定位垂直领域原创爆文 |
| 📅 日期回溯 | 支持指定日期查询,默认回溯30天 | 灵活查询历史数据 |
| 🎨 HTML可视化报告 | 微信绿主题设计,支持PDF导出 | 便于分享存档和数据汇报 |
| ⏰ 订阅推送 | 每日19:30定时推送 | 持续追踪原创爆文动态 |
特色亮点
- 强制脚本调用:禁止AI自行生成内容,确保数据真实性
- 逐条完整展示:limit=20就必须逐条输出全部20条,严禁截断或省略
- 智能时间判断:根据数据同步时间(19:30)自动计算正确查询区间
- 分类泛化匹配:支持模糊关键词自动匹配到23个标准分类
3. 一键安装
鉴权
获取 API Key
请前往 红狐hub 获取API KEY
配置 API Key
方案1: 以OpenClaw为例,将REDFOX_API_KEY添加到~/.openclaw/openclaw.json中,部分内容如下:
{ "env": { "REDFOX_API_KEY": "ak_xxxx..." } }方案2: 终端配置:
export REDFOX_API_KEY="ak_xxxx..."依赖安装
无需额外安装,脚本使用Python标准库(json、argparse、os、sys、urllib、ssl)。
环境变量配置
| 变量名 | 说明 | 必填 |
|---|---|---|
REDFOX_API_KEY | 红狐 API Key | 是 |
4. 使用指南
基础使用
启动提示
Skill 加载后提示用户:「技能已加载完成。是否为您推送最新的原创爆款文章?」
查询总榜原创爆文
python scripts/fetch_articles.py --type "总排名" --limit 20或不传 --type,默认为"总排名"。输出最新原创爆款文章Top20。
分类查询
python scripts/fetch_articles.py --type "科技数码" --limit 20分类名称从用户输入中匹配,参考 分类映射表。
日期查询
python scripts/fetch_articles.py --type "总排名" --start_date "2026-05-03" --limit 20脚本会自动计算 endDate = startDate + 1天。
高级使用
用户意图识别
智能体首先识别用户意图类型:
| 意图类型 | 触发词 | 处理方式 |
|---|---|---|
| 直接查询 | "今日爆文"、"原创爆文"、"最新爆文" | 输出最新原创爆款Top20 |
| 分类查询 | "科技"、"健康养生"、"财经" | 匹配分类,输出该分类Top20 |
| 日期查询 | "5月3日的原创爆款"、"昨日爆文" | 输出指定日期爆文Top20 |
| 时间范围 | "最近"、"最新"(无明确时间) | 默认查询过去7天,输出Top20 |
| 模糊输入 | 输入不明确 | 直接推送今日原创热门文章 |
数据时间说明
- 数据同步时间:每日 19:30 同步
- 同步数据范围:前一天(T-1)的数据
- 查询数据范围:
- 当前时间 < 19:30:查询 T-2 数据(前天)
- 当前时间 >= 19:30:查询 T-1 数据(昨天)
- 默认查询时间:用户提到"最近"、"最新"等无明确时间说法时,默认查询过去7天
接口信息
- 接口URL:
https://redfox.hk/story/api/cozeSkill/getWxDataByCategoryAndTime - 请求方式:POST(JSON body)
- 认证方式:X-API-KEY header,值从
REDFOX_API_KEY环境变量获取 - 参数:
type:分类名称(如"科技数码"、"总排名"等)startDate:开始日期(YYYY-MM-DD格式)endDate:结束日期(YYYY-MM-DD格式)source:"公众号文章原创之王"(固定值)
核心输出规则(必须遵守)
- 必须调用脚本:智能体必须调用
fetch_articles.py脚本,不能自己生成文章内容 - 禁止输出脚本路径或代码:智能体不得向用户展示任何脚本路径、代码内容或执行命令
- 必须原样展示脚本输出:脚本stdout输出内容原样展示,不得省略、修改、重新格式化
- 禁止截断或省略:
limit=20就必须逐条输出全部20条,严禁使用"..."只展示前N条 - 禁止修改数据:不得修改、重新格式化、美化或隐藏脚本输出的任何部分
- 脚本输出即最终输出:脚本输出内容就是展示给用户的最终内容
- 询问部分不可省略:脚本输出的"共获取到XX条..."和订阅服务必须完整展示
输出格式
文章概览表格:
| 序号 | 作者 | 标题 | 阅读数 |
|------|------|------|--------|
| 1 | [夜听](https://open.weixin.qq.com/qr/code?username=yetingfm) | [【夜听】有一种感情,叫不再联系](https://mp.weixin.qq.com/s?...) | 10w+ |输出示例(最新一期数据,时间不一致需提示):
💡 数据说明
公众号原创文章推荐将在每日19点30分准时更新昨日文章数据,以下数据为获取时间时的快照,和实时数据有所差别。
非常抱歉🙏,最新的是前天的数据,我将为您查询最接近您需求的2026年05月12日原创文章。
📊 原创爆文推荐
最近30天的原创爆款文章
| 序号 | 作者 | 标题 | 阅读数 |
|------|------|------|--------|
| 1 | [作者A](链接) | [文章标题](链接) | 10w+ |
...
导出功能:输出生成HTML榜单页面,表格内容支持导出 PDF 格式,自动生成并打开
共获取到50条爆款原创热门文章,当前展示前20条。
📬 订阅服务
是否需要订阅具体赛道的账号表现?我们支持:
人文资讯、知识百科、健康养生、时尚潮流、美食餐饮、乐活生活、旅游出行、搞笑幽默、情感心理、体育娱乐、美容美体、文摘精选、民生资讯、财富理财、科技数码、创投商业、汽车交通、房产楼市、职场发展、教育考试、学术研究、企业品牌、总排名
订阅推送 — 每天19点30分推送最新公众号原创文章
暂不需要 — 仅本次查询时间不一致提示规则
只要数据查询日期和用户询问日期不一致,就必须在数据说明后显示提示信息:
- 查询日期 > 最新日期:
非常抱歉🙏,最新的是{查询日期}的数据,我将为您查询最接近您需求的{查询日期}原创文章。 - 查询日期 < 回溯日期:
非常抱歉🙏,目前最多支持回溯「过去30天」(最早至{回溯日期}),我将为您查询最接近您需求的时间范围~
查询"最近7天"(查询范围超出最新数据)
💡 数据说明
公众号原创文章推荐将在每日19点30分准时更新昨日文章数据,以下数据为获取时间时的快照,和实时数据有所差别。
非常抱歉🙏,最新的是前天的数据,我将为您查询最接近您需求的时间范围。
📊 原创爆文推荐
2026年05月06日至2026年05月12日的原创爆款文章HTML页面生成
在对话中完整输出所有文章数据后,自动生成HTML页面:
python scripts/generate_hot_html.py --temp_file temp_articles.json --output "原创爆文_榜单.html" --display_count 20HTML视觉规范:
- 主色:
rgb(0, 179, 84)(微信绿) - 辅助色:
#0088ff(阅读数、用户名称) - 序号1-3使用🥇🥈🥉,4往后使用数字
- 用户名称可点击跳转公众号名片(
https://open.weixin.qq.com/qr/code?username={accountId}) - PDF导出:自适应单页模式,
pagebreak: { mode: 'none' }
生成前置条件:生成HTML前必须自检对话回复中是否已逐条展示了 --limit 指定数量的文章。
订阅服务
主动询问用户是否需要订阅特定分类。用户选择订阅后,每天19:30推送最新一期原创爆文数据。
命令速查表
| 场景 | 命令示例 |
|---|---|
| 总榜查询 | python scripts/fetch_articles.py --type "总排名" --limit 20 |
| 分类查询 | python scripts/fetch_articles.py --type "科技数码" --limit 20 |
| 日期查询 | python scripts/fetch_articles.py --type "总排名" --start_date "2026-05-03" --limit 20 |
| 生成HTML | python scripts/generate_hot_html.py --temp_file temp_articles.json --output "原创爆文_榜单.html" --display_count 20 |
5. 使用场景
场景一:原创作者学习爆文写法
角色:公众号原创写手 需求:研究近期原创爆款文章的标题、选题和结构,提升写作水平 使用方式:查询全领域或特定领域原创爆文Top20,逐条阅读分析 预期收益:学习头部原创文章的成功模式,提升自身内容原创力和吸引力
场景二:垂直领域内容对标
角色:垂直领域公众号运营 需求:了解本领域原创爆文的最新趋势和竞争格局 使用方式:按分类查询,如"健康养生",输出该领域Top20原创爆文 预期收益:精准对标同领域头部内容,掌握领域内容风向
场景三:历史爆文回顾研究
角色:内容策略分析师 需求:研究某个特定时间段(如节假日)的原创爆文特征 使用方式:使用日期查询功能,指定具体日期获取当日原创爆文 预期收益:发现时间周期性的爆文规律,制定更精准的内容日历
场景四:每日订阅持续追踪
角色:自媒体创业者 需求:持续追踪原创爆文动态,不错过任何内容趋势变化 使用方式:订阅每日推送,每天19:30自动获取最新原创爆文 预期收益:保持对公众号原创内容生态的持续感知,及时调整创作方向
6. 项目架构
目录结构
wechat-original-hot/
├── SKILL.md # 技能描述文件
├── scripts/
│ ├── fetch_articles.py # 原创爆文数据获取脚本
│ └── generate_hot_html.py # HTML页面生成脚本
└── references/
└── category_mapping.md # 分类映射表技术栈
| 组件 | 技术 | 说明 |
|---|---|---|
| 脚本语言 | Python 3 | 使用标准库(json, argparse, os, sys, urllib, ssl) |
| 数据接口 | Redfox API (POST) | https://redfox.hk/story/api/cozeSkill/getWxDataByCategoryAndTime |
| 认证方式 | X-API-KEY | 请求头认证 |
| 报告模板 | HTML/CSS/JS | 微信绿主题风格 |
| PDF导出 | html2pdf.js | 自适应单页模式 |
核心模块说明
| 模块 | 功能 |
|---|---|
fetch_articles.py | 获取原创爆款文章数据,支持分类查询和日期查询,保存到临时JSON文件 |
generate_hot_html.py | 从临时JSON文件读取数据,生成公众号风格HTML页面 |
category_mapping.md | 分类映射表,包含22个标准分类和泛化关键词 |
资源索引
- 脚本: 见 scripts/fetch_articles.py(用途: 获取原创爆款文章数据,支持分类查询和日期查询)
- 脚本: 见 scripts/generate_hot_html.py(用途: 从临时JSON文件读取数据,生成公众号风格HTML页面)
- 参考: 见 references/category_mapping.md(用途: 分类映射表,包含22个标准分类和泛化关键词)
7. 常见问答
安装相关
Q: 脚本运行报错 "REDFOX_API_KEY not found" A: 请确保已配置环境变量 REDFOX_API_KEY,可参考上方鉴权章节配置。
Q: 是否需要安装第三方依赖? A: 不需要。脚本使用Python标准库(json, argparse, os, sys, urllib, ssl),无需额外安装。
使用相关
Q: 为什么查询"今日"的文章返回的是前天的? A: 数据每日19:30同步前一天数据。如果在19:30之前查询,前一天数据尚未同步,系统会自动查询前天的数据并给出提示。
Q: 对话中输出的文章数量为什么和limit参数不一致? A: 本技能强制要求 limit=20 就必须逐条输出全部20条。如发现缺少文章,说明智能体执行出错,需要重新调用脚本。
Q: 和 wechat-10w-hot 有什么区别? A: wechat-original-hot 专注原创爆款文章(对应来源"公众号文章原创之王"),wechat-10w-hot 收录所有10w+阅读文章(包含转载和非原创内容)。
Q: 如何按日期查询? A: 使用 --start_date 参数,如 --start_date "2026-05-03"。脚本会自动计算 endDate = startDate + 1天。
故障排除
Q: 分类查询匹配不到正确分类? A: 请参考 references/category_mapping.md 中的分类映射表。如果用户输入的关键词不在映射表中,可以提示用户从23个标准分类中选择。
Q: HTML生成后文章数量不对? A: 使用 --display_count 参数确保HTML展示数量与对话中一致。生成前必须自检对话中展示的文章数量。
Q: 查询返回空数据? A: 可能原因:1) 该分类在查询时间段内无原创爆文;2) 查询日期超出30天回溯范围;3) 查询日期早于最早可用数据。系统会给出相应提示。
WeChat Original Viral Articles / wechat-original-hot
---
Introduction
Quickly find original viral articles on WeChat Official Accounts — get creative inspiration and stay on top of content trends.
Core Value
Continuously indexes daily original viral WeChat Official Account articles across the web, updated daily at 19:30 with yesterday's data. Query high-read original articles by 22 track categories and date range. Quickly find quality original benchmarks in each niche, reference peer hot content ideas with ease — a one-stop solution for daily topic research.
Who It's For
- 📝 WeChat editors / operators — Find topics and benchmarks
- 📊 Content planners — Filter original viral rankings by category
- 📈 Operations leads — Review by specific date + PDF export
---
Core Capabilities
- Original viral articles by category: 22 track categories with colloquial terms auto-mapped to standard names for precise original viral discovery
- Flexible time filtering: Query by specific date or default to the past 7 days with "recent / latest"
- One-click ranking page: Auto-generates an HTML ranking page exportable to PDF after each table output
- Niche subscription push: Subscribe by track for daily 19:30 pushes of latest original viral data
Highlights
- Rankings only: Outputs original viral article data only — no auto-generated viral pattern analysis or writing advice
- Honest data timing: "Latest available date" differs before vs after 19:30 — fixed messaging explains this
- Low-volume niches: When a track has too few results, prompts to switch category or widen the time range
---
API Key Acquisition & Security
- This skill requires the environment variable:
REDFOX_API_KEY. REDFOX_API_KEYis provided by RedFoxHub (https://redfox.hk).- Register at RedFoxHub to obtain your
REDFOX_API_KEY. - Configure
REDFOX_API_KEYas a device environment variable before using this skill. - Before providing your key, confirm its source, available scope, validity period, and whether reset/revocation is supported.
- Do not hard-code or expose the key in plaintext in code, prompts, logs, or output files.
---
Usage Guide
Describe what you need in plain language — no fixed commands to memorize.
Quick phrase reference
| What you say | What you get |
|---|---|
| "Latest original viral articles", "today's originals", "viral recommendations" | Overall Top 20 table + data note + subscription prompt + HTML |
| "Tech / health / finance… original viral articles" | Matched category Top 20 + HTML |
| "May 3 original viral articles", "yesterday's viral posts" | Query by date + table + HTML |
| "Recent / latest original hot articles" | Default past 7 days Top 20 |
Sample output
💡 Data note
WeChat original article recommendations update daily at 19:30 with yesterday's data. The data below is a snapshot at fetch time and may differ from real-time figures.
📊 Original viral article recommendations
| # | Author | Title | Reads |
|---|---|---|---|
| 1 | Author A | Article title | 10w+ |
| 2 | Author B | Article title | 10w+ |
| … | … | … | … |
50 viral original hot articles found; showing first 20.
📬 Subscription
Subscribe to a specific niche? We support subscriptions across 22 track categories.
1️⃣ Subscribe — Daily push of latest WeChat original articles at 19:30 2️⃣ Not now — This query only
---
Use Cases
| Scenario | Role | Example question | Benefit |
|---|---|---|---|
| Daily original topics | WeChat editor | "Latest original viral articles" | Latest original hot samples + shareable HTML |
| Niche benchmarking | Content planner | "Tech original viral articles" | Top ranking filtered by category |
| Date-specific review | Operations lead | "May 3 original viral articles" | Original viral list for that day + PDF export |
| Subscription push | Regular ops rhythm | "Subscribe to finance original articles" | Daily scheduled original viral pushes |
---
Important data notes
Update schedule and lookback
| Query type | Update time | Lookback | Default range |
|---|---|---|---|
| Original viral query | Daily 19:30 — previous day's data | Past 30 days | Past 7 days |
Supported niches (22)
Humanities & news, knowledge encyclopedia, health & wellness, fashion, food & dining, lifestyle, travel, humor, emotions & psychology, sports & entertainment, beauty, digest picks, civic news, wealth & finance, tech & digital, venture & business, automotive, real estate, career development, education & exams, academic research, corporate brands
Data freshness
- Data is a snapshot at fetch time — may differ from live read counts
- "Latest available date" differs before vs after 19:30
- When a niche returns fewer than 10 articles, prompts to extend to 30 days or view the overall ranking
---
公众号热门原创文章 / wechat-original-hot
---
简介
帮你快速找到公众号平台原创爆款文章,获取创作灵感,把握内容趋势。
核心价值
全网持续收录每日公众号原创爆款文章,每日 19:30 更新昨日数据。支持按 22 个赛道分类和日期范围查询高阅读原创文章。快速查找各赛道原创优质对标内容,轻松参考同行热门创作思路,一站式满足日常选题参考需求。
适用对象
- 📝 公众号编辑 / 运营 — 找选题、看对标
- 📊 内容策划 — 按分类筛选原创爆款榜单
- 📈 运营负责人 — 指定日复盘 + PDF 导出
---
核心功能
- 原创爆文分类速查:支持 22 个赛道分类,口语自动映射标准分类名,精准定位原创爆款
- 时间维度灵活筛选:支持指定日期或「最近 / 最新」默认近 7 天范围查询
- 榜单页面一键生成:每次输出表格后自动生成可导出 PDF 的 HTML 榜单页
- 赛道订阅定时推送:按赛道订阅,每日 19:30 推送最新原创爆文数据
特色亮点
- 只做榜单:仅输出原创爆款文章数据,不自行生成爆文规律分析或写作建议
- 数据诚实:当前时间早于 19:30 与晚于 19:30,「最新可查日期」不同,固定话术说明
- 冷门提示:某赛道数据过少时,提示可换分类或扩大时间范围
---
密钥获取与安全说明
- 本技能需要使用环境变量:
REDFOX_API_KEY。 REDFOX_API_KEY由 红狐 hub (https://redfox.hk)提供。- 请前往 红狐 hub 注册账号,获取
REDFOX_API_KEY。 - 配置设备环境变量
REDFOX_API_KEY后使用本技能。 - 在提供密钥前,请先确认密钥来源、可用范围、有效期及是否支持重置/撤销。
- 禁止在代码、提示词、日志或输出文件中硬编码/明文暴露密钥。
---
使用指南
直接用自然语言说出你的需求即可,无需记忆固定命令。
常用说法速查
| 你的说法 | 大致会得到什么 |
|---|---|
| 「最新原创爆文」「今日原创文章」「爆文推荐」 | 总榜 Top20 表格 + 数据说明 + 订阅问句 + HTML |
| 「科技 / 健康 / 财经…类原创爆款」 | 匹配分类后 Top20 + HTML |
| 「5 月 3 日的原创爆款」「昨日爆文」 | 指定日期查询 + 表格 + HTML |
| 「最近 / 最新有什么原创热门」 | 默认近 7 天范围 Top20 |
输出示例
💡 数据说明
公众号原创文章推荐将在每日 19:30 准时更新昨日文章数据,以下数据为获取时间时的快照,和实时数据有所差别。
📊 原创爆文推荐
| 序号 | 作者 | 标题 | 阅读数 |
|---|---|---|---|
| 1 | 作者 A | 文章标题 | 10w+ |
| 2 | 作者 B | 文章标题 | 10w+ |
| … | … | … | … |
共获取到 50 条爆款原创热门文章,当前展示前 20 条。
📬 订阅服务
是否需要订阅具体赛道的账号表现?我们支持 22 个赛道分类订阅。
1️⃣ 订阅推送 — 每天 19:30 推送最新公众号原创文章 2️⃣ 暂不需要 — 仅本次查询
---
使用场景
| 场景 | 角色 | 示例问法 | 收益 |
|---|---|---|---|
| 每日原创选题 | 公众号编辑 | "最新原创爆文" | 最新原创热门样本 + 可分享 HTML |
| 垂类对标 | 内容策划 | "科技类原创爆款" | 按分类筛选的 Top 榜单 |
| 指定日复盘 | 运营负责人 | "5 月 3 日的原创爆款" | 某日原创爆文列表 + PDF 导出 |
| 订阅推送 | 固定节奏运营 | "订阅财经赛道原创文章" | 每日定时推送原创爆文 |
---
重要数据说明
更新时间与数据回溯
| 查询类型 | 更新时间 | 可回溯范围 | 默认范围 |
|---|---|---|---|
| 原创爆款查询 | 每日 19:30 同步前一日数据 | 过去 30 天 | 近 7 天 |
支持赛道(22 个)
人文资讯、知识百科、健康养生、时尚潮流、美食餐饮、乐活生活、旅游出行、搞笑幽默、情感心理、体育娱乐、美容美体、文摘精选、民生资讯、财富理财、科技数码、创投商业、汽车交通、房产楼市、职场发展、教育考试、学术研究、企业品牌
数据时效
- 数据为获取时刻快照,与线上实时阅读可能不一致
- 当前时间早于 19:30 与晚于 19:30,「最新可查日期」不同
- 某赛道结果少于 10 篇时,提示可拓展至 30 天或查看综合榜单
---
分类映射表
标准分类列表
| 序号 | 标准分类名称 | 泛化关键词(用户可能的提问方式) |
|---|---|---|
| 1 | 人文资讯 | 人文、文化、历史、哲学、文学、艺术、人文社科 |
| 2 | 知识百科 | 知识、百科、科普、冷知识、常识、百科全书 |
| 3 | 健康养生 | 健康、养生、保健、医疗、中医、健身、营养、健康生活 |
| 4 | 时尚潮流 | 时尚、潮流、穿搭、美妆、时尚穿搭、流行趋势 |
| 5 | 美食餐饮 | 美食、餐饮、美食推荐、菜谱、烹饪、吃货、饮食 |
| 6 | 乐活生活 | 乐活、生活、生活方式、生活品质、品质生活、生活技巧 |
| 7 | 旅游出行 | 旅游、出行、旅行、攻略、景点、自驾游、旅游攻略 |
| 8 | 搞笑幽默 | 搞笑、幽默、段子、笑话、娱乐、开心、趣事 |
| 9 | 情感心理 | 情感、心理、情感故事、恋爱、婚姻、亲情、友情、心理辅导 |
| 10 | 体育娱乐 | 体育、娱乐、运动、明星、电影、音乐、综艺、体育赛事 |
| 11 | 美容美体 | 美容、美体、护肤、减肥、瘦身、美容护肤、塑形 |
| 12 | 文摘精选 | 文摘、精选、好文、美文、精选文章、优秀文章 |
| 13 | 民生资讯 | 民生、社会、新闻、时事、热点、社会新闻、民生热点 |
| 14 | 财富理财 | 财富、理财、投资、金融、基金、股票、保险、财商 |
| 15 | 科技数码 | 科技、数码、互联网、手机、电脑、科技新闻、数码产品 |
| 16 | 创投商业 | 创投、商业、创业、投资、商业模式、创业故事、商业财经 |
| 17 | 汽车交通 | 汽车、交通、购车、用车、汽车评测、交通出行 |
| 18 | 房产楼市 | 房产、楼市、买房、卖房、房产投资、房地产、房价 |
| 19 | 职场发展 | 职场、职业、工作、求职、职场技巧、职业发展、职场故事 |
| 20 | 教育考试 | 教育、考试、学习、培训、高考、考研、教育培训、学习方法 |
| 21 | 学术研究 | 学术、研究、论文、科研、学术前沿、研究成果 |
| 22 | 企业品牌 | 企业、品牌、公司、管理、企业文化、品牌故事、企业案例 |
| 23 | 总排名 | 总排名、全部、所有分类、综合排名、总榜 |
分类匹配规则
匹配优先级
1. 精确匹配:用户输入与标准分类名称完全一致 2. 包含匹配:用户输入包含标准分类名称,或标准分类名称包含用户输入 3. 关键词匹配:用户输入包含泛化关键词
匹配示例
| 用户输入 | 匹配分类 | 匹配方式 |
|---|---|---|
| 科技类原创爆款 | 科技数码 | 关键词匹配 |
| 健康养生的文章 | 健康养生 | 精确匹配 |
| 我想看旅游 | 旅游出行 | 关键词匹配 |
| 美食推荐 | 美食餐饮 | 关键词匹配 |
| 最新原创爆文 | 总排名 | 无分类关键词,默认总排名 |
| 财富理财 | 财富理财 | 精确匹配 |
时间区间判断规则
更新时间逻辑
数据每日 16:00 更新,更新前一天(T-1)的数据。
时间判断
- 当前时间 < 16:00:查询 T-2 数据(前天)
- 当前时间 >= 16:00:查询 T-1 数据(昨天)
日期计算
| 当前时间 | 数据更新日期 | 查询日期 | 说明 |
|---|---|---|---|
| 5月9日 10:00 | 5月8日 | 5月7日 | 数据还未更新,查询前天数据 |
| 5月9日 17:00 | 5月9日 | 5月8日 | 数据已更新,查询昨天数据 |
用户指定日期
如果用户指定了具体日期(如"5月3日的原创文章"),则直接使用用户指定的日期,不受更新时间影响。
startDate:用户指定日期endDate:用户指定日期 + 1天
未指定日期
如果用户未指定日期,则根据更新时间判断:
- 当前时间 < 16:00:查询 T-2 数据
startDate:前天endDate:昨天
- 当前时间 >= 16:00:查询 T-1 数据
startDate:昨天endDate:今天
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
公众号原创爆款文章获取脚本
功能:
1. 调用API获取公众号原创爆款文章数据
2. 按阅读量排序
3. 输出TOP榜单列表(Markdown格式,含链接)
4. 支持分类查询和时间区间查询
使用方法:
python fetch_articles.py --type "科技数码" --source "公众号文章原创之王-GitHub" --start_date "2026-05-03" --end_date "2026-05-04"
python fetch_articles.py --type "总排名" --source "公众号文章原创之王-GitHub" # 自动判断时间
python fetch_articles.py # 默认:总排名,自动判断时间
"""
import argparse
import json
import os
import sys
import ssl
import urllib.request
import urllib.error
from datetime import datetime, timedelta
# ==================== 分类映射 ====================
CATEGORY_MAPPING = {
"人文资讯": ["人文", "文化", "历史", "哲学", "文学", "艺术", "人文社科"],
"知识百科": ["知识", "百科", "科普", "冷知识", "常识", "百科全书"],
"健康养生": ["健康", "养生", "保健", "医疗", "中医", "健身", "营养", "健康生活"],
"时尚潮流": ["时尚", "潮流", "穿搭", "美妆", "时尚穿搭", "流行趋势"],
"美食餐饮": ["美食", "餐饮", "美食推荐", "菜谱", "烹饪", "吃货", "饮食"],
"乐活生活": ["乐活", "生活", "生活方式", "生活品质", "品质生活", "生活技巧"],
"旅游出行": ["旅游", "出行", "旅行", "攻略", "景点", "自驾游", "旅游攻略"],
"搞笑幽默": ["搞笑", "幽默", "段子", "笑话", "娱乐", "开心", "趣事"],
"情感心理": ["情感", "心理", "情感故事", "恋爱", "婚姻", "亲情", "友情", "心理辅导"],
"体育娱乐": ["体育", "娱乐", "运动", "明星", "电影", "音乐", "综艺", "体育赛事"],
"美容美体": ["美容", "美体", "护肤", "减肥", "瘦身", "美容护肤", "塑形"],
"文摘精选": ["文摘", "精选", "好文", "美文", "精选文章", "优秀文章"],
"民生资讯": ["民生", "社会", "新闻", "时事", "热点", "社会新闻", "民生热点"],
"财富理财": ["财富", "理财", "投资", "金融", "基金", "股票", "保险", "财商"],
"科技数码": ["科技", "数码", "互联网", "手机", "电脑", "科技新闻", "数码产品"],
"创投商业": ["创投", "商业", "创业", "投资", "商业模式", "创业故事", "商业财经"],
"汽车交通": ["汽车", "交通", "购车", "用车", "汽车评测", "交通出行"],
"房产楼市": ["房产", "楼市", "买房", "卖房", "房产投资", "房地产", "房价"],
"职场发展": ["职场", "职业", "工作", "求职", "职场技巧", "职业发展", "职场故事"],
"教育考试": ["教育", "考试", "学习", "培训", "高考", "考研", "教育培训", "学习方法"],
"学术研究": ["学术", "研究", "论文", "科研", "学术前沿", "研究成果"],
"企业品牌": ["企业", "品牌", "公司", "管理", "企业文化", "品牌故事", "企业案例"],
"总排名": ["总排名", "全部", "所有分类", "综合排名", "总榜"]
}
def match_category(user_input: str) -> str:
"""
根据用户输入匹配分类
Args:
user_input: 用户输入的文本
Returns:
匹配的标准分类名称,默认返回"总排名"
"""
if not user_input:
return "总排名"
user_input_lower = user_input.lower().strip()
# 1. 精确匹配
for category in CATEGORY_MAPPING.keys():
if user_input_lower == category.lower():
return category
# 2. 包含匹配
for category in CATEGORY_MAPPING.keys():
if category.lower() in user_input_lower or user_input_lower in category.lower():
return category
# 3. 关键词匹配
for category, keywords in CATEGORY_MAPPING.items():
for keyword in keywords:
if keyword.lower() in user_input_lower:
return category
# 默认返回总排名
return "总排名"
# ==================== API Key 获取 ====================
def get_api_key():
"""从当前环境变量获取 REDFOX_API_KEY"""
api_key = os.getenv("REDFOX_API_KEY")
if not api_key:
print("❌ 未找到 REDFOX_API_KEY,请配置环境变量:export REDFOX_API_KEY=<你的apikey>", file=sys.stderr)
sys.exit(1)
return api_key
# ==================== 接口调用 ====================
def fetch_articles_api(url: str, params: dict, api_key: str, timeout: int = 30) -> dict:
"""
使用原生 urllib.request 发 HTTPS POST 请求(verify=False)
Args:
url: API 地址
params: 请求参数(JSON body)
api_key: X-API-KEY 认证密钥
timeout: 超时时间(秒)
Returns:
API 响应数据(dict)
"""
headers = {
"Content-Type": "application/json",
"X-API-KEY": api_key
}
body = json.dumps(params, ensure_ascii=False)
data = body.encode("utf-8")
req = urllib.request.Request(url, data=data, headers=headers, method="POST")
ssl_ctx = ssl.create_default_context()
ssl_ctx.check_hostname = False
ssl_ctx.verify_mode = ssl.CERT_NONE
try:
with urllib.request.urlopen(req, context=ssl_ctx, timeout=timeout) as resp:
result = json.loads(resp.read().decode("utf-8"))
except urllib.error.HTTPError as e:
raise Exception(f"HTTP请求失败: {e.code}, {e.read().decode('utf-8', errors='replace')}")
except urllib.error.URLError as e:
raise Exception(f"请求失败: {e.reason}")
return result
def fetch_articles_by_category(type: str, source: str, start_date: str = "", end_date: str = "") -> dict:
"""
调用新接口获取文章数据
Args:
type: 分类名称
source: 数据源
start_date: 开始日期(YYYY-MM-DD)
end_date: 结束日期(YYYY-MM-DD)
Returns:
API 响应数据
"""
url = "https://redfox.hk/story/api/cozeSkill/getWxDataByCategoryAndTime"
# 获取 API Key(环境变量 > shell配置文件 > 提示用户配置)
api_key = get_api_key()
params = {
"type": type,
"source": source
}
if start_date:
params["startDate"] = start_date
if end_date:
params["endDate"] = end_date
print(f"正在获取原创爆款文章推荐...")
print(f"分类: {type}")
print(f"数据源: {source}")
if start_date:
print(f"开始日期: {start_date}")
if end_date:
print(f"结束日期: {end_date}")
print("-" * 60)
data = fetch_articles_api(url, params, api_key)
if data.get("code") != 2000:
raise Exception(f"API错误: {data.get('msg', '未知错误')}")
return data
# ==================== 数据处理 ====================
def parse_count_to_int(count_str: str) -> int:
"""解析阅读数字符串为整数"""
if not count_str:
return 0
count_str = str(count_str).strip().replace(",", "")
if "10w+" in count_str.lower() or "10万+" in count_str:
return 100000
elif "w+" in count_str.lower() or "万+" in count_str:
# 处理 "1w+", "5w+" 等格式
try:
num_str = count_str.lower().replace("w+", "").replace("万+", "")
num = float(num_str)
return int(num * 10000)
except:
return 0
elif "w" in count_str.lower() or "万" in count_str:
try:
num = float(count_str.lower().replace("w", "").replace("万", ""))
return int(num * 10000)
except:
return 0
else:
try:
return int(float(count_str))
except:
return 0
def get_reading_score(article: dict) -> int:
"""获取阅读量作为排序依据"""
clicks_count = parse_count_to_int(article.get("clicksCount", "0"))
return int(clicks_count or 0)
def process_articles(data: dict) -> list:
"""
处理文章数据,按阅读量排序
Args:
data: API 响应数据
Returns:
排序后的文章列表
"""
articles = data.get("data", {}).get("originalRank", [])
if not articles:
return []
# 按阅读量排序
articles.sort(key=get_reading_score, reverse=True)
return articles
# ==================== 时间计算 ====================
def get_query_date_range() -> tuple:
"""
根据当前时间判断查询日期范围
数据每日 19:30 更新,更新前一天(T-1)的数据。
- 当前时间 < 19:30:查询 T-2 数据
- 当前时间 >= 19:30:查询 T-1 数据
Returns:
(start_date, end_date, query_date_display, update_date_display)
"""
now = datetime.now()
current_hour = now.hour
current_minute = now.minute
if current_hour < 19 or (current_hour == 19 and current_minute < 30):
# 当前时间 < 19:30,查询 T-2 数据
query_date = now - timedelta(days=2) # 前天
update_date = now - timedelta(days=1) # 昨天
else:
# 当前时间 >= 19:30,查询 T-1 数据
query_date = now - timedelta(days=1) # 昨天
update_date = now # 今天
start_date = query_date.strftime("%Y-%m-%d")
end_date = (query_date + timedelta(days=1)).strftime("%Y-%m-%d")
query_date_display = query_date.strftime("%Y年%m月%d日")
update_date_display = update_date.strftime("%Y年%m月%d日")
return start_date, end_date, query_date_display, update_date_display
def get_recent_days_range(days: int = 7) -> tuple:
"""
获取最近N天的日期范围
Args:
days: 天数(默认7天)
Returns:
(start_date, end_date, date_range_display)
"""
now = datetime.now()
end_date = now
start_date = now - timedelta(days=days-1) # 包含今天,所以是days-1
start_date_str = start_date.strftime("%Y-%m-%d")
end_date_str = end_date.strftime("%Y-%m-%d")
date_range_display = f"{start_date.strftime('%Y年%m月%d日')}至{end_date.strftime('%Y年%m月%d日')}"
return start_date_str, end_date_str, date_range_display
# ==================== 输出格式化 ====================
def format_count(count_str: str) -> str:
"""格式化阅读数显示"""
if not count_str:
return "0"
count_str = str(count_str).strip()
# 保留原始的 w+ 或 万+ 格式
if "10w+" in count_str.lower() or "10万+" in count_str or "10W+" in count_str:
return "10w+"
elif "w+" in count_str.lower() or "万+" in count_str:
# 保留原始格式,如 "1w+", "5w+"
return count_str.lower().replace("万+", "w+")
count = parse_count_to_int(count_str)
if count >= 10000:
return f"{count/10000:.1f}w"
else:
return str(count)
def output_table(articles: list, limit: int = 20, mode: str = "preview", category: str = "总排名"):
"""
输出文章表格
Args:
articles: 文章列表
limit: 显示条数
mode: 输出模式(preview/full)
category: 分类名称
"""
total = len(articles)
display_count = min(limit, total) if mode == "preview" else total
display_articles = articles[:display_count]
# 输出表格
print("\n| 序号 | 作者 | 标题 | 阅读数 |")
print("|------|------|------|--------|")
for i, article in enumerate(display_articles, 1):
author = article.get("userName", "-")
title = article.get("title", "-")
clicks = format_count(article.get("clicksCount", "0"))
account_id = article.get("accountId", "")
ori_url = article.get("oriUrl", "")
# 构建链接
author_link = f"https://open.weixin.qq.com/qr/code?username={account_id}" if account_id else ""
title_link = ori_url if ori_url else ""
# 输出行
if author_link:
author_display = f"[{author}]({author_link})"
else:
author_display = author
if title_link:
title_display = f"[{title}]({title_link})"
else:
title_display = title
print(f"| {i} | {author_display} | {title_display} | {clicks} |")
print("\n导出功能:输出生成HTML榜单页面,表格内容支持导出 PDF 格式,自动生成并打开")
# 文章少于10篇时的提示
if total < 10:
category_display = category if category != "总排名" else "综合"
print(f"\n💡 {category_display}赛道10w+文章较少,您可以拓展过去30天或者看看综合10w+文章~")
if mode == "preview" and total > limit:
print(f"\n共获取到{total}条爆款原创热门文章,当前展示前{limit}条。")
print(f"\n📬 订阅服务")
print("是否需要订阅具体赛道的账号表现?我们支持:")
print("人文资讯、知识百科、健康养生、时尚潮流、美食餐饮、乐活生活、旅游出行、搞笑幽默、情感心理、体育娱乐、美容美体、文摘精选、民生资讯、财富理财、科技数码、创投商业、汽车交通、房产楼市、职场发展、教育考试、学术研究、企业品牌、总排名")
print("订阅推送 — 每天19点30分推送最新公众号原创文章")
print("暂不需要 — 仅本次查询")
else:
print(f"\n共获取到{total}条爆款原创热门文章。")
# ==================== 主函数 ====================
def main():
parser = argparse.ArgumentParser(description="获取公众号原创爆款文章")
parser.add_argument("--type", default="", help="分类名称(如'科技数码',默认'总排名')")
parser.add_argument("--source", default="公众号文章原创之王-GitHub", help="数据源")
parser.add_argument("--start_date", default="", help="开始日期(YYYY-MM-DD)")
parser.add_argument("--end_date", default="", help="结束日期(YYYY-MM-DD)")
parser.add_argument("--recent", type=int, default=0, help="最近N天(如7表示最近7天)")
parser.add_argument("--limit", type=int, default=20, help="显示条数(默认20)")
parser.add_argument("--mode", default="preview", choices=["preview", "full"], help="输出模式")
parser.add_argument("--temp_file", default="temp_articles.json", help="临时JSON文件路径")
parser.add_argument("--query_intent", default="", help="查询意图(today/yesterday,用于判断是否需要提示)")
args = parser.parse_args()
# 匹配分类
category = match_category(args.type) if args.type else "总排名"
# 确定日期范围
if args.recent > 0:
# 用户查询最近N天
start_date, end_date, date_range_display = get_recent_days_range(args.recent)
query_date_display = date_range_display
is_specific_date = False
is_recent_query = True
elif args.start_date:
# 用户指定了日期
start_date = args.start_date
end_date = args.end_date if args.end_date else (datetime.strptime(start_date, "%Y-%m-%d") + timedelta(days=1)).strftime("%Y-%m-%d")
query_date_display = datetime.strptime(start_date, "%Y-%m-%d").strftime("%Y年%m月%d日")
is_specific_date = True
is_recent_query = False
else:
# 自动判断日期
start_date, end_date, query_date_display, update_date_display = get_query_date_range()
is_specific_date = False
is_recent_query = False
# 判断用户查询意图是否为"今日"或"昨日"
query_intent = args.query_intent.lower() if args.query_intent else ""
need_prompt = False
if query_intent in ["today", "今日", "今天"]:
# 用户想查询今日数据
need_prompt = True
intent_date_str = "今天"
elif query_intent in ["yesterday", "昨日", "昨天"]:
# 用户想查询昨日数据
need_prompt = True
intent_date_str = "昨天"
# 调用接口
try:
data = fetch_articles_by_category(category, args.source, start_date, end_date)
except Exception as e:
print(f"❌ 错误: {str(e)}")
sys.exit(1)
# 处理数据
articles = process_articles(data)
if not articles:
print("❌ 未获取到文章数据")
sys.exit(0)
# 保存临时文件
with open(args.temp_file, 'w', encoding='utf-8') as f:
json.dump({"articles": articles}, f, ensure_ascii=False, indent=2)
print(f"✅ 数据已保存到临时文件: {args.temp_file}")
# 输出数据说明
print(f"\n💡 数据说明")
print(f"公众号原创文章推荐将在每日19点30分准时更新昨日文章数据,以下数据为获取时间时的快照,和实时数据有所差别。")
# 判断数据更新状态(所有情况统一处理)
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
current_hour = datetime.now().hour
current_minute = datetime.now().minute
if current_hour < 19 or (current_hour == 19 and current_minute < 30):
# 19:30前,最新数据是T-2(前天)
latest_date = today - timedelta(days=2)
latest_date_str = "前天"
else:
# 19:30后,最新数据是T-1(昨天)
latest_date = today - timedelta(days=1)
latest_date_str = "昨天"
# 回溯日期(30天前)
earliest_date = today - timedelta(days=30)
# 判断是否需要提示(数据查询日期和用户询问日期不一致)
need_date_prompt = False
prompt_message = ""
if is_recent_query:
# 最近N天查询,检查查询的结束日期是否晚于最新日期
# 解析 query_date_display 获取结束日期
if "至" in query_date_display:
end_date_str = query_date_display.split("至")[1].replace("年", "-").replace("月", "-").replace("日", "")
try:
query_end_date = datetime.strptime(end_date_str.strip(), "%Y-%m-%d")
if query_end_date > latest_date:
need_date_prompt = True
prompt_message = f"\n非常抱歉🙏,最新的是{latest_date_str}的数据,我将为您查询最接近您需求的时间范围。"
except:
pass
print(f"\n📊 原创爆文推荐")
print(f"{query_date_display}的原创爆款文章")
elif is_specific_date:
# 指定日期查询
query_date = datetime.strptime(start_date, "%Y-%m-%d")
if query_date > latest_date:
# 用户查询未更新日期的榜单(查询日期晚于最新日期)
need_date_prompt = True
prompt_message = f"\n非常抱歉🙏,最新的是{latest_date_str}的数据,我将为您查询最接近您需求的{query_date_display}原创文章。"
elif query_date < earliest_date:
# 用户查询时间早于回溯日期
need_date_prompt = True
prompt_message = f"\n非常抱歉🙏,目前最多支持回溯「过去30天」,我将为您查询最接近您需求的时间范围~"
print(f"\n📊 原创爆文推荐")
print(f"{query_date_display}当天的原创爆款文章")
else:
# 默认查询,检查是否需要提示
if need_prompt:
# 用户想查询"今日"或"昨日",但数据还没更新
need_date_prompt = True
prompt_message = f"\n非常抱歉🙏,最新的是{latest_date_str}的数据,我将为您查询最接近您需求的{query_date_display}原创文章。"
print(f"\n📊 原创爆文推荐")
print(f"{query_date_display}当天的原创爆款文章")
# 统一输出提示信息
if need_date_prompt:
print(prompt_message)
# 输出表格
output_table(articles, args.limit, args.mode, category)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
公众号原创爆款文章HTML生成脚本
功能:
1. 读取API数据
2. 生成公众号风格的HTML页面
3. 支持PDF导出
使用方法:
python generate_hot_html.py --data_file data.json --output ranking.html
python generate_hot_html.py --articles '[{"title": "...", ...}]' --output ranking.html
"""
import argparse
import json
import os
from datetime import datetime
def get_rank_display(rank: int) -> str:
"""获取排名显示(奖牌或数字)"""
if rank == 1:
return "🥇"
elif rank == 2:
return "🥈"
elif rank == 3:
return "🥉"
else:
return str(rank)
def get_article_html(article: dict, rank: int, is_top: bool = False) -> str:
"""生成单篇文章的HTML"""
try:
title = article.get("title", "未知标题")
url = article.get("oriUrl", "#")
account = article.get("userName", article.get("accountId", "未知账号"))
account_id = article.get("accountId", "")
reads = article.get("clicksCount", "0")
# 处理日期
public_time = article.get("publicTime", "")
if public_time:
try:
date = str(public_time)[:10]
except:
date = ""
else:
date = ""
# 生成公众号名片链接
if account_id:
account_url = f"https://open.weixin.qq.com/qr/code?username={account_id}"
else:
account_url = "#"
top_class = " top-item" if is_top else ""
rank_display = get_rank_display(rank)
top_rank_class = " top" if is_top else ""
return f'''
<li class="article-item{top_class}">
<div class="article-body">
<div class="article-rank{top_rank_class}">{rank_display}</div>
<div class="article-content">
<a href="{url}" target="_blank" class="article-title">{title}</a>
<div class="article-info">
<span class="info-item"><a href="{account_url}" target="_blank" class="info-source-link"><span class="info-source-icon">👤</span>{account}</a></span>
<span class="info-item"><span class="info-stat">📖 阅读 <span class="info-stat-value">{reads}</span></span></span>
<span class="info-item"><span class="info-stat">📅 {date}</span></span>
</div>
</div>
</div>
</li>'''
except:
# 如果生成失败,返回空字符串
return ""
def generate_html(keyword: str, articles: list, insights: dict = None, top_n: int = 10) -> str:
"""生成完整的HTML页面"""
# 数据验证
if not articles:
articles = []
# 计算统计数据
try:
account_count = len(set(a.get("accountId", "") for a in articles if a.get("accountId")))
except:
account_count = 0
# 计算日期范围
dates = []
for a in articles:
try:
public_time = a.get("publicTime", "")
if public_time:
date_str = str(public_time)[:10] # 只取日期部分
if date_str and len(date_str) >= 10: # 确保日期格式正确
dates.append(date_str)
except:
continue
if dates:
try:
min_date = min(datetime.strptime(d, "%Y-%m-%d") for d in dates if d)
max_date = max(datetime.strptime(d, "%Y-%m-%d") for d in dates if d)
days = (max_date - min_date).days + 1
except:
days = 30
else:
days = 30
# 生成文章列表HTML
articles_html = ""
for i, article in enumerate(articles[:top_n], 1):
try:
articles_html += get_article_html(article, i, i <= 3)
except:
# 如果单篇文章生成失败,跳过
continue
# 完整的HTML模板
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>{keyword} · 公众号原创爆款文章</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
<style>
* {{
margin: 0;
padding: 0;
box-sizing: border-box;
}}
body {{
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
background: #f5f5f5;
color: #333;
line-height: 1.8;
padding: 12px;
}}
.container {{
max-width: 680px;
margin: 0 auto;
background: #fff;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06);
overflow: hidden;
}}
.brand-header {{
background: linear-gradient(135deg, #fff 0%, #f8fff8 100%);
padding: 32px 24px 28px;
text-align: center;
border-bottom: 1px solid #eee;
}}
.brand-title {{
font-size: 26px;
font-weight: 700;
color: #1a1a1a;
margin-bottom: 8px;
letter-spacing: 1px;
}}
.brand-subtitle {{
font-size: 14px;
color: #888;
}}
.export-btn {{
display: inline-flex;
align-items: center;
gap: 4px;
background: linear-gradient(135deg, #0088ff, #0066cc);
color: #fff;
border: none;
padding: 5px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(0, 136, 255, 0.3);
}}
.export-btn:hover {{
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 136, 255, 0.4);
}}
.brand-stats {{
display: flex;
justify-content: center;
gap: 32px;
margin-top: 24px;
padding-top: 20px;
border-top: 1px dashed #e5e5e5;
}}
.brand-stat {{
text-align: center;
}}
.brand-stat-num {{
font-size: 24px;
font-weight: 700;
color: rgb(0, 179, 84);
}}
.brand-stat-label {{
font-size: 12px;
color: #999;
margin-top: 4px;
}}
.content {{
padding: 24px 20px;
}}
.section-header {{
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
gap: 12px;
}}
.section-header .export-btn {{
font-size: 12px;
padding: 5px 12px;
}}
.section-right {{
display: flex;
align-items: center;
gap: 10px;
}}
.section-title {{
font-size: 18px;
font-weight: 600;
color: #1a1a1a;
display: flex;
align-items: center;
gap: 8px;
}}
.section-title::before {{
content: "";
width: 4px;
height: 20px;
background: rgb(0, 179, 84);
border-radius: 2px;
}}
.section-badge {{
background: rgba(0, 179, 84, 0.1);
color: rgb(0, 179, 84);
font-size: 12px;
padding: 4px 12px;
border-radius: 12px;
font-weight: 500;
}}
.article-list {{
list-style: none;
}}
.article-item {{
background: #fff;
border: 1px solid #eee;
border-radius: 12px;
padding: 20px;
margin-bottom: 16px;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}}
.article-item:hover {{
border-color: rgb(0, 179, 84);
box-shadow: 0 4px 16px rgba(0, 179, 84, 0.12);
transform: translateY(-2px);
}}
.article-item:last-child {{
margin-bottom: 0;
}}
.article-item.top-item {{
background: linear-gradient(135deg, #fff 0%, #f8fff8 100%);
border-color: rgba(0, 179, 84, 0.3);
}}
.article-item.top-item::before {{
content: "";
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
background: linear-gradient(180deg, rgb(0, 179, 84), #06a54a);
}}
.article-body {{
display: flex;
align-items: flex-start;
gap: 14px;
}}
.article-rank {{
width: 36px;
height: 36px;
background: rgb(0, 179, 84);
color: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 14px;
flex-shrink: 0;
}}
.article-rank.top {{
background: linear-gradient(135deg, #ffd700, #ff8c00);
color: #fff;
font-size: 22px;
font-weight: normal;
}}
.article-content {{
flex: 1;
}}
.article-title {{
font-size: 17px;
font-weight: 600;
color: #1a1a1a;
line-height: 1.5;
text-decoration: none;
display: block;
margin-bottom: 12px;
cursor: pointer;
transition: color 0.2s;
}}
.article-title:hover {{
color: rgb(0, 179, 84);
}}
.article-info {{
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0;
font-size: 14px;
color: #888;
}}
.info-item {{
padding: 0 12px;
position: relative;
}}
.info-item::before {{
content: "|";
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
color: #e0e0e0;
font-size: 10px;
}}
.info-item:first-child {{
padding-left: 0;
}}
.info-item:first-child::before {{
display: none;
}}
.info-source-link {{
display: inline-flex;
align-items: center;
gap: 4px;
color: #0088ff;
text-decoration: none;
transition: color 0.2s;
}}
.info-source-link:hover {{
text-decoration: underline;
}}
.info-source-icon {{
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 14px;
vertical-align: middle;
}}
.info-category {{
font-size: 11px;
color: rgb(0, 179, 84);
background: rgba(0, 179, 84, 0.1);
padding: 2px 8px;
border-radius: 4px;
font-weight: 500;
}}
.info-stat {{
display: flex;
align-items: center;
gap: 4px;
}}
.info-stat-value {{
color: #0088ff;
font-weight: 600;
}}
.footer {{
background: #fafafa;
padding: 24px;
text-align: center;
border-top: 1px solid #eee;
}}
.footer-text {{
font-size: 13px;
color: #888;
}}
.footer-copy {{
font-size: 11px;
color: #bbb;
margin-top: 8px;
}}
@media (max-width: 480px) {{
body {{
padding: 0;
}}
.container {{
border-radius: 0;
}}
.brand-stats {{
gap: 20px;
}}
.article-info {{
gap: 8px;
}}
}}
@media print {{
.export-btn {{
display: none !important;
}}
.container {{
padding: 15mm !important;
}}
.brand-header {{
padding: 8mm 10mm !important;
margin-bottom: 8mm !important;
}}
.brand-stats {{
padding: 6mm !important;
gap: 10px !important;
}}
.brand-stat {{
padding: 4mm 6mm !important;
}}
.section-header {{
padding: 6mm 0 !important;
}}
.article-item {{
padding: 6mm 8mm !important;
margin-bottom: 4mm !important;
}}
.footer {{
padding: 8mm !important;
}}
}}
</style>
</head>
<body>
<div class="container">
<div class="brand-header">
<h1 class="brand-title">{keyword} · 原创爆款文章</h1>
<p class="brand-subtitle">每日推送公众号原创爆款内容,解析流量密码</p>
<div class="brand-stats">
<div class="brand-stat">
<div class="brand-stat-num">{top_n}</div>
<div class="brand-stat-label">篇爆款文章</div>
</div>
<div class="brand-stat">
<div class="brand-stat-num">{account_count}</div>
<div class="brand-stat-label">个账号</div>
</div>
<div class="brand-stat">
<div class="brand-stat-num">{days}</div>
<div class="brand-stat-label">天内热文</div>
</div>
</div>
</div>
<div class="content">
<div class="section-header">
<h2 class="section-title">TOP{top_n} 爆款文章</h2>
<div class="section-right">
<span class="section-badge">阅读量排序</span>
<button class="export-btn" onclick="exportToPDF()">📥 导出PDF</button>
</div>
</div>
<ul class="article-list">
{articles_html}
</ul>
</div>
<div class="footer">
<p class="footer-text">公众号原创爆款文章,每日更新最新爆文内容</p>
<p class="footer-copy">备注:互动数据为入库快照,实时数据可能持续增长</p>
</div>
</div>
<script>
function exportToPDF() {{
const element = document.querySelector('.container');
const button = document.querySelector('.export-btn');
button.style.display = 'none';
// 计算内容高度,决定PDF尺寸
const contentHeight = element.scrollHeight;
const contentWidth = element.scrollWidth;
// 根据内容比例决定纸张尺寸,mm为单位,1mm ≈ 3.78px
const pdfWidth = Math.max(210, Math.ceil(contentWidth / 3.78) + 20);
const pdfHeight = Math.max(297, Math.ceil(contentHeight / 3.78) + 20);
const opt = {{
margin: 0,
filename: '{keyword}_爆款内容分析.pdf',
image: {{ type: 'jpeg', quality: 0.95 }},
html2canvas: {{
scale: 2,
useCORS: true,
logging: false,
scrollX: 0,
scrollY: 0
}},
jsPDF: {{
unit: 'mm',
format: [pdfWidth, pdfHeight],
orientation: pdfWidth > pdfHeight ? 'landscape' : 'portrait'
}},
pagebreak: {{ mode: 'none' }}
}};
html2pdf().set(opt).from(element).save().then(() => {{
button.style.display = 'inline-flex';
}});
}}
</script>
</body>
</html>'''
return html
def main():
parser = argparse.ArgumentParser(description="生成公众号原创爆款文章HTML")
parser.add_argument("--temp_file", default="temp_articles.json", help="临时JSON文件路径")
parser.add_argument("--output", default="ranking.html", help="输出文件路径")
parser.add_argument("--display_count", type=int, default=None, help="要展示的文章数量(如果不指定,则展示所有文章)")
args = parser.parse_args()
try:
# 从临时JSON文件读取数据
try:
with open(args.temp_file, "r", encoding="utf-8") as f:
temp_data = json.load(f)
keyword = temp_data.get("keyword", "热门文章")
articles = temp_data.get("articles", [])
if not isinstance(articles, list):
articles = []
except FileNotFoundError:
print(f"错误: 临时文件不存在 - {args.temp_file}", file=sys.stderr)
return 1
except json.JSONDecodeError:
print(f"错误: 临时文件格式错误 - {args.temp_file}", file=sys.stderr)
return 1
except Exception as e:
print(f"错误: 读取临时文件失败 - {str(e)}", file=sys.stderr)
return 1
if not articles:
print(f"错误: 临时文件中没有文章数据", file=sys.stderr)
return 1
# 根据display_count参数确定要展示的文章数量
display_count = args.display_count if args.display_count is not None else len(articles)
display_articles = articles[:display_count]
print(f"✅ 从临时文件读取到 {len(articles)} 条文章数据,本次展示 {len(display_articles)} 条")
# 生成HTML(使用display_articles而不是articles)
try:
html_content = generate_html(keyword, display_articles, None, len(display_articles))
except Exception as e:
print(f"错误: 生成HTML失败 - {str(e)}", file=sys.stderr)
return 1
# 写入文件
try:
with open(args.output, "w", encoding="utf-8") as f:
f.write(html_content)
except Exception as e:
print(f"错误: 写入文件失败 - {str(e)}", file=sys.stderr)
return 1
print(f"✅ HTML文件已生成: {args.output}")
return 0
except Exception as e:
print(f"错误: {str(e)}", file=sys.stderr)
return 1
if __name__ == "__main__":
import sys
sys.exit(main())