
Douyin Hot Trend
- 261 installs
- 316 repo stars
- Updated August 4, 2026
- redfox-data/redfox-community
Use douyin-hot-trend for development tasks
About
douyin-hot-trend: A skill for development. This provides functionality for development workflows.
- douyin-hot-trend
Douyin Hot Trend by the numbers
- 261 all-time installs (skills.sh)
- +18 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #1,481 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 douyin-hot-trendAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 261 |
|---|---|
| repo stars | ★ 316 |
| Last updated | August 4, 2026 |
| Repository | redfox-data/redfox-community ↗ |
What it does
Use douyin-hot-trend for development tasks
Files
抖音热榜
简介
抖音热榜是一款实时追踪抖音平台热点趋势的数据工具,对接 红狐Hub 数据 API,每小时更新热榜内容,并提供创作者视角的深度分析。
通过简单的自然语言指令,你可以:
- 📊 实时热榜查询:每 60 分钟抓取抖音 TOP 50 榜单,同步热度值与跳转链接
- 📅 历史热榜回溯:覆盖近 7 天、近 30 天历史数据,支持按日期与关键词检索
- 🔀 热榜数据对比:对比任意两个时间点(如昨天 vs 今天)的热榜变化
- 💡 深度洞察分析:从创作者视角提供选题机会、标题套路与趋势预判
- 🔔 个性化订阅:支持每小时或每日定时推送热榜数据
适用于内容创作者、短视频运营、MCN 机构、品牌营销人员等需要追踪抖音热点、寻找创作灵感、制定内容策略的场景。
技术基础:Python 3.x + requests 库,通过 红狐Hub APIKey获取数据。
---
功能特性
🎯 核心功能
- 📊 实时热榜:获取抖音当前 TOP 50 热榜,每小时刷新,展示排名、热度值、话题标题和跳转链接
- 📅 历史回溯:支持查询近 7 天或近 30 天历史热榜,最长覆盖 30 天
- 💡 创作者洞察:自动生成选题机会分析、标题套路拆解、趋势预判与行动建议
- 📄 可视化报告:一键生成紫色极简风格 HTML 页面,支持导出 PDF,每条记录可点击跳转
- 🔔 订阅推送:支持每小时或每日定时推送,自动追踪热点变化
✨ 特色亮点
- ⚡ 自然语言交互:输入「今日热榜」「近7天热榜」等即可查询,无需记忆命令参数
- 🔒 三级鉴权回退:API Key 优先从环境变量读取,自动回退到 Shell 配置文件
- ✅ 数据一致性:表格、洞察、HTML、PDF 全链路数据一致,不做加工或美化
- 🎬 创作者视角:每条洞察都回答「怎么用」,给出可执行的行动建议
---
一键安装
前置条件
- 已安装 Python 3.x 及
requests库 - 已注册 红狐Hub官网 账号并获取 API Key(格式
ak_xxxxxxxx) - 新注册用户将获赠免费积分,可立即开始使用
安装方式
方式一:WorkBuddy(龙虾)技能安装(推荐)
1. 将 douyin-hot-trend 技能文件夹复制到 WorkBuddy 的 skills 目录下 2. 在 WorkBuddy 对话中直接说「抖音热榜」或「今日热榜」,Agent 将自动加载本技能 3. 配置 API Key(见下方「环境变量配置」) 4. 安装 Python 依赖:
pip install requests方式二:Cursor / 其他 Agent 环境手动安装
1. 将技能文件夹放置到对应环境的 skills 目录 2. 确保 Agent 可读取 SKILL.md 与 references/core_workflow.md 3. 配置 REDFOX_API_KEY 环境变量 4. 执行 pip install requests
环境变量配置
| 变量名 | 必填 | 说明 |
|---|---|---|
REDFOX_API_KEY | 是 | 红狐Hub API 访问密钥,格式 ak_xxxxxxxx |
配置方式(任选其一):
# 方式一:直接设置环境变量(推荐)
export REDFOX_API_KEY=ak_xxxxxxxx
# 方式二:写入 Shell 配置文件(~/.bashrc / ~/.bash_profile / ~/.zshrc)
echo 'export REDFOX_API_KEY=ak_xxxxxxxx' >> ~/.bashrc
source ~/.bashrc若未配置环境变量,技能将自动从 Shell 配置文件读取;若仍未获取到,会在运行时提示配置。
在 WorkBuddy 中配置 Key:可在系统环境变量中设置 REDFOX_API_KEY,或在 Shell 配置文件中写入后重启 WorkBuddy,无需在每次对话中手动传入。
---
使用指南
基础使用
1. 查看实时热榜
告诉助手你想查看当前抖音热点:
用户:抖音热榜
助手:为您展示抖音实时热榜 TOP 20(含排名、热度值、话题链接),并附创作者洞察报告。
2. 查看历史热榜
查询指定时间范围的历史数据:
用户:近7天热榜
助手:为您展示近 7 天抖音历史热榜,支持按日期回溯热点演变。
用户:4月15日热榜
助手:为您查询 4 月 15 日当天的热榜数据。
3. 加载完整榜单
默认展示 TOP 20,可继续加载至 TOP 50:
用户:继续加载
助手:为您展示 TOP 21–50,并生成包含完整 TOP 50 的 HTML 报告。
高级使用
1. 创作者洞察
热榜展示后自动输出深度分析:
用户:今日热榜
助手:(展示表格后)输出爆款选题机会、标题套路破解、趋势预判与行动建议。
2. 热榜对比
对比两个时间点的热榜变化:
用户:对比昨天和今天的热榜
助手:为您对比两日热榜排名与热度变化,标注新进榜与掉榜话题。
3. 订阅推送
设置定时自动推送:
用户:订阅每日热榜推送
助手:已设置每日推送,将在指定时间自动推送 TOP 50 完整热榜数据。
用户:取消订阅
助手:已取消定时推送。
4. 生成报告
每次查询自动生成可视化报告:
用户:今日热榜
助手:(查询完成后)已生成 HTML 页面与 PDF 文件,数据与表格展示完全一致,支持点击跳转。
常用指令速查
| 指令 | 功能 |
|---|---|
| 抖音热榜 / 今日热榜 | 查询实时热榜 TOP 20 |
| 昨日热榜 | 查询昨日热榜 |
| 近7天热榜 | 查询近 7 天历史热榜 |
| 近30天热榜 | 查询近 30 天历史热榜 |
| X月X日热榜 | 查询指定日期热榜 |
| 继续加载 / 加载更多 | 加载 TOP 21–50 完整榜单 |
| 订阅每日/每小时推送 | 设置定时推送 |
| 取消订阅 | 停止定时推送 |
---
使用场景
场景一:内容创作者找选题
角色:抖音短视频创作者
需求:每天快速了解平台热点,找到适合自己赛道的创作方向
使用方式: 1. 每天早晨说「今日热榜」,获取最新热点 2. 查看创作者洞察报告,获取选题建议和标题公式 3. 直接套用可迁移选题,快速产出内容
预期收益:缩短选题时间,提高内容命中率
---
场景二:MCN 机构运营管理
角色:MCN 运营人员
需求:监控平台热点趋势,为旗下达人提供选题指导和内容策略
使用方式: 1. 订阅每日热榜推送,自动获取最新热点 2. 查看历史热榜,分析热点演变规律 3. 将洞察报告分享给达人团队,指导内容创作
预期收益:提升团队内容策略的时效性和精准度
---
场景三:品牌营销热点借势
角色:品牌营销经理
需求:及时发现可借势的热点话题,制定蹭热点营销方案
使用方式: 1. 关注每日热榜,筛选与品牌调性匹配的热点 2. 分析趋势预判,提前布局内容 3. 导出 HTML/PDF 报告,用于团队沟通和方案汇报
预期收益:提升品牌热点的响应速度和借势效果
---
场景四:短视频运营学习研究
角色:短视频运营新手
需求:学习爆款内容的创作规律,提升运营能力
使用方式: 1. 定期查看热榜和创作者洞察 2. 学习标题套路和情绪触发词 3. 模仿可迁移选题进行练习
预期收益:快速建立内容 sense,掌握爆款创作方法论
---
项目架构
目录结构
douyin-hot-trend/
├── SKILL.md # 技能说明文档(当前文件)
├── references/
│ └── core_workflow.md # Agent 核心工作流指令
├── scripts/
│ ├── hotspot_fetcher.py # 热榜数据获取脚本
│ └── gen_douyin_hot_html.py # HTML 可视化报告生成脚本
└── assets/
└── douyin_hot_trend_template.html # HTML 页面模板核心模块说明
| 模块 | 文件 | 职责 |
|---|---|---|
| 数据获取 | hotspot_fetcher.py | 调用 红狐Hub API 获取抖音热榜 JSON,支持实时与历史查询 |
| 报告生成 | gen_douyin_hot_html.py | 将热榜数据渲染为紫色极简风格 HTML,支持导出 PDF |
| 页面模板 | douyin_hot_trend_template.html | 独立 HTML 模板,支持占位符快速替换生成 |
技术栈
| 类别 | 说明 |
|---|---|
| 运行环境 | Python 3.x |
| HTTP 请求 | 原生 requests 库 |
| 认证方式 | X-API-KEY 请求头,三级回退(环境变量 → Shell 配置 → 提示配置) |
| 数据来源 | 红狐Hub API |
| 部署平台 | WorkBuddy / Cursor 等 Agent 环境 |
常见问答
安装相关问题
Q1: 运行时提示「缺少 REDFOX_API_KEY 配置」怎么办?
A: 请按以下步骤排查: 1. 确认已注册 红狐Hub 账号并在个人中心获取 API Key 2. 设置环境变量:export REDFOX_API_KEY=ak_xxxxxxxx 3. 或写入 Shell 配置文件后执行 source ~/.bashrc 使其生效 4. 确认 API Key 格式正确,以 ak_ 开头
Q2: 需要安装哪些 Python 依赖?
A: 仅需 requests 库:pip install requests
Q3: 如何在 WorkBuddy(龙虾)中启用本技能?
A: 将技能文件夹放入 WorkBuddy 的 skills 目录,配置好 REDFOX_API_KEY 后,在对话中说「抖音热榜」即可触发。Agent 将按 references/core_workflow.md 执行完整工作流。
---
使用相关问题
Q4: 热榜数据多久更新一次?
A: 每小时更新一次。每次查询都会调用 API 获取最新数据,不依赖本地缓存。
Q5: 可以查询多久之前的历史数据?
A: 最长支持查询近 30 天的历史热榜。超过 30 天的数据暂不支持。
---
故障排除
Q6: API 调用失败怎么办?
A: 请按以下顺序排查: 1. 检查网络连接,确认可以访问 https://redfox.hk 2. 确认 API Key 未过期,账户积分是否充足 3. 检查环境变量:echo $REDFOX_API_KEY 4. 稍后重试,API 服务可能存在临时波动
Q7: 订阅推送没有收到怎么办?
A: 确认订阅已成功设置。如需重新设置,说「取消订阅」后重新订阅。如仍有问题,检查WorkBuddy等系统的自动化任务是否正常运行。
Q8: HTML 报告预览不成功或显示空白怎么办?
A: 请按以下方式处理: 1. 确认 HTML 文件已成功生成(脚本会输出 ✅ 已生成 提示) 2. 直接双击打开交付的 HTML 文件,用 Chrome/Edge 浏览器查看,不依赖WorkBuddy等系统内置预览 3. 若文件已交付但内置预览空白,属于已知限制——Windows 中文用户名路径可能导致预览组件兼容性问题,直接用浏览器打开即可正常显示
---
安全与许可
Q9: API Key 如何安全存储?
A: 建议通过环境变量或 Shell 配置文件存储,不要将 Key 写入代码仓库或公开分享。本技能支持三级鉴权回退,优先读取环境变量,避免在对话中明文传递。
Q10: 数据来源与使用许可?
A: 热榜数据来自 红狐Hub 官方 API,使用时须遵守 红狐Hub 平台服务条款及抖音平台相关规定。本技能仅作数据展示与分析辅助,不保证数据的商业用途合规性。
---
获取帮助
如有其他问题,可通过以下方式获取帮助:
- 📖 查阅 Agent 执行参考:
references/core_workflow.md - 🌐 红狐Hub 官网:https://redfox.hk
- 📧 联系 红狐Hub 平台客服获取 API 相关问题支持
---
Agent 执行参考:完整的操作步骤、输出格式、数据规则等执行层指令,请查阅 references/core_workflow.md。
Douyin Hot Trend Tracker / douyin-hot-trend
---
Introduction
A smart hot trend tool that tracks Douyin trending topics in real time, uncovers viral content patterns, and helps creators and operators catch hot topics and find content ideas efficiently.
Core Value
- Automatically updates the Douyin TOP 50 chart every hour, with heat scores and direct links at a glance
- Covers up to 30 days of historical charts so you can trace past trend shifts anytime
- Automatically generates topic opportunity analysis, title pattern breakdowns, and trend forecasts from a creator's perspective
- Supports side-by-side comparison of any two time points, clearly marking newly-entered and dropped topics
- Supports hourly or daily scheduled delivery — no manual triggering needed
Who It's For
- 🎬 Short-video Creators — Quickly lock in daily hot topics and shorten your ideation time
- 📣 MCN Operations Teams — Monitor platform trends centrally and guide creator content strategies efficiently
- 🏷️ Brand Marketing Managers — Identify leverage-worthy topics and respond to hot trends faster
- 📚 Operations Beginners — Learn viral title patterns and quickly build content intuition
---
Features
Core Functions
- Real-Time Chart Query: Fetch the current Douyin TOP 50 trending topics with rankings, heat scores, titles, and clickable links — refreshed every 60 minutes
- Historical Chart Lookup: Query up to the past 7 days or 30 days of trending data, with support for specifying an exact date
- Chart Comparison: Compare rankings and heat score changes between any two time points, with new entrants and dropped topics automatically flagged
- Creator Insights Report: After displaying the chart, automatically outputs viral topic opportunities, title pattern analysis, trend forecasts, and actionable recommendations
- Visual Report Export: One-click generation of a minimal-purple-style HTML page with PDF export support and clickable links for every topic
- Scheduled Subscription Push: Set up hourly or daily automated delivery to stay on top of trending topics without manual effort
---
API Key Acquisition & Security
- This skill requires the environment variable:
REDFOX_API_KEY. REDFOX_API_KEYis provided by RedFoxHub (https://redfox.hk).- Register an account at RedFoxHub to obtain your
REDFOX_API_KEY. - Configure the device environment variable
REDFOX_API_KEYbefore 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 |
|---|---|---|
| Check real-time chart | "Douyin trending" / "Today's hot list" | Shows current TOP 20 with heat scores and links, plus a creator insights report |
| Check yesterday's chart | "Yesterday's hot list" | Retrieves all-day trending data from yesterday |
| View historical chart | "Hot list from the past 7 days" / "Past 30 days" | Traces trending topics over the selected time range |
| View a specific date | "Hot list from May 15" | Retrieves the chart for that specific date |
| Load the full chart | "Load more" / "Continue loading" | Expands from TOP 20 to the full TOP 50 |
| Compare chart changes | "Compare today's and yesterday's hot list" | Outputs differences between the two days with flagged new entrants and dropouts |
| Subscribe to updates | "Subscribe to daily hot list push" | Sets up scheduled automatic delivery every day |
| Cancel subscription | "Cancel subscription" | Stops scheduled delivery |
Sample Output
After querying "Today's hot list", you will receive:
1. Trending Table: Rank, topic title, heat score, and direct links (TOP 20; expandable to 50) 2. Creator Insights Report:
- 🔥 Viral Topic Opportunities (transferable topics and content angles)
- 🎯 Title Pattern Breakdown (emotional hooks, sentence structures)
- 📈 Trend Forecast (which topics are rising or falling)
- 💡 Action Recommendations (executable content strategy suggestions)
3. Visual Report: Auto-generated HTML page with PDF export support
---
Use Cases
| Scenario | Role | Example Query | Benefit |
|---|---|---|---|
| Daily content ideation | Short-video Creator | "Today's hot list" | Quickly identify hot directions and get transferable topics with title formulas |
| Trend monitoring | MCN Operations | "Past 7 days hot list" | Track a week's trend shifts and inform creator content strategy |
| Brand hot-topic leverage | Brand Marketing Manager | "Compare this week's chart changes" | Spot sustained topics and plan brand content in advance |
| Operations learning | Short-video Beginner | "Today's hot list — analyze the title patterns" | Learn viral sentence structures and rapidly build content intuition |
---
抖音热榜神器 / douyin-hot-trend
---
简介
实时追踪抖音热点、挖掘爆款规律,帮助内容创作者和运营者高效蹭热点、找选题的智能热榜工具。
核心价值
- 每小时自动更新抖音 TOP 50 榜单,热度值与跳转链接一键可见
- 覆盖近 7 天、近 30 天历史热榜,随时回溯过往热点演变
- 从创作者视角自动生成选题机会、标题套路与趋势预判
- 支持任意两个时间点热榜对比,清晰标注新进榜与掉榜话题
- 支持每小时或每日定时推送,无需手动触发
适用对象
- 🎬 短视频创作者 — 每天快速锁定热点方向,缩短选题时间
- 📣 MCN 机构运营 — 统一监控平台趋势,高效指导达人内容策略
- 🏷️ 品牌营销人员 — 发现可借势话题,提升热点响应速度
- 📚 运营新手 — 学习爆款标题套路,快速建立内容感知
---
功能特性
核心功能
- 实时热榜查询:获取抖音当前 TOP 50 热榜,包含排名、热度值、话题标题和跳转链接,每 60 分钟刷新
- 历史热榜回溯:支持查询近 7 天或近 30 天历史数据,可按日期指定查看特定某天
- 热榜对比:对比任意两个时间点的热榜排名与热度变化,自动标注新进榜与掉榜话题
- 创作者洞察报告:热榜展示后自动输出爆款选题机会、标题套路拆解、趋势预判与行动建议
- 可视化报告导出:一键生成紫色极简风格 HTML 页面,支持导出 PDF,每条话题可点击跳转
- 定时订阅推送:支持每小时或每日定时推送,自动追踪热点变化
---
密钥获取与安全说明
- 本技能需要使用环境变量:
REDFOX_API_KEY。 REDFOX_API_KEY由 红狐 hub (https://redfox.hk)提供。- 请前往 红狐 hub 注册账号,获取
REDFOX_API_KEY。 - 配置设备环境变量
REDFOX_API_KEY后使用本技能。 - 在提供密钥前,请先确认密钥来源、可用范围、有效期及是否支持重置/撤销。
- 禁止在代码、提示词、日志或输出文件中硬编码/明文暴露密钥。
---
使用指南
直接用自然语言描述需求,无需记忆命令。
常用说法速查
| 意图 | 示例话术 | 效果 |
|---|---|---|
| 查看实时热榜 | 抖音热榜 / 今日热榜 | 展示当前 TOP 20,含热度值与话题链接,附创作者洞察报告 |
| 查看昨日热榜 | 昨日热榜 | 查询昨天全天热榜数据 |
| 查看历史热榜 | 近7天热榜 / 近30天热榜 | 按时间范围回溯历史热点 |
| 查看指定日期 | 5月15日热榜 | 查询特定日期的榜单数据 |
| 加载完整榜单 | 继续加载 / 加载更多 | 从 TOP 20 扩展至完整 TOP 50 |
| 对比热榜变化 | 对比昨天和今天的热榜 | 输出两日热榜差异,标注新进榜与掉榜话题 |
| 订阅推送 | 订阅每日热榜推送 | 设置定时自动推送,每日获取最新热榜 |
| 取消订阅 | 取消订阅 | 停止定时推送 |
输出示例
查询「今日热榜」后,你将收到:
1. 热榜表格:序号、话题标题、热度值、跳转链接(TOP 20,可继续加载至 50) 2. 创作者洞察报告:
- 🔥 爆款选题机会(可迁移话题与创作方向)
- 🎯 标题套路拆解(情绪词、句式结构)
- 📈 趋势预判(哪些话题正在上升/下降)
- 💡 行动建议(可执行的内容策略)
3. 可视化报告:自动生成 HTML 页面,支持导出 PDF
---
使用场景
| 场景 | 角色 | 示例问法 | 收益 |
|---|---|---|---|
| 每日选题灵感 | 短视频创作者 | 「今日热榜」 | 快速锁定热点方向,获取可迁移选题与标题公式 |
| 热点趋势监控 | MCN 运营人员 | 「近7天热榜」 | 掌握一周热点演变规律,为达人提供内容策略参考 |
| 热点借势营销 | 品牌营销经理 | 「对比本周热榜变化」 | 发现持续发酵话题,提前布局品牌借势内容 |
| 运营学习研究 | 短视频新手 | 「今日热榜,帮我分析标题套路」 | 学习爆款句式结构,快速建立内容感知 |
---
核心工作流(Agent 执行参考)
本文档为 Agent 执行层的详细工作流指令,包含数据获取、展示、分析、订阅、HTML 生成等完整步骤。
任务目标
- 获取抖音平台热榜数据,并提供创作者视角的深度洞察
- 触发条件:用户询问抖音热榜、热门话题、热搜榜单、历史热榜、订阅热榜推送等
操作步骤
1. 获取热榜数据
Python 执行说明:
- 优先使用系统默认
python命令 - 若
python不可用(Windows Git Bash 常见),使用 WorkBuddy 管理的 Python 全路径:"C:/Users/马祯/.workbuddy/binaries/python/versions/3.13.12/python.exe" - 始终先
cd到技能根目录再执行
1.1 实时热榜(默认)
cd "C:/Users/马祯/.workbuddy/skills/douyin-hot-trend" && python scripts/hotspot_fetcher.py1.2 历史热榜查询
支持查询近7天、近30天的历史热榜数据:
# 查询昨日热榜(假设今天是2026-04-16)
cd "C:/Users/马祯/.workbuddy/skills/douyin-hot-trend" && python scripts/hotspot_fetcher.py --start-date 2026-04-15 --end-date 2026-04-16
# 查询近7天热榜(假设今天是2026-04-16)
cd "C:/Users/马祯/.workbuddy/skills/douyin-hot-trend" && python scripts/hotspot_fetcher.py --start-date 2026-04-09 --end-date 2026-04-16
# 查询近30天热榜(假设今天是2026-04-16)
cd "C:/Users/马祯/.workbuddy/skills/douyin-hot-trend" && python scripts/hotspot_fetcher.py --start-date 2026-03-17 --end-date 2026-04-16
# 查询4月1日热榜
cd "C:/Users/马祯/.workbuddy/skills/douyin-hot-trend" && python scripts/hotspot_fetcher.py --start-date 2026-04-01 --end-date 2026-04-02日期范围规则:
- 日期范围是 [start_date, end_date) 左闭右开区间
- 例如:
start-date 2026-04-01 --end-date 2026-04-02查询的是4月1日当天的数据 - 例如:
start-date 2026-04-09 --end-date 2026-04-16查询的是4月9日至4月15日共7天的数据
参数说明:
--start-date:开始日期(包含),格式 YYYY-MM-DD--end-date:结束日期(不包含),格式 YYYY-MM-DD--days:查询天数,自动计算日期范围(end_date为今天)- 最长查询范围:30天
1.3 意图判断逻辑
根据用户意图自动选择查询方式(假设今天日期为T):
| 用户表达 | start_date | end_date | 查询范围 |
|---|---|---|---|
| "今日热榜" / "最新热榜" / "热榜" | 无(实时) | 无(实时) | 实时 |
| "昨日热榜" / "昨天热榜" | T-1 | T | 昨日当天 |
| "近7天热榜" / "一周热榜" | T-7 | T | 近7天 |
| "近30天热榜" / "本月热榜" | T-30 | T | 近30天 |
| "X月X日热榜" | X月X日 | X月X日+1 | 指定日期当天 |
2. 展示热榜数据表格
将脚本返回的JSON数据转换为表格形式展示。
标题格式:
实时热榜:抖音实时热榜(2026-04-14 16:00 每小时更新)
历史热榜:抖音历史热榜(2026-04-14 至 2026-04-15)
表格格式(默认展示TOP20数据):
| 排名 | 热度值 | 话题 | 核心内容 |
|---|
表格绘制规则: 1. 排名:默认 TOP01 到 TOP20(双数字符);加载更多 TOP21 到 TOP50 2. 热度值:保留一位小数,单位统一为 w(小写),示例 1109.6w 3. 话题:使用 Markdown 链接格式 [标题](URL) 4. 核心内容:30-80字,概括话题主要内容
加载更多:
表格下方展示:
抖音实时热榜为你提供TOP50的数据,是否继续加载剩余30条?
- 确认后仅输出 TOP21-TOP50,不重复 TOP1-20
- 确认后必须生成新的 HTML 文件,包含完整 TOP50 数据
3. 创作者洞察报告
展示数据后自动输出创作者视角的深度洞察:
3.1 爆款选题机会
分析热榜内容的共同规律,提供可迁移的选题建议。
输出格式:
📌 爆款选题机会
发现:[核心发现]
- 数据支撑:[具体数据]
- 核心公式:[拆解出的公式]
🔄 可迁移选题:
· 美妆赛道:[具体选题建议]
· 穿搭赛道:[具体选题建议]
· 其他赛道:[具体选题建议]3.2 标题套路破解
提炼爆款标题模式和情绪触发词。
输出格式:
✏️ 标题套路破解
爆款标题模式TOP3:
1. [类型名称]:[示例]
2. [类型名称]:[示例]
3. [类型名称]:[示例]
立即可用的标题公式:
· "用[方法],[效果]" → 例:用万能旅行拍照姿势美美出片
· "耗时[时间],拍下[成果]" → 例:耗时三年拍下古诗词里的中国
· "原来[认知],真的存在" → 例:原来古诗词里的河南真的存在
情绪触发词:
[高频词列表]3.3 趋势预判与行动建议
基于热榜数据给出前瞻性判断。
输出格式:
📈 趋势预判
一、[预判点1]
[具体分析内容和依据]
行动建议:[具体建议]
二、[预判点2]
[具体分析内容和依据]
行动建议:[具体建议]
三、[预判点3]
[具体分析内容和依据]
行动建议:[具体建议]3.4 分析原则
- 基于实际数据,不编造
- 给出可执行的行动建议,不泛泛而谈
- 从创作者视角出发,每条洞察都要回答"怎么用"
- 保持专业但易懂,避免术语堆砌
4. 询问订阅
分析完成后必须主动询问是否需要订阅。
🔔 订阅服务
是否需要订阅每日/每小时热榜推送?
1. 每小时推送 - 实时追踪热点变化(默认推送TOP50完整数据)
2. 每日推送 - 每天早/晚获取一次热榜汇总(默认推送TOP50完整数据)
3. 暂不需要 - 仅本次查询
请回复数字或"取消",如有其他推送时间偏好请说明。订阅处理流程:
- 用户选择1(每小时推送):记录偏好,后续每小时自动推送TOP50数据
- 用户选择2(每日推送):询问具体推送时间(早/晚),记录偏好,推送TOP50数据
- 用户选择3或"取消":结束本次交互,不记录
- 用户有其他偏好:灵活记录并确认
订阅数据默认推送TOP50完整数据;如用户特别要求TOP20,按用户要求执行。
5. 生成热榜HTML页面(强制执行)
HTML文件生成是必须执行的强制步骤,不等待用户回复,立即执行。
执行顺序:获取数据(保存JSON) → 展示表格 → 创作者洞察 → 询问订阅 → 立即执行HTML生成(从JSON读取)
关键优化:HTML生成器通过 --json-file 参数直接读取第一步保存的JSON文件,不再重复调用API。确保 API 调用次数始终为 1 次。
触发场景:
| 场景 | HTML数据范围 | 文件命名 |
|---|---|---|
| 查询实时热榜 | TOP20(严格20条) | douyin_hot_YYYYMMDD_HHMM.html |
| 查询历史热榜 | TOP20(严格20条) | douyin_hot_YYYYMMDD_YYYYMMDD.html |
| 用户加载更多 | TOP50(严格50条) | douyin_hot_top50_YYYYMMDD_HHMM.html |
HTML数据展示规则:
- 默认展示TOP20,仅用户明确要求时展示TOP50
- 数据条数严格控制:TOP20模式20条,TOP50模式50条
- 禁止自动扩展数据范围
执行流程(两步,共1次API调用):
# 步骤1:获取数据并保存为JSON文件
cd "C:/Users/马祯/.workbuddy/skills/douyin-hot-trend" && python scripts/hotspot_fetcher.py > scripts/temp_hot_data.json
# 步骤2:从JSON文件生成HTML(不再调用API)
# 实时热榜(TOP20)
cd "C:/Users/马祯/.workbuddy/skills/douyin-hot-trend" && python scripts/gen_douyin_hot_html.py --json-file scripts/temp_hot_data.json --output scripts/douyin_hot_20260529_1000.html
# 加载更多(TOP50)
cd "C:/Users/马祯/.workbuddy/skills/douyin-hot-trend" && python scripts/gen_douyin_hot_html.py --json-file scripts/temp_hot_data.json --top 50 --output scripts/douyin_hot_top50_20260529_1000.htmlAgent执行注意事项:
- 第一步获取数据时,将
hotspot_fetcher.py的 stdout 输出重定向保存到临时JSON文件 - 第二步 HTML 生成时,必须使用
--json-file参数指向临时JSON文件,禁止省略该参数 - 临时 JSON 文件可在 HTML 生成后保留或清理,不影响功能
文件命名规则:
- 实时热榜:
douyin_hot_YYYYMMDD_HHMM.html - 历史热榜:
douyin_hot_起始日期_结束日期.html - 加载更多:
douyin_hot_top50_YYYYMMDD_HHMM.html
加载更多场景:仅输出TOP21-TOP50表格,立即生成新HTML文件(含完整TOP50)。
HTML输出要求:
- 必须直接输出完整HTML文件内容(使用代码块展示HTML源码)
- 同时告知文件路径
- 无论实时还是历史查询,都必须输出完整HTML
HTML 交付与预览流程(强制执行):
1. 生成 HTML:执行 gen_douyin_hot_html.py 脚本,确认 stderr 输出 ✅ 已生成 和 📊 共 X 条 2. 尝试预览:调用 preview_url 工具,传入 file:///C:/Users/马祯/.workbuddy/skills/douyin-hot-trend/scripts/文件名.html 3. 交付文件:调用 deliver_attachments 工具,将 HTML 文件作为附件交付给用户 4. 预览失败备用方案:若 preview_url 因中文路径或 file:// 协议限制未能正常渲染,提示用户直接双击打开交付的 HTML 文件。不要反复重试 preview_url
预览注意事项:
- Windows 中文用户名路径(如
马祯)可能导致file://协议预览失败,这是已知限制 deliver_attachments交付的文件用户可直接下载并在浏览器中打开,不受路径编码影响- 优先确保文件交付成功,预览仅作为辅助手段
PDF输出要求:
- 必须输出PDF文件
- 生成方式:先生成HTML,再转换为PDF
- PDF内容支持点击跳转到对应话题页面
数据一致性要求:
- HTML数据必须完全来自脚本执行的API返回数据
- 禁止修改排名、热度值、话题标题、跳转链接
- 禁止重新排序、美化或编造数据
- HTML中每条数据必须与表格输出完全一致
HTML统计数据:
- 话题总数 = 实际展示数据条数(TOP20或TOP50)
- 最高热度 = 所有展示热度值的最大值
- 平均热度 = 所有展示热度值的平均值
- 禁止编造统计数据
6. 可选操作
- 用户可点击标题直接跳转到对应热点页面
- 可根据热度值排序或筛选特定话题
- 可随时发送"取消订阅"停止推送
资源索引
- 数据获取脚本:
scripts/hotspot_fetcher.py— 获取抖音热榜JSON数据 - HTML生成脚本:
scripts/gen_douyin_hot_html.py— 生成热榜可视化HTML页面 - HTML模板文件:
assets/douyin_hot_trend_template.html— HTML页面模板,支持占位符快速生成
模板使用说明
douyin_hot_trend_template.html 支持以下占位符:
| 占位符 | 说明 | 示例值 |
|---|---|---|
{{PAGE_TITLE}} | 页面标题 | 抖音实时热榜 |
{{FETCH_TIME}} | 获取时间 | 2026-04-17 10:00:00 |
{{HOT_LIST_DATA}} | 热榜数据JSON | [{...}, {...}] |
将占位符替换为实际数据即可生成完整HTML页面。
注意事项
- API调用需要网络连接
- 热榜数据实时更新,每次调用获取最新数据
- 历史热榜最长查询范围为30天
- 如遇API调用失败,提示用户稍后重试
- 订阅偏好需要用户明确确认后记录
- 分析必须基于实际数据,给出可执行的行动建议
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
抖音热榜HTML生成器
从JSON文件读取热榜数据,生成可独立打开的HTML页面。
用法:
python gen_douyin_hot_html.py --json-file data.json --output output.html
python gen_douyin_hot_html.py --json-file data.json --output output.html --top 50
数据来源优先级:
1. --json-file 参数:从指定JSON文件读取(推荐,避免重复API调用)
2. --start-date / --end-date / --days 参数:自行调用API获取(兼容旧用法)
样式特性:
- 紫色系极简风格(#6c5ce7 / #a29bfe)
- 卡片式表格(border-collapse: separate,每行独立圆角白卡)
- TOP3 奖牌徽章 + 对应色竖线边框
- 4+ 序号深灰小字
- 热度值纯紫色 #6c5ce7
- 整行点击跳转(location.href,兼容 file:// 协议)
- 导出 PDF 功能(仅截取内容区,不含按钮栏,单页 A4 自适应缩放)
- 页面最大宽度 750px
"""
import json
import sys
import os
import re
from datetime import datetime, timedelta
import requests
def get_api_key():
"""
获取 REDFOX_API_KEY,按三级优先级回退:
1. 从当前设备环境变量 REDFOX_API_KEY 获取
2. 从 shell 配置文件(~/.bashrc / ~/.bash_profile / ~/.zshrc)中读取
3. 提示用户配置
Returns:
str: API Key 字符串
Raises:
SystemExit: 未能获取到有效的 API Key
"""
# 第一级:从环境变量获取
api_key = os.getenv("REDFOX_API_KEY")
if api_key and api_key.strip():
return api_key.strip()
# 第二级:从 shell 配置文件读取
home = os.path.expanduser("~")
shell_configs = [
os.path.join(home, ".bashrc"),
os.path.join(home, ".bash_profile"),
os.path.join(home, ".zshrc"),
]
for config_path in shell_configs:
if os.path.isfile(config_path):
try:
with open(config_path, "r", encoding="utf-8", errors="ignore") as f:
content = f.read()
match = re.search(
r'export\s+REDFOX_API_KEY\s*=\s*["\']?([a-zA-Z0-9_]+)["\']?',
content
)
if match:
api_key = match.group(1).strip()
if api_key:
return api_key
except Exception:
continue
# 第三级:提示用户配置
raise ValueError(
"缺少 REDFOX_API_KEY 配置。"
"请设置环境变量 REDFOX_API_KEY=ak_xxxxxxxx,"
"或将其写入 shell 配置文件(~/.bashrc / ~/.bash_profile / ~/.zshrc)。"
"访问 https://redfox.hk/login 注册账号,在个人中心获取 API Key。"
)
def fetch_douyin_hotspot(start_date=None, end_date=None, days=None):
"""获取抖音热榜数据 - 使用原生 requests,API Key 三级回退"""
# 获取 API Key(三级回退)
credential = get_api_key()
# 构建请求URL和参数
url = "https://redfox.hk/story/api/hotSpot/getListByPlatform"
params = {
"platform": 2,
"source": "抖音热榜-GitHub"
}
query_type = "实时"
if days:
today = datetime.now().date()
end_date_obj = today
start_date_obj = today - timedelta(days=days)
params["startDate"] = start_date_obj.strftime("%Y-%m-%d")
params["endDate"] = end_date_obj.strftime("%Y-%m-%d")
query_type = f"近{days}天"
if start_date and end_date:
params["startDate"] = start_date
params["endDate"] = end_date
query_type = f"{start_date} 至 {end_date}"
# 构建请求头
headers = {
"X-API-KEY": credential,
"Accept": "application/json, text/plain, */*",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
response = requests.get(url, params=params, headers=headers, timeout=30)
if response.status_code >= 400:
raise Exception(f"HTTP请求失败: {response.status_code}, {response.text}")
api_response = response.json()
if isinstance(api_response, dict):
data = api_response.get("data", api_response.get("list", []))
elif isinstance(api_response, list):
data = api_response
else:
data = []
# 处理数据:去除标题中的所有空格(半角空格、全角空格、制表符、换行符等)
for item in data:
if 'title' in item and item['title']:
item['title'] = ''.join(item['title'].split())
if 'word' in item and item['word']:
item['word'] = ''.join(item['word'].split())
return {
"fetch_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"query_type": query_type,
"hot_list": data
}
def generate_desc(title, hot_count):
"""根据标题和热度值生成核心内容摘要(30-80字)"""
import re
desc = title
hot_str = f"热度{hot_count}"
# 核心内容生成规则 - 基于标题关键词匹配
if any(k in title for k in ['春天', '春日', '花开', '樱花', '踏青']):
return f"春季相关话题持续升温,用户分享春日生活美好瞬间与旅行记录,展现春日美景与生活方式,引发大量互动讨论,当前{hot_str}"
elif any(k in title for k in ['裙摆', '穿搭', '时尚', '美妆', '妆容', '造型']):
return f"时尚穿搭类话题火爆,博主分享穿搭技巧与造型灵感,年轻用户积极参与模仿创作,带动相关话题持续走高,当前{hot_str}"
elif any(k in title for k in ['海洋', '科技', '技术', '突破', '创新', '研发']):
return f"硬核科技/工业成就引发全民关注,展现中国技术实力与发展成果,网友纷纷点赞转发表达民族自豪感,当前{hot_str}"
elif any(k in title for k in ['骑马', '公主', '古装', '汉服', 'cosplay']):
return f"古风/角色扮演类内容走红,创作者通过特色造型吸引关注,用户参与度高,评论区互动热烈,当前{hot_str}"
elif any(k in title for k in ['对镜', '自拍', '拍照', '镜头', '摄影']):
return f"摄影/自拍技巧类内容广受欢迎,创作者分享实用拍摄方法,帮助普通用户提升出片质量,传播度极高,当前{hot_str}"
elif any(k in title for k in ['出游', '旅行', '旅游', '景点', '打卡']):
return f"旅游出行话题热度攀升,各地景点迎来游客高峰,用户分享旅行攻略与见闻,激发更多人规划行程,当前{hot_str}"
elif any(k in title for k in ['赖清德', '弹劾', '政治', '政策', '政府', '官员']):
return f"时政类重大事件引发全网热议,各平台讨论量激增,用户密切关注事态发展,相关分析解读视频获得高播放,当前{hot_str}"
elif any(k in title for k in ['房价', '楼市', '房贷', '购房', '经济', 'GDP', '股市', 'A股']):
return f"财经/民生话题牵动大众神经,专业机构与个人投资者高度关注市场动态,各类解读分析内容刷屏,当前{hot_str}"
elif any(k in title for k in ['电影', '剧集', '开播', '定档', '综艺', '选秀', '歌手']):
return f"影视娱乐类话题霸榜,新作品/节目上线引发追剧热潮,明星动态与剧情讨论占据热搜前列,当前{hot_str}"
elif any(k in title for k in ['游戏', '电竞', '比赛', '战队', '选手']):
return f"游戏/电竞赛事话题火热,职业赛事精彩操作被广泛传播,玩家社区讨论氛围活跃,相关二创内容爆发式增长,当前{hot_str}"
elif any(k in title for k in ['美食', '做饭', '食谱', '奶茶', '探店', '餐厅']):
return f"美食类内容持续吸睛,创作者推荐美食做法与探店体验,激发用户尝试欲望与打卡热情,评论区求教程留言众多,当前{hot_str}"
elif any(k in title for k in ['萌宠', '猫咪', '狗狗', '动物', '可爱']):
return f"宠物/动物类治愈系内容广受喜爱,萌宠日常视频轻松获取百万播放,用户在评论区晒出自家毛孩子照片,互动率极高,当前{hot_str}"
elif any(k in title for k in ['健身', '减肥', '运动', '瑜伽', '瘦身']):
return f"健康运动话题受关注度提升,健身达人分享训练计划与饮食建议,激励大批用户开启锻炼模式,跟练打卡成风潮,当前{hot_str}"
elif any(k in title for k in ['教育', '高考', '考研', '学校', '老师', '家长']):
return f"教育相关话题引发家长群体强烈共鸣,升学政策与学习方法的讨论热度居高不下,干货分享型内容获大量收藏,当前{hot_str}"
elif any(k in title for k in ['恋爱', '感情', '婚姻', '分手', '相亲']):
return f"情感婚恋话题触动年轻人共鸣,真实故事分享与情感分析视频引发深度讨论,用户在评论区倾诉经历寻求建议,当前{hot_str}"
elif any(k in title for k in ['职场', '工资', '面试', '辞职', '老板', '打工']):
return f"职场话题直击打工人痛点,薪资待遇、工作体验等议题引发广泛共鸣,职场经验分享内容获高收藏转发,当前{hot_str}"
else:
# 兜底:基于标题长度智能扩展
return f"该话题在抖音平台引发广泛关注与讨论,大量创作者围绕此主题产出优质内容,用户互动活跃,相关视频播放量持续增长,当前{hot_str}"
def generate_html(result, top_n=20):
"""生成HTML页面 - 紫色极简风格
Args:
result: 热榜数据结果
top_n: 显示条数,默认20,可设为50
重要:只传递实际需要展示的数据到HTML,确保统计数据与展示数据一致
"""
hot_list = result["hot_list"]
fetch_time = result["fetch_time"]
query_type = result["query_type"]
# 限制显示条数 - 必须先截取,确保统计数据准确
top_n = min(top_n, 50) # 最大支持TOP50
# ⚠️ 关键:只保留实际需要展示的数据,确保HTML中统计数据与展示数据一致
hot_list = hot_list[:top_n]
def fmt_hot_value(n):
"""格式化热度值 - 保持与表格显示一致"""
n = int(n or 0)
if n >= 100000000:
return f"{n / 100000000:.1f}亿"
if n >= 10000:
# 保留一位小数,如 1109.6w
return f"{n / 10000:.1f}w"
return str(n)
# 为每条数据生成核心内容摘要和格式化热度值
for item in hot_list:
# 生成核心内容摘要
if not item.get('desc') and not item.get('excerpt'):
title = item.get('title', '') or item.get('word', '')
hot_count = item.get('hotCount', '') or item.get('hotValue', '0')
item['_genDesc'] = generate_desc(title, fmt_hot_value(hot_count))
# 格式化热度值,确保HTML显示与智能体表格一致
hot_count = item.get('hotCount', '') or item.get('hotValue', '0')
item['_fmtHeat'] = fmt_hot_value(hot_count)
js_data = json.dumps(hot_list, ensure_ascii=False, indent=2)
if query_type == "实时":
page_title = "抖音实时热榜"
else:
page_title = f"抖音热榜({query_type})"
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: #f0f0f5;
color: #333;
line-height: 1.6;
}}
/* ===== 页面容器 - 最大宽度750px ===== */
.page-wrap {{
max-width: 750px;
margin: 0 auto;
padding: 16px 16px 32px;
}}
/* ===== 导出按钮栏 - 不参与PDF导出 ===== */
.export-bar {{
position: sticky;
top: 0;
z-index: 100;
display: flex;
justify-content: flex-end;
padding: 10px 0 12px;
margin-bottom: 4px;
}}
.btn-export-pdf {{
background: #fff;
color: #6c5ce7;
border: 1.5px solid #6c5ce7;
border-radius: 20px;
padding: 7px 22px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}}
.btn-export-pdf:hover {{ background: #f8f6ff; transform: translateY(-1px); }}
.btn-export-pdf:active {{ transform: translateY(0); }}
/* ===== PDF内容区域 ===== */
.pdf-content {{ background: transparent; }}
/* ===== 头部卡片 - 紫色渐变 ===== */
.hot-title-wrap {{
background: linear-gradient(135deg, #6c5ce7, #a29bfe);
border-radius: 14px;
padding: 24px 24px 20px;
margin-bottom: 18px;
color: #fff;
text-align: center;
}}
.hot-title-wrap h1 {{
font-size: 22px; font-weight: 800;
letter-spacing: 0.5px;
}}
.hot-update-time {{
margin-top: 4px;
font-size: 12.5px;
opacity: 0.85;
}}
/* 统计卡片区 */
.stats-row {{
display: flex;
justify-content: center;
gap: 16px;
margin-top: 16px;
}}
.stat-card {{
background: rgba(255,255,255,0.18);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-radius: 11px;
padding: 11px 20px;
min-width: 95px;
text-align: center;
}}
.stat-num {{ font-size: 21px; font-weight: 800; }}
.stat-label {{ font-size: 11.5px; opacity: 0.85; margin-top: 2px; }}
/* ===== 表格区域 ===== */
.table-area {{ background: transparent; }}
/* 卡片式表格 */
.hot-table {{
width: 100%;
border-collapse: separate;
border-spacing: 0 6px;
}}
.hot-table thead {{ display: none; }}
.hot-table tbody tr {{
background: #fff;
border-radius: 10px;
transition: all 0.2s ease;
cursor: pointer;
}}
.hot-table tbody tr:hover {{
transform: translateY(-1.5px);
box-shadow: 0 6px 20px rgba(108,92,231,0.15);
}}
.hot-table td {{
padding: 13px 14px;
vertical-align: middle;
border: none;
}}
.hot-table tr td:first-child {{ border-radius: 10px 0 0 10px; }}
.hot-table tr td:last-child {{ border-radius: 0 10px 10px 0; }}
/* 排名序号 */
.rank-cell {{ width: 58px; text-align: center; }}
/* TOP3 奖牌徽章 */
.rank-badge {{
display: inline-flex;
align-items: center;
justify-content: center;
width: 31px; height: 31px;
border-radius: 50%;
font-size: 14px;
font-weight: 800;
position: relative;
}}
/* TOP1 紫红 */
.rank-badge.top1 {{
background: linear-gradient(135deg, #e056a0, #a855f7);
color: #fff;
box-shadow: 0 2px 8px rgba(168,85,247,0.35);
}}
/* TOP2 蓝紫 */
.rank-badge.top2 {{
background: linear-gradient(135deg, #6c5ce7, #74b9ff);
color: #fff;
box-shadow: 0 2px 8px rgba(108,92,231,0.30);
}}
/* TOP3 浅紫 */
.rank-badge.top3 {{
background: linear-gradient(135deg, #a29bfe, #dfe6e9);
color: #6c5ce7;
box-shadow: 0 2px 8px rgba(162,155,254,0.35);
}}
/* 4+ 序号 */
.rank-normal {{
font-size: 13px;
font-weight: 700;
color: #555;
}}
/* TOP1-3 行左侧色条 + 背景色 */
.hot-table tbody tr.row-top1 {{ background: linear-gradient(90deg, rgba(168,85,247,0.07), #fff 6%); }}
.hot-table tbody tr.row-top2 {{ background: linear-gradient(90deg, rgba(108,92,231,0.07), #fff 6%); }}
.hot-table tbody tr.row-top3 {{ background: linear-gradient(90deg, rgba(162,155,254,0.09), #fff 6%); }}
.hot-table tbody tr.row-top1 > td:first-child {{ border-left: 3.5px solid #a855f7; }}
.hot-table tbody tr.row-top2 > td:first-child {{ border-left: 3.5px solid #6c5ce7; }}
.hot-table tbody tr.row-top3 > td:first-child {{ border-left: 3.5px solid #a29bfe; }}
/* 标题描述 */
.info-cell {{}}
.topic-title-link {{
text-decoration: none;
display: block;
}}
.topic-title-link:hover .topic-title {{
color: #6c5ce7;
}}
.topic-title {{
font-size: 14.5px;
font-weight: 650;
color: #222;
line-height: 1.45;
word-break: break-all;
transition: color 0.2s ease;
}}
.topic-desc {{
font-size: 12.5px;
color: #888;
margin-top: 3px;
line-height: 1.4;
word-break: break-all;
}}
/* 热度值 - 纯紫色 */
.heat-cell {{ text-align: right; white-space: nowrap; width: 110px; }}
.heat-value {{
font-size: 14px;
font-weight: 800;
color: #6c5ce7;
}}
/* 标签 */
.tag {{
display: inline-block;
font-size: 11px;
font-weight: 600;
padding: 2px 9px;
border-radius: 10px;
margin-right: 4px;
vertical-align: middle;
}}
.tag-hot {{ background: rgba(231,76,60,0.08); color: #e74c3c; }}
.tag-new {{ background: rgba(52,152,219,0.08); color: #3498db; }}
.tag-hot-rising {{ background: rgba(230,126,34,0.1); color: #e67e22; }}
.tag-descend {{ background: rgba(149,165,166,0.1); color: #7f8c8d; }}
.tag-steady {{ background: rgba(39,174,96,0.1); color: #27ae60; }}
/* 时间标签 */
.time-tag {{
font-size: 11.5px;
color: #999;
white-space: nowrap;
}}
/* 底部说明 */
.footer-note {{
text-align: center;
font-size: 11.5px;
color: #bbb;
margin-top: 20px;
padding: 10px 0;
}}
@media (max-width: 480px) {{
.page-wrap {{ padding: 10px 8px 24px; }}
.hot-title-wrap {{ padding: 18px 16px 16px; }}
.stats-row {{ gap: 8px; }}
.stat-card {{ padding: 8px 14px; min-width: 75px; }}
.stat-num {{ font-size: 18px; }}
.hot-table td {{ padding: 10px 10px; }}
.topic-title {{ font-size: 13.5px; }}
.heat-value {{ font-size: 15px; }}
}}
</style>
</head>
<body>
<div class="page-wrap">
<!-- 导出按钮(不包含在PDF内) -->
<div class="export-bar">
<button class="btn-export-pdf" onclick="exportPdf()">导出 PDF</button>
</div>
<!-- PDF内容区域 -->
<div class="pdf-content" id="pdfContent">
<!-- 头部 -->
<div class="hot-title-wrap">
<h1>🔥 {page_title}</h1>
<div class="hot-update-time">更新时间:{fetch_time}</div>
<div class="stats-row">
<div class="stat-card">
<div class="stat-num" id="totalCount">--</div>
<div class="stat-label">话题总数</div>
</div>
<div class="stat-card">
<div class="stat-num" id="maxHeat">--</div>
<div class="stat-label">最高热度</div>
</div>
<div class="stat-card">
<div class="stat-num" id="avgHeat">--</div>
<div class="stat-label">平均热度</div>
</div>
</div>
</div>
<!-- 表格 -->
<div class="table-area">
<table class="hot-table">
<thead>
<tr><th>排名</th><th>话题信息</th><th>热度值</th></tr>
</thead>
<tbody id="hotTableBody"></tbody>
</table>
</div>
<div class="footer-note">数据来源:抖音 · 仅供参考</div>
</div><!-- /pdf-content -->
</div><!-- /page-wrap -->
<script>
// 行点击跳转 - 使用 location.href 避免 file:// 下 window.open 被拦截
(function() {{
function bindRowClick() {{
var rows = document.querySelectorAll('.hot-table tbody tr[data-href]');
rows.forEach(function(row) {{
row.style.cursor = 'pointer';
row.addEventListener('click', function(e) {{
if (e.target.closest('button, a, .btn-export-pdf')) return;
var url = this.getAttribute('data-href');
if (url) window.location.href = url;
}});
}});
}}
if (document.readyState === 'loading') {{
document.addEventListener('DOMContentLoaded', bindRowClick);
}} else {{
bindRowClick();
}}
}})();
// 渲染热榜数据
(function() {{
var RAW = {js_data};
// 统计
var totalCount = RAW.length;
var maxH = 0, sumH = 0;
for (var i = 0; i < RAW.length; i++) {{
var h = parseInt(RAW[i].hotCount || RAW[i].hotValue || 0);
if (h > maxH) maxH = h;
sumH += h;
}}
var avgH = totalCount > 0 ? sumH / totalCount : 0;
function fmtHot(n) {{
n = parseInt(n || 0);
if (n >= 100000000) return (n / 100000000).toFixed(1) + '亿';
if (n >= 10000) return (n / 10000).toFixed(1) + 'w'; // 保留一位小数,如 1109.6w
return n.toString();
}}
// 更新统计
document.getElementById('totalCount').textContent = totalCount;
document.getElementById('maxHeat').textContent = fmtHot(maxH);
document.getElementById('avgHeat').textContent = fmtHot(Math.round(avgH));
// 保持API返回的原始排名顺序(不重新排序)
// ⚠️ 数据一致性要求:必须使用API返回的原始数据,禁止修改排名、热度、链接
// RAW.sort(function(a, b) {{ return (a.index || 999) - (b.index || 999); }});
// 渲染表格 - 显示TOP{top_n}
// ⚠️ 每条数据的URL必须是API返回的真实链接,禁止伪造或替换
// ⚠️ 热度值直接使用Python格式化后的值(_fmtHeat),确保与智能体表格一致
var html = '';
for (var i = 0; i < RAW.length && i < {top_n}; i++) {{
var d = RAW[i];
var rank = i + 1;
var title = d.title || d.word || '--';
// 直接使用Python格式化后的热度值,不再使用fmtHot函数
var heatDisplay = d._fmtHeat || fmtHot(d.hotCount || d.hotValue || 0);
var desc = d.desc || d.excerpt || d._genDesc || '';
// 链接必须使用API返回的真实URL
var url = d.url || d.schemeUrl || '#';
// TOP3 样式类
var rowCls = '', badgeHtml = '', tagHtml = '';
if (rank === 1) {{
rowCls = 'row-top1';
badgeHtml = '<span class="rank-badge top1">🥇</span>';
tagHtml = '<span class="tag tag-new">新</span>';
}} else if (rank === 2) {{
rowCls = 'row-top2';
badgeHtml = '<span class="rank-badge top2">🥈</span>';
tagHtml = '<span class="tag tag-hot">热</span>';
}} else if (rank === 3) {{
rowCls = 'row-top3';
badgeHtml = '<span class="rank-badge top3">🥉</span>';
tagHtml = '<span class="tag tag-hot-rising">升</span>';
}} else {{
badgeHtml = '<span class="rank-normal">' + rank + '</span>';
}}
html += '<tr class="' + rowCls + '" data-href="' + url + '">'
+ '<td class="rank-cell">' + badgeHtml + '</td>'
+ '<td class="info-cell">'
+ '<a class="topic-title-link" href="' + url + '" target="_blank" onclick="event.stopPropagation()"><div class="topic-title">' + title + '</div></a>'
+ '<div class="topic-desc">' + desc + '</div>'
+ '</td><td class="heat-cell">'
+ '<div class="heat-value">' + heatDisplay + '</div>';
if (tagHtml) {{
html += '<div style="margin-top:3px">' + tagHtml + '</div>';
}}
html += '</td></tr>';
}}
document.getElementById('hotTableBody').innerHTML = html;
// 重新绑定行点击事件(动态插入后)
var rows = document.querySelectorAll('.hot-table tbody tr[data-href]');
rows.forEach(function(row) {{
row.addEventListener('click', function(e) {{
if (e.target.closest('button, a, .btn-export-pdf')) return;
var u = this.getAttribute('data-href');
if (u && u !== '#') window.location.href = u;
}});
}});
}})();
// 导出PDF - 只截取 pdfContent 区域,单页自适应A4,支持链接跳转
function exportPdf() {{
var btn = document.querySelector('.btn-export-pdf');
btn.textContent = '生成中...';
btn.style.pointerEvents = 'none';
var target = document.getElementById('pdfContent');
// 收集所有链接信息 - 链接必须是API返回的真实URL
var links = [];
var rows = document.querySelectorAll('.hot-table tbody tr[data-href]');
rows.forEach(function(row, idx) {{
var url = row.getAttribute('data-href');
if (url && url !== '#') {{
links.push({{
url: url,
top: row.offsetTop,
height: row.offsetHeight,
idx: idx
}});
}}
}});
html2canvas(target, {{
scale: 2,
useCORS: true,
backgroundColor: '#f0f0f5',
logging: false,
windowWidth: target.scrollWidth,
windowHeight: target.scrollHeight
}}).then(function(canvas) {{
var imgData = canvas.toDataURL('image/png');
var pdf = new jspdf.jsPDF('p', 'mm', 'a4');
var pdfW = pdf.internal.pageSize.getWidth();
var pdfH = pdf.internal.pageSize.getHeight();
var margin = 10;
var contentW = pdfW - margin * 2;
var contentH = pdfH - margin * 2;
var imgW = contentW;
var imgH = (canvas.height * imgW) / canvas.width;
// 计算缩放比例
var scaleX = imgW / target.scrollWidth;
var scaleY = imgH / target.scrollHeight;
// 计算内容在PDF中的位置
var imgX, imgY;
if (imgH > contentH) {{
imgH = contentH;
imgW = (canvas.width * imgH) / canvas.height;
scaleX = imgW / target.scrollWidth;
scaleY = imgH / target.scrollHeight;
imgX = (contentW - imgW) / 2 + margin;
imgY = margin;
}} else {{
imgX = margin;
imgY = margin;
}}
// 添加图片
pdf.addImage(imgData, 'PNG', imgX, imgY, imgW, imgH);
// 添加可点击链接注解(覆盖整行)
// 注意:PDF阅读器对链接注解的支持不一,但这是PDF规范的标准方式
links.forEach(function(link) {{
// 转换坐标:HTML像素 -> PDF毫米
var pdfY = imgY + (link.top * scaleY);
var pdfH = Math.max(link.height * scaleY, 3); // 最小高度3mm确保可点击
var pdfX = imgX;
var pdfW = imgW;
// 使用 link 方法添加链接注解
// 这是PDF规范的标准方式,大多数现代PDF阅读器支持
pdf.link(pdfX, pdfY, pdfW, pdfH, {{ url: link.url }});
}});
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('{page_title}_' + dateStr + '.pdf');
btn.textContent = '导出 PDF';
btn.style.pointerEvents = '';
// 提示用户PDF链接功能
console.log('PDF已生成,包含' + links.length + '个可点击链接');
}}).catch(function(err) {{
alert('PDF 生成失败:' + err.message);
btn.textContent = '导出 PDF';
btn.style.pointerEvents = '';
}});
}}
</script>
</body>
</html>'''
return html
def load_result_from_json(json_path):
"""
从JSON文件读取热榜数据结果。
支持两种JSON格式:
1. 完整结果对象:{"fetch_time": "...", "query_type": "...", "hot_list": [...]}
2. 纯数组:[{"index": 1, "title": "...", "hotCount": "...", "url": "..."}, ...]
"""
with open(json_path, 'r', encoding='utf-8') as f:
content = f.read().strip()
data = json.loads(content)
if isinstance(data, list):
# 纯数组格式:包装成完整结果对象
return {
"fetch_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"query_type": "实时",
"hot_list": data
}
elif isinstance(data, dict):
# 完整结果对象:确保有 hot_list
if "hot_list" not in data:
# 可能是 {"data": [...]} 或 {"list": [...]} 格式
hot_list = data.get("data", data.get("list", []))
return {
"fetch_time": data.get("fetch_time", datetime.now().strftime("%Y-%m-%d %H:%M:%S")),
"query_type": data.get("query_type", "实时"),
"hot_list": hot_list
}
return data
else:
raise ValueError(f"不支持的JSON格式: {type(data)}")
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description='生成抖音热榜HTML页面')
parser.add_argument('--json-file', type=str, help='从JSON文件读取热榜数据(优先使用,避免重复API调用)')
parser.add_argument('--start-date', type=str, help='开始日期,格式 YYYY-MM-DD')
parser.add_argument('--end-date', type=str, help='结束日期,格式 YYYY-MM-DD')
parser.add_argument('--days', type=int, help='查询天数')
parser.add_argument('--output', type=str, help='输出文件路径')
parser.add_argument('--top', type=int, default=20, help='显示条数,默认20,可设为50')
args = parser.parse_args()
# 数据获取:优先从JSON文件读取,其次通过API获取
if args.json_file:
if not os.path.isfile(args.json_file):
print(f"❌ JSON文件不存在: {args.json_file}", file=sys.stderr)
sys.exit(1)
print(f"从JSON文件读取数据: {args.json_file}", file=sys.stderr)
result = load_result_from_json(args.json_file)
else:
print("正在获取抖音热榜数据...", file=sys.stderr)
result = fetch_douyin_hotspot(
start_date=args.start_date,
end_date=args.end_date,
days=args.days
)
html = generate_html(result, top_n=args.top)
if args.output:
output_path = args.output
else:
output_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "douyin_hot_trend.html")
with open(output_path, 'w', encoding='utf-8') as f:
f.write(html)
print(f"✅ 已生成:{output_path}", file=sys.stderr)
print(f"📊 共 {len(result['hot_list'])} 条热榜数据,展示TOP{args.top}", file=sys.stderr)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
抖音热榜数据获取脚本
功能:调用红狐数据热榜API获取抖音实时热点数据
接口:https://redfox.hk/story/api/hotSpot/getListByPlatform
参数:platform=2, source=抖音热榜-GitHub, startDate, endDate(可选)
方法:GET
认证:X-API-KEY(三级回退:环境变量 → shell配置文件 → 提示用户配置)
"""
import json
import os
import sys
import re
from datetime import datetime, timedelta
import requests
def get_api_key():
"""
获取 REDFOX_API_KEY,按三级优先级回退:
1. 从当前设备环境变量 REDFOX_API_KEY 获取
2. 从 shell 配置文件(~/.bashrc / ~/.bash_profile / ~/.zshrc)中读取
3. 提示用户配置
Returns:
str: API Key 字符串
Raises:
SystemExit: 未能获取到有效的 API Key
"""
# 第一级:从环境变量获取
api_key = os.getenv("REDFOX_API_KEY")
if api_key and api_key.strip():
return api_key.strip()
# 第二级:从 shell 配置文件读取
home = os.path.expanduser("~")
shell_configs = [
os.path.join(home, ".bashrc"),
os.path.join(home, ".bash_profile"),
os.path.join(home, ".zshrc"),
]
for config_path in shell_configs:
if os.path.isfile(config_path):
try:
with open(config_path, "r", encoding="utf-8", errors="ignore") as f:
content = f.read()
match = re.search(
r'export\s+REDFOX_API_KEY\s*=\s*["\']?([a-zA-Z0-9_]+)["\']?',
content
)
if match:
api_key = match.group(1).strip()
if api_key:
return api_key
except Exception:
continue
# 第三级:提示用户配置
error_msg = {
"error": "缺少 REDFOX_API_KEY 配置",
"hint": "请设置环境变量 REDFOX_API_KEY=ak_xxxxxxxx,或将其写入 shell 配置文件(~/.bashrc / ~/.bash_profile / ~/.zshrc)",
"guide": "访问 https://redfox.hk/login 注册账号,在个人中心获取 API Key"
}
print(json.dumps(error_msg, ensure_ascii=False))
sys.exit(1)
def fetch_douyin_hotspot(start_date=None, end_date=None, days=None):
"""
获取抖音热榜数据
使用原生 requests 发起请求,X-API-KEY 通过三级回退获取
Args:
start_date: 开始日期,格式 YYYY-MM-DD
end_date: 结束日期,格式 YYYY-MM-DD
days: 查询天数,如7表示近7天,30表示近30天
Returns:
None (结果直接打印到标准输出)
"""
# 获取 API Key(三级回退)
credential = get_api_key()
# 构建请求URL
url = "https://redfox.hk/story/api/hotSpot/getListByPlatform"
# 构建请求参数
params = {
"platform": 2,
"source": "抖音热榜-GitHub"
}
# 处理日期参数
query_type = "实时"
if days:
today = datetime.now().date()
end_date_obj = today
start_date_obj = today - timedelta(days=days)
params["startDate"] = start_date_obj.strftime("%Y-%m-%d")
params["endDate"] = end_date_obj.strftime("%Y-%m-%d")
query_type = f"近{days}天"
if start_date and end_date:
params["startDate"] = start_date
params["endDate"] = end_date
query_type = f"{start_date} 至 {end_date}"
# 构建请求头
headers = {
"X-API-KEY": credential,
"Accept": "application/json, text/plain, */*",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
try:
response = requests.get(url, params=params, headers=headers, timeout=30)
if response.status_code >= 400:
raise Exception(f"HTTP请求失败: {response.status_code}, {response.text}")
api_response = response.json()
# 处理不同的响应格式
if isinstance(api_response, dict):
if "data" in api_response:
data = api_response["data"]
elif "list" in api_response:
data = api_response["list"]
else:
data = []
elif isinstance(api_response, list):
data = api_response
else:
data = []
# 提取并格式化热榜数据
if isinstance(data, list):
now = datetime.now()
fetch_time = now.strftime("%Y-%m-%d %H:00")
result = {
"fetch_time": fetch_time,
"query_type": query_type,
"start_date": start_date,
"end_date": end_date,
"hot_list": []
}
for item in data:
# 处理标题:去除所有空格(半角空格、全角空格、制表符、换行符等)
title = item.get("title", "")
if title:
title = ''.join(title.split())
result["hot_list"].append({
"index": item.get("index"),
"title": title,
"hotCount": item.get("hotCount", ""),
"url": item.get("url", "")
})
print(json.dumps(result, ensure_ascii=False, indent=2))
else:
print(json.dumps([], ensure_ascii=False))
except requests.exceptions.RequestException as e:
error_msg = {"error": f"请求失败: {str(e)}"}
print(json.dumps(error_msg, ensure_ascii=False))
sys.exit(1)
except Exception as e:
error_msg = {"error": f"错误: {str(e)}"}
print(json.dumps(error_msg, ensure_ascii=False))
sys.exit(1)
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description='获取抖音热榜数据')
parser.add_argument('--start-date', type=str, help='开始日期,格式 YYYY-MM-DD')
parser.add_argument('--end-date', type=str, help='结束日期,格式 YYYY-MM-DD')
parser.add_argument('--days', type=int, help='查询天数,如7表示近7天,30表示近30天')
args = parser.parse_args()
fetch_douyin_hotspot(
start_date=args.start_date,
end_date=args.end_date,
days=args.days
)