
Daily Hot News
- 673 installs
- 11 repo stars
- Updated February 6, 2026
- one-box-u/openclaw-daily-hot-news
daily-hot-news is an OpenClaw skill that instantly pulls trending topics and hot lists from 54 Chinese platforms including Weibo, Zhihu, Bilibili, and Toutiao for developers who need localized trend data, history, and sc
About
daily-hot-news is an OpenClaw skill from one-box-u/openclaw-daily-hot-news that queries hot-ranking data from 54 Chinese platforms including Weibo, Zhihu, Bilibili, Douyin, and Toutiao via a local DailyHotApi service. It supports categorized source browsing, daily history persistence, on-demand hot-list queries, and scheduled pushes to Feishu. Developers reach for daily-hot-news when building agents or automations that need current Chinese social trend signals for content planning, community monitoring, or distribution timing. Installation requires python3 plus requests and aiohttp dependencies, and the skill wraps the open-source DailyHotApi project for localized hot-list access.
- Queries hot lists from 54 distinct platforms
- Supports category browsing of all available sources
- Automatically saves daily hot list records
- Enables scheduled push delivery to Feishu
- Runs a local DailyHotApi service for fast aggregation
Daily Hot News by the numbers
- 673 all-time installs (skills.sh)
- +3 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #617 of 3,282 Productivity & Planning skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/one-box-u/openclaw-daily-hot-news --skill daily-hot-newsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 673 |
|---|---|
| repo stars | ★ 11 |
| Security audit | 1 / 3 scanners passed |
| Last updated | February 6, 2026 |
| Repository | one-box-u/openclaw-daily-hot-news ↗ |
How do you fetch Chinese platform hot lists in Python?
Instantly pull trending topics and hot lists from 54 Chinese platforms including Weibo, Zhihu, Bilibili, and Toutiao.
Who is it for?
Developers building Chinese-market content agents who need automated hot-list queries across Weibo, Zhihu, Bilibili, and 51 other platforms.
Skip if: Developers needing global English news APIs or deep article scraping should skip daily-hot-news because it only aggregates Chinese platform hot rankings.
When should I use this skill?
Trigger when an agent needs current Chinese social hot lists, trend history, or scheduled Feishu hot-rank pushes.
What you get
Hot-list JSON results, categorized source catalogs, daily history archives, and scheduled Feishu trend pushes
- Hot-list query results
- Daily history archives
- Scheduled Feishu notifications
By the numbers
- Supports 54 Chinese hot-list platforms
- Requires python3 with requests and aiohttp dependencies
Files
🔥 每日热榜
🎯 概述
提供 54 个热榜源 的本地化查询服务,基于 DailyHotApi 项目。
核心功能:
- 📊 热榜查询 - 查询任意平台的热榜数据
- 📋 分类浏览 - 列出所有支持的热榜源
- 💾 历史记录 - 自动保存每日热榜数据
- ⏰ 定时推送 - 自动推送热榜到飞书
---
🏗️ 架构设计
用户请求 → DailyHotApi Skill → 本地 DailyHotApi 服务 → 返回格式化结果组件说明
| 组件 | 职责 |
|---|---|
| DailyHotApi 服务 | 独立运行,抓取/聚合热榜数据 |
| DailyHotApi Skill | OpenClaw 插件,处理用户请求 |
---
📡 支持的热榜源(54个)
🎬 视频/直播平台
| 接口 | 名称 |
|---|---|
| bilibili | 哔哩哔哩 |
| acfun | AcFun |
| douyin | 抖音 |
| kuaishou | 快手 |
| coolapk | 酷安 |
💬 社交媒体
| 接口 | 名称 |
|---|---|
| 微博 | |
| zhihu | 知乎 |
| zhihu-daily | 知乎日报 |
| tieba | 百度贴吧 |
| douban-group | 豆瓣讨论小组 |
| v2ex | V2EX |
| ngabbs | NGA |
| hupu | 虎扑 |
📰 新闻资讯
| 接口 | 名称 |
|---|---|
| baidu | 百度热搜 |
| thepaper | 澎湃新闻 |
| toutiao | 今日头条 |
| 36kr | 36氪 |
| qq-news | 腾讯新闻 |
| sina | 新浪网 |
| sina-news | 新浪新闻 |
| netease-news | 网易新闻 |
| huxiu | 虎嗅 |
| ifanr | 爱范儿 |
💻 科技/技术社区
| 接口 | 名称 |
|---|---|
| ithome | IT之家 |
| ithome-xijiayi | IT之家「喜加一」 |
| sspai | 少数派 |
| csdn | CSDN |
| juejin | 稀土掘金 |
| 51cto | 51CTO |
| nodeseek | NodeSeek |
| hellogithub | HelloGitHub |
🎮 游戏/ACG
| 接口 | 名称 |
|---|---|
| genshin | 原神 |
| miyoushe | 米游社 |
| honkai | 崩坏3 |
| starrail | 崩坏:星穹铁道 |
| lol | 英雄联盟 |
📚 阅读/文化
| 接口 | 名称 |
|---|---|
| jianshu | 简书 |
| guokr | 果壳 |
| weread | 微信读书 |
| douban-movie | 豆瓣电影 |
🔧 工具/其他
| 接口 | 名称 |
|---|---|
| 52pojie | 吾爱破解 |
| hostloc | 全球主机交流 |
| weatheralarm | 中央气象台 |
| earthquake | 中国地震台 |
| history | 历史上的今天 |
---
🚀 部署说明
1. PM2 方式管理(推荐)
DailyHotApi 服务使用 PM2 管理,确保稳定运行。
cd /root/.openclaw/workspace/skills/daily-hot-api
# 部署并启动服务
./deploy.sh
# 查看状态
./deploy.sh status
# 重启服务
./deploy.sh restart
# 停止服务
./deploy.sh stop
# 查看日志
./deploy.sh logs服务地址: http://localhost:6688
2. 配置环境变量
export DAILY_HOT_API_URL=http://localhost:66883. 安装 Skill 依赖
cd /root/.openclaw/workspace/skills/daily-hot-news
pip install requests aiohttp---
🎮 使用示例
查询热榜
用户: 微博热搜
Skill: 调用 /weibo → 返回 Top 10 热榜查看所有热榜
用户: 有什么热榜
Skill: 返回 54 个热榜源列表查询历史热榜
用户: 微博历史
Skill: 显示之前保存的微博热榜记录查看已保存数据
用户: 已保存了哪些数据
Skill: 返回所有已保存的热榜数据统计定时推送
用户: 每天早上8点推送B站热门
Skill: 设置 cron 任务 → 每日调用 /bilibili → 推送到飞书---
💾 数据存储
存储位置
所有热榜数据保存在:
/root/.openclaw/workspace/skills/daily-hot-news/data/文件结构
data/
├── weibo/
│ ├── 2026-02-05.json
│ └── 2026-02-04.json
├── zhihu/
│ └── 2026-02-05.json
└── ...配置项
| 环境变量 | 默认值 | 说明 |
|---|---|---|
DAILY_HOT_DATA_DIR | data/ | 数据存储目录 |
DAILY_HOT_AUTO_SAVE | true | 是否自动保存热榜数据 |
管理命令
# 查看已保存的数据统计
python3 storage.py
# 清理 30 天前的旧数据
python3 storage.py --clear 30---
📊 响应格式
热榜列表响应
{
"platform": "微博",
"updateTime": "2026-02-05 17:00:00",
"data": [
{
"rank": 1,
"title": "热搜标题",
"hot": "1234万",
"url": "https://..."
}
]
}---
⚙️ 配置项
| 环境变量 | 默认值 | 说明 |
|---|---|---|
DAILY_HOT_API_URL | http://localhost:6688 | DailyHotApi 服务地址 |
DAILY_HOT_CACHE_TTL | 3600 | 缓存时间(秒) |
DAILY_HOT_MAX_ITEMS | 20 | 返回最大条数 |
---
📊 资源占用
| 组件 | 内存 | CPU |
|---|---|---|
| DailyHotApi 服务 | ~200MB | 极低 |
| DailyHotApi Skill | <10MB | 可忽略 |
总计: <250MB,对服务器无压力
---
🔧 故障排查
问题: 服务无法连接
# 检查 PM2 状态
./deploy.sh status
# 查看日志
./deploy.sh logs
# 重启服务
./deploy.sh restart---
📁 文件结构
daily-hot-news/
├── SKILL.md # 本说明书
├── daily_hot_news.py # 核心 Skill 脚本
├── api_client.py # API 客户端封装
├── formatter.py # 响应格式化
├── config.py # 配置管理
├── storage.py # 数据存储模块
├── data/ # 热榜数据存储目录
├── README.md # 快速开始
└── requirements.txt # 依赖列表---
📝 更新日志
v1.1.0 (2026-02-05)
- ✨ 新增数据存储功能
- ✨ 支持自动保存每日热榜
- ✨ 支持查询历史记录
- ✨ 新增数据统计命令
v1.0.0 (2026-02-05)
- 初始版本
- 支持 54 个热榜源
- 基础查询和定时推送功能
"""
每日热榜技能扩展模块
整合了以下功能:
- news_digest: 热点新闻摘要(15种标签分类)
- industry_hot: 行业热榜垂直(十大行业分类)
- personalized: 个性化订阅(关键词/平台/排除项配置)
- cross_platform: 跨平台聚合(TOP10聚合)
- sentiment_monitor: 舆情监控(关键词过滤)
"""
from .news_digest import (
NewsDigest,
DigestConfig,
DigestMode,
TAG_MAPPING,
ALL_TAGS,
create_digest as create_news_digest
)
from .industry_hot import (
IndustryHot,
IndustryConfig,
IndustryMode,
INDUSTRIES,
ALL_INDUSTRIES,
create_industry_hot
)
from .personalized import (
PersonalizedSubscription,
UserPreferences,
SubscriptionMode,
KEYWORD_OPTIONS,
PLATFORM_OPTIONS,
EXCLUDE_OPTIONS,
create_personalized
)
from .cross_platform import (
CrossPlatformAggregator,
CrossPlatformConfig,
create_cross_platform
)
from .sentiment_monitor import (
SentimentMonitor,
SentimentConfig,
SentimentType,
create_sentiment_monitor
)
__all__ = [
# News Digest
"NewsDigest",
"DigestConfig",
"DigestMode",
"TAG_MAPPING",
"ALL_TAGS",
"create_news_digest",
# Industry Hot
"IndustryHot",
"IndustryConfig",
"IndustryMode",
"INDUSTRIES",
"ALL_INDUSTRIES",
"create_industry_hot",
# Personalized
"PersonalizedSubscription",
"UserPreferences",
"SubscriptionMode",
"KEYWORD_OPTIONS",
"PLATFORM_OPTIONS",
"EXCLUDE_OPTIONS",
"create_personalized",
# Cross Platform
"CrossPlatformAggregator",
"CrossPlatformConfig",
"create_cross_platform",
# Sentiment Monitor
"SentimentMonitor",
"SentimentConfig",
"SentimentType",
"create_sentiment_monitor"
]
# Dependencies
node_modules/
.pnpm-store/
# Build outputs
dist/
build/
# Environment variables
.env
.env.local
.env.*.local
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Logs
logs/
*.log
npm-debug.log*
# Testing
coverage/
.nyc_output/
# Cache
.cache/
.__pycache__/
*.py[cod]
*$py.class
# Data files (user data)
data/
*.json
# Temporary files
tmp/
temp/
*.tmp
# OpenClaw specific
.clawdhub/
# Personal milestone (local only)
MILESTONE.md
# -*- coding: utf-8 -*-
"""
DailyHotApi Skill - API 客户端封装
"""
import aiohttp
import asyncio
import subprocess
import os
from typing import Optional, Dict, List, Any
from datetime import datetime
from config import config
from storage import storage # 导入存储模块
# 部署状态存储
_deployment_status = {
"is_deploying": False,
"last_check": None,
"needs_deployment": False,
"message": ""
}
class HotSource:
"""热榜源定义"""
def __init__(self, id: str, name: str, category: str, description: str = ""):
self.id = id
self.name = name
self.category = category
self.description = description
# 54 个热榜源定义
HOT_SOURCES = {
# 视频/直播平台
"bilibili": HotSource("bilibili", "哔哩哔哩", "video", "热门榜"),
"acfun": HotSource("acfun", "AcFun", "video", "排行榜"),
"douyin": HotSource("douyin", "抖音", "video", "热点榜"),
"kuaishou": HotSource("kuaishou", "快手", "video", "热点榜"),
"coolapk": HotSource("coolapk", "酷安", "video", "热榜"),
# 社交媒体
"weibo": HotSource("weibo", "微博", "social", "热搜榜"),
"zhihu": HotSource("zhihu", "知乎", "social", "热榜"),
"zhihu-daily": HotSource("zhihu-daily", "知乎日报", "social", "推荐榜"),
"tieba": HotSource("tieba", "百度贴吧", "social", "热议榜"),
"douban-group": HotSource("douban-group", "豆瓣讨论小组", "social", "讨论精选"),
"v2ex": HotSource("v2ex", "V2EX", "social", "主题榜"),
"ngabbs": HotSource("ngabbs", "NGA", "social", "热帖"),
"hupu": HotSource("hupu", "虎扑", "social", "步行街热帖"),
# 新闻资讯
"baidu": HotSource("baidu", "百度", "news", "热搜榜"),
"thepaper": HotSource("thepaper", "澎湃新闻", "news", "热榜"),
"toutiao": HotSource("toutiao", "今日头条", "news", "热榜"),
"36kr": HotSource("36kr", "36氪", "news", "热榜"),
"qq-news": HotSource("qq-news", "腾讯新闻", "news", "热点榜"),
"sina": HotSource("sina", "新浪网", "news", "热榜"),
"sina-news": HotSource("sina-news", "新浪新闻", "news", "热点榜"),
"netease-news": HotSource("netease-news", "网易新闻", "news", "热点榜"),
"huxiu": HotSource("huxiu", "虎嗅", "news", "24小时"),
"ifanr": HotSource("ifanr", "爱范儿", "news", "快讯"),
# 科技/技术社区
"ithome": HotSource("ithome", "IT之家", "tech", "热榜"),
"ithome-xijiayi": HotSource("ithome-xijiayi", "IT之家「喜加一」", "tech", "最新动态"),
"sspai": HotSource("sspai", "少数派", "tech", "热榜"),
"csdn": HotSource("csdn", "CSDN", "tech", "排行榜"),
"juejin": HotSource("juejin", "稀土掘金", "tech", "热榜"),
"51cto": HotSource("51cto", "51CTO", "tech", "推荐榜"),
"nodeseek": HotSource("nodeseek", "NodeSeek", "tech", "最新动态"),
"hellogithub": HotSource("hellogithub", "HelloGitHub", "tech", "Trending"),
# 游戏/ACG
"genshin": HotSource("genshin", "原神", "game", "最新消息"),
"miyoushe": HotSource("miyoushe", "米游社", "game", "最新消息"),
"honkai": HotSource("honkai", "崩坏3", "game", "最新动态"),
"starrail": HotSource("starrail", "崩坏:星穹铁道", "game", "最新动态"),
"lol": HotSource("lol", "英雄联盟", "game", "更新公告"),
# 阅读/文化
"jianshu": HotSource("jianshu", "简书", "reading", "热门推荐"),
"guokr": HotSource("guokr", "果壳", "reading", "热门文章"),
"weread": HotSource("weread", "微信读书", "reading", "飙升榜"),
"douban-movie": HotSource("douban-movie", "豆瓣电影", "reading", "新片榜"),
# 工具/其他
"52pojie": HotSource("52pojie", "吾爱破解", "tool", "榜单"),
"hostloc": HotSource("hostloc", "全球主机交流", "tool", "榜单"),
"weatheralarm": HotSource("weatheralarm", "中央气象台", "tool", "全国气象预警"),
"earthquake": HotSource("earthquake", "中国地震台", "tool", "地震速报"),
"history": HotSource("history", "历史上的今天", "tool", "月-日"),
}
class DailyHotApiClient:
"""DailyHotApi 客户端"""
def __init__(self):
self.api_url = config.api_url
self.timeout = config.timeout
async def fetch_hot_list(self, source_id: str, use_cache: bool = False) -> Optional[Dict[str, Any]]:
"""
获取热榜数据
Args:
source_id: 热榜源 ID(如 weibo, zhihu, bilibili)
use_cache: 是否使用缓存(默认False,每次获取最新数据)
Returns:
热榜数据字典或 None(失败时)
"""
# 获取热榜源信息
source = HOT_SOURCES.get(source_id)
if not source:
return None
# 构建请求 URL
url = config.get_api_url(source_id)
# 步骤1:检查API是否可用
print(f"[DailyHotApi] 正在连接 {source.name}...")
api_available = await check_api_availability(url)
if not api_available:
# API不可用,尝试部署
print(f"[DailyHotApi] ⚠️ 后端服务不可用,尝试自动部署...")
# 触发部署
deploy_result = await deploy_daily_hot_api()
if deploy_result["success"]:
# 部署成功,等待服务启动
print(f"[DailyHotApi] ⏳ 等待服务启动 (5秒)...")
await asyncio.sleep(5)
# 再次检查API
api_available = await check_api_availability(url)
if not api_available:
# 仍然不可用,可能需要更多时间
print(f"[DailyHotApi] ⏳ 服务可能需要更多时间启动,再次等待 (10秒)...")
await asyncio.sleep(10)
api_available = await check_api_availability(url)
# 返回部署状态
if deploy_result["success"]:
return {
"success": True,
"deploy_message": deploy_result["message"],
"is_deployed": True,
"data": None,
"message": "🎉 后端服务已部署成功!请稍后再次尝试获取热榜数据。"
}
else:
return {
"success": False,
"deploy_message": "❌ 自动部署失败",
"steps": deploy_result.get("steps", []),
"data": None,
"message": f"⚠️ 无法连接后端服务\n\n自动部署失败:{deploy_result['message']}\n\n请手动部署:\n1. cd /root/.openclaw\n2. git clone https://github.com/imsyy/DailyHotApi.git\n3. cd DailyHotApi\n4. bash deploy.sh"
}
# API可用,获取数据
try:
timeout_obj = aiohttp.ClientTimeout(total=self.timeout)
async with aiohttp.ClientSession(timeout=timeout_obj) as session:
async with session.get(url) as response:
if response.status != 200:
return None
result = await response.json()
# 检查返回格式
if result.get("code") != 200:
return None
# 格式化数据
data = {
"platform": source.name,
"category": source.category,
"source_id": source_id,
"update_time": result.get("updateTime"),
"from_cache": result.get("fromCache", False),
"total": result.get("total", 0),
"data": self._format_items(result.get("data", [])),
}
# 每次获取最新数据后,保存到本地历史记录
save_result = storage.save_hot_list(source_id, data)
if save_result:
print(f"[DailyHotApi] ✅ 已保存 {source.name} 热榜数据到历史记录")
return data
except Exception as e:
print(f"[DailyHotApi] Error fetching {source_id}: {e}")
return None
async def get_hot榜单(self, source_id: str, limit: int = 10) -> Optional[List[Dict]]:
"""
获取热榜条目列表(兼容旧接口)
Args:
source_id: 热榜源 ID
limit: 返回条目数限制
Returns:
热榜条目列表或 None
"""
data = await self.fetch_hot_list(source_id)
if data:
return data.get("data", [])[:limit]
return None
def _format_items(self, items: List[Dict]) -> List[Dict]:
"""格式化热榜条目"""
formatted = []
for i, item in enumerate(items[:config.max_items], 1):
formatted.append({
"rank": i,
"title": item.get("title", ""),
"desc": item.get("desc", ""),
"hot": item.get("hot", ""),
"url": item.get("url", ""),
"mobile_url": item.get("mobileUrl", ""),
})
return formatted
def get_all_sources(self) -> Dict[str, Dict]:
"""获取所有热榜源"""
return {
source_id: {
"id": source_id,
"name": source.name,
"category": source.category,
"description": source.description,
}
for source_id, source in HOT_SOURCES.items()
}
def get_sources_by_category(self) -> Dict[str, List[Dict]]:
"""按类别获取热榜源"""
categories = {}
for source_id, source in HOT_SOURCES.items():
if source.category not in categories:
categories[source.category] = []
categories[source.category].append({
"id": source_id,
"name": source.name,
"description": source.description,
})
return categories
def search_sources(self, query: str) -> List[Dict]:
"""搜索热榜源"""
query = query.lower()
results = []
for source_id, source in HOT_SOURCES.items():
if (query in source.name.lower() or
query in source.id.lower() or
query in source.category.lower()):
results.append({
"id": source_id,
"name": source.name,
"category": source.category,
})
return results
# 全局客户端实例
api_client = DailyHotApiClient()
# ============================================
# 自动检测和部署功能
# ============================================
async def check_api_availability(url: str, timeout: int = 3) -> bool:
"""
检查API是否可用
Args:
url: API地址
timeout: 超时时间(秒)
Returns:
True表示可用,False表示不可用
"""
try:
timeout_obj = aiohttp.ClientTimeout(total=timeout)
async with aiohttp.ClientSession(timeout=timeout_obj) as session:
async with session.get(url, allow_redirects=True) as response:
return response.status == 200
except Exception as e:
print(f"[DailyHotApi] API不可用: {e}")
return False
async def deploy_daily_hot_api() -> Dict[str, Any]:
"""
自动部署DailyHotApi后端服务
Returns:
部署结果字典
"""
global _deployment_status
if _deployment_status["is_deploying"]:
return {
"success": False,
"message": "部署正在进行中,请稍候...",
"is_deploying": True
}
_deployment_status["is_deploying"] = True
_deployment_status["message"] = "🚀 正在自动部署DailyHotApi后端服务..."
result = {
"success": False,
"message": "",
"steps": []
}
try:
# 检查是否已安装
daily_hot_path = "/root/.openclaw/DailyHotApi"
if not os.path.exists(daily_hot_path):
# 步骤1:克隆仓库
step_msg = "📦 正在克隆DailyHotApi仓库..."
print(step_msg)
result["steps"].append(step_msg)
clone_cmd = ["git", "clone", "https://github.com/imsyy/DailyHotApi.git", daily_hot_path]
clone_proc = await asyncio.create_subprocess_exec(
*clone_cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await clone_proc.communicate()
if clone_proc.returncode != 0:
error_msg = f"❌ 克隆失败: {stderr.decode()}"
print(error_msg)
result["steps"].append(error_msg)
result["message"] = "部署失败:无法克隆仓库"
_deployment_status["is_deploying"] = False
return result
result["steps"].append("✅ 克隆成功")
else:
result["steps"].append("✅ DailyHotApi已存在,跳过克隆")
# 步骤2:部署服务
if os.path.exists(daily_hot_path):
step_msg = "🔧 正在部署DailyHotApi服务..."
print(step_msg)
result["steps"].append(step_msg)
deploy_script = os.path.join(daily_hot_path, "deploy.sh")
deploy_cmd = ["bash", deploy_script]
deploy_proc = await asyncio.create_subprocess_exec(
*deploy_cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await deploy_proc.communicate()
deploy_output = stdout.decode() + stderr.decode()
if deploy_proc.returncode == 0:
result["steps"].append("✅ 部署成功")
result["success"] = True
result["message"] = "🎉 DailyHotApi后端服务部署成功!正在启动..."
_deployment_status["message"] = result["message"]
else:
error_msg = f"❌ 部署失败: {deploy_output}"
print(error_msg)
result["steps"].append(error_msg)
result["message"] = "部署失败,请手动检查"
except Exception as e:
error_msg = f"❌ 部署异常: {str(e)}"
print(error_msg)
result["steps"].append(error_msg)
result["message"] = f"部署异常: {str(e)}"
finally:
_deployment_status["is_deploying"] = False
_deployment_status["last_check"] = datetime.now().isoformat()
return result
def get_deployment_status() -> Dict[str, Any]:
"""
获取当前部署状态
Returns:
部署状态字典
"""
return {
"is_deploying": _deployment_status["is_deploying"],
"last_check": _deployment_status["last_check"],
"message": _deployment_status["message"]
}
async def test_connection() -> bool:
"""测试 API 连接"""
try:
timeout_obj = aiohttp.ClientTimeout(total=5)
async with aiohttp.ClientSession(timeout=timeout_obj) as session:
async with session.get(f"{config.api_url}/") as response:
return response.status == 200
except Exception:
return False
if __name__ == "__main__":
import sys
print("Testing DailyHotApi connection...")
if asyncio.run(test_connection()):
print("✓ Service is running")
else:
print("✗ Service not available")
print(f" URL: {config.api_url}")
sys.exit(1)
# -*- coding: utf-8 -*-
"""
每日热榜 Skill - 配置管理
"""
import os
from typing import Optional
class Config:
"""配置管理类"""
def __init__(self):
# 每日热榜服务地址
self.api_url: str = os.getenv(
"DAILY_HOT_API_URL",
"http://localhost:6688"
)
# 缓存时间(秒)
self.cache_ttl: int = int(os.getenv("DAILY_HOT_CACHE_TTL", "3600"))
# 返回最大条数
self.max_items: int = int(os.getenv("DAILY_HOT_MAX_ITEMS", "20"))
# 请求超时时间(秒)
self.timeout: int = int(os.getenv("DAILY_HOT_TIMEOUT", "10"))
# 启用调试模式
self.debug: bool = os.getenv("DAILY_HOT_DEBUG", "false").lower() == "true"
# 数据存储路径
self.data_dir: str = os.getenv(
"DAILY_HOT_DATA_DIR",
"/root/.openclaw/workspace/skills/daily-hot-news/data"
)
# 是否自动保存每日热榜
self.auto_save: bool = os.getenv("DAILY_HOT_AUTO_SAVE", "true").lower() == "true"
def get_api_url(self, endpoint: str = "") -> str:
"""获取完整的 API 地址"""
# 移除末尾斜杠
base_url = self.api_url.rstrip("/")
if endpoint:
return f"{base_url}/{endpoint}"
return base_url
def is_service_available(self) -> bool:
"""检查服务是否配置"""
return bool(self.api_url)
def get_data_path(self, source_id: str = "", date_str: str = "") -> str:
"""获取数据文件路径"""
import os
from datetime import datetime
if not date_str:
date_str = datetime.now().strftime("%Y-%m-%d")
data_dir = self.data_dir
if source_id:
# 按热榜源分目录存储
source_dir = os.path.join(data_dir, source_id)
os.makedirs(source_dir, exist_ok=True)
return os.path.join(source_dir, f"{date_str}.json")
else:
# 主目录
os.makedirs(data_dir, exist_ok=True)
return os.path.join(data_dir, f"all_{date_str}.json")
# 全局配置实例
config = Config()
"""
跨平台聚合 - Cross Platform Aggregation Module
功能:
- 直接用全量数据做TOP10聚合
- 跨平台热点排行
"""
from typing import List, Dict, Optional, Any
from dataclasses import dataclass
import asyncio
@dataclass
class CrossPlatformConfig:
"""跨平台聚合配置"""
total_items: int = 10 # 总条目数限制(默认TOP10)
min_hot_score: float = 0 # 最小热度阈值
merge_strategy: str = "score" # 合并策略:score(按热度), time(按时间)
include_platforms: List[str] = None # 包含的平台(None表示全部)
exclude_platforms: List[str] = None # 排除的平台
class CrossPlatformAggregator:
"""跨平台聚合类"""
def __init__(self, api_client=None, formatter=None):
"""
初始化跨平台聚合
Args:
api_client: API客户端实例(可选)
formatter: 格式化器实例(可选)
"""
self.api_client = api_client
self.formatter = formatter
self.all_platforms = self._get_default_platforms()
def _get_default_platforms(self) -> List[str]:
"""获取默认的全部平台列表"""
return [
"weibo", "zhihu", "douban-group", "douban-movie",
"ithome", "36kr", "sspai", "csdn", "juejin",
"genshin", "miyoushe", "bilibili", "hupu",
"sina-news", "netease-news", "qq-news",
"sina-money", "eastmoney", "xueqiu",
"autohome", "懂车帝", "mafengwo", "ctrip",
"dianping", "xiaohongshu", "weibo"
]
async def fetch_all_hot_data(self, limit_per_platform: int = 10) -> List[Dict[str, Any]]:
"""
一次性获取全部平台的热榜数据
Args:
limit_per_platform: 每个平台获取的条目数
Returns:
全部平台的热榜数据列表
"""
all_items = []
if self.api_client:
for platform in self.all_platforms:
try:
items = await self.api_client.get_hot榜单(platform, limit=limit_per_platform)
if items:
for item in items:
item["source_platform"] = platform
all_items.extend(items)
except Exception as e:
print(f"Error fetching {platform}: {e}")
continue
return all_items
async def aggregate_top_hot(self, config: Optional[CrossPlatformConfig] = None) -> Dict[str, Any]:
"""
聚合跨平台TOP热点(新版:先全部获取,再聚合)
Args:
config: 配置对象(可选)
Returns:
聚合后的热榜数据
"""
if config is None:
config = CrossPlatformConfig()
# 步骤1:全部获取
all_items = await self.fetch_all_hot_data()
# 步骤2:聚合处理
# 平台过滤
if config.include_platforms:
all_items = [item for item in all_items if item.get("source_platform") in config.include_platforms]
if config.exclude_platforms:
all_items = [item for item in all_items if item.get("source_platform") not in config.exclude_platforms]
# 热度过滤
if config.min_hot_score > 0:
all_items = [item for item in all_items if (item.get("hot", 0) or item.get("score", 0)) >= config.min_hot_score]
# 去重和排序
merged_items = self._merge_and_sort(all_items, config.merge_strategy)
# 限制总数
merged_items = merged_items[:config.total_items]
return {
"total_items": len(merged_items),
"items": merged_items
}
def _merge_and_sort(self, items: List[Dict], strategy: str = "score") -> List[Dict]:
"""
合并、去重和排序
Args:
items: 条目列表
strategy: 排序策略
Returns:
处理后的条目列表
"""
if not items:
return []
# 按标题去重
seen_titles = set()
unique_items = []
for item in items:
title = item.get("title", "").strip().lower()
if title and title not in seen_titles:
seen_titles.add(title)
unique_items.append(item)
# 根据策略排序
if strategy == "score":
unique_items.sort(key=lambda x: x.get("hot", 0) or x.get("score", 0), reverse=True)
elif strategy == "time":
unique_items.sort(key=lambda x: x.get("time", "") or "", reverse=True)
return unique_items
def format_aggregation_response(self, agg_data: Dict[str, Any]) -> str:
"""
格式化聚合响应
Args:
agg_data: 聚合数据
Returns:
格式化的响应文本
"""
items = agg_data.get("items", [])
if not items:
return "❌ 暂无热点数据"
response = "🏆 **跨平台热点TOP10**\n"
response += f"共 {agg_data['total_items']} 条热点\n"
response += "-" * 40 + "\n\n"
for i, item in enumerate(items, 1):
title = item.get("title", "无标题")
hot = item.get("hot", item.get("score", ""))
platform = item.get("source_platform", "")
response += f"{i}. {title}\n"
if hot:
response += f" 🔥 热度: {hot}"
if platform:
response += f" | 📱 {platform}"
response += "\n\n"
return response
async def process_user_request(self, user_input: str = None) -> Dict[str, Any]:
"""
处理用户请求
Args:
user_input: 用户输入(可选)
Returns:
处理结果
"""
# 获取聚合数据
agg_data = await self.aggregate_top_hot()
# 格式化响应
response_text = self.format_aggregation_response(agg_data)
return {
"action": "show_top_hot",
"data": agg_data,
"message": response_text
}
# 便捷函数
async def create_cross_platform(api_client=None, formatter=None) -> CrossPlatformAggregator:
"""创建跨平台聚合实例"""
return CrossPlatformAggregator(api_client=api_client, formatter=formatter)
if __name__ == "__main__":
# 测试代码
async def test():
aggregator = await create_cross_platform()
print("获取跨平台热点TOP10...")
result = await aggregator.process_user_request()
print(result["message"])
asyncio.run(test())
"""
每日热榜技能主入口示例
这个文件展示如何将三个扩展功能集成到主Skill中。
在实际使用时,可以根据需要调整和整合。
"""
import asyncio
from typing import Dict, Any, Optional
# 支持相对导入(作为包的一部分)和绝对导入(直接运行)
try:
from .news_digest import NewsDigest, DigestConfig, create_digest as create_news_digest
from .industry_hot import IndustryHot, IndustryConfig, create_industry_hot
from .personalized import PersonalizedSubscription, UserPreferences, create_personalized
from .storage import storage
from .api_client import api_client, check_api_availability, deploy_daily_hot_api, get_deployment_status
from .config import config
except ImportError:
from news_digest import NewsDigest, DigestConfig, create_digest as create_news_digest
from industry_hot import IndustryHot, IndustryConfig, create_industry_hot
from personalized import PersonalizedSubscription, UserPreferences, create_personalized
from storage import storage
from api_client import api_client, check_api_availability, deploy_daily_hot_api, get_deployment_status
from config import config
class DailyHotNewsSkill:
"""每日热榜技能主类"""
def __init__(self, api_client=None, formatter=None):
"""
初始化技能
Args:
api_client: API客户端实例(用于获取热榜数据)
formatter: 格式化器实例(用于格式化输出)
"""
self.api_client = api_client
self.formatter = formatter
# 初始化各功能模块
self.news_digest: Optional[NewsDigest] = None
self.industry_hot: Optional[IndustryHot] = None
self.personalized: Optional[PersonalizedSubscription] = None
# 标记是否已检查过旧数据(避免每次都提示)
self._old_data_checked = False
self._old_data_notification = None
async def initialize(self):
"""初始化各模块"""
self.news_digest = await create_news_digest(
api_client=self.api_client,
formatter=self.formatter
)
self.industry_hot = await create_industry_hot(
api_client=self.api_client,
formatter=self.formatter
)
self.personalized = await create_personalized(
api_client=self.api_client,
formatter=self.formatter
)
# 启动时检查是否有7天前的旧数据
await self._check_old_data()
async def _check_old_data(self):
"""检查并提示用户清理7天前的旧数据"""
if self._old_data_checked:
return
old_files = storage.get_old_data_files(days=7)
if old_files:
# 统计
sources = set(f["source_id"] for f in old_files)
print(f"\n⚠️ 发现 {len(old_files)} 个旧数据文件(7天前)")
print(f" 涉及平台: {', '.join(list(sources)[:5])}...")
print(f" 示例文件: {old_files[0]['date_str']} - {old_files[0]['source_id']}")
# 返回提示信息给用户
self._old_data_notification = {
"has_old_data": True,
"count": len(old_files),
"sources": list(sources),
"message": f"""🗑️ **发现旧热榜数据**
检测到 {len(old_files)} 个热榜数据文件已超过7天未清理,涉及 {len(sources)} 个平台。
是否需要清理这些旧数据?
- 回复"**清理**"或"**是**":删除7天前的所有旧数据
- 回复"**跳过**"或"**否**":保留数据,下次启动不再提醒"""
}
else:
self._old_data_notification = None
self._old_data_checked = True
def get_old_data_notification(self) -> Optional[Dict[str, Any]]:
"""获取旧数据清理提示(如果有)"""
return self._old_data_notification
async def handle_old_data_cleanup(self, confirm: bool = False) -> Dict[str, Any]:
"""
处理旧数据清理
Args:
confirm: 是否确认清理
Returns:
清理结果
"""
if not confirm:
return {
"action": "ask_confirm",
"message": "请确认是否清理7天前的旧数据?\n回复\"清理\"确认,回复\"跳过\"取消。"
}
old_files = storage.get_old_data_files(days=7)
if not old_files:
return {
"action": "show_message",
"message": "✅ 没有需要清理的旧数据"
}
deleted = storage.cleanup_old_files(old_files)
return {
"action": "show_message",
"message": f"✅ 成功清理 {deleted} 个旧数据文件"
}
async def handle_request(self, user_input: str, intent: str = None) -> Dict[str, Any]:
"""
处理用户请求
Args:
user_input: 用户输入
intent: 意图(可选,用于路由到对应功能)
Returns:
处理结果
"""
user_input_lower = user_input.lower()
# 检查是否是清理旧数据命令
cleanup_keywords = ["清理", "删除旧数据", "清除缓存", "clean"]
if any(kw in user_input_lower for kw in cleanup_keywords):
return await self.handle_old_data_cleanup(confirm=True)
# 检查是否是获取热榜的请求
is_hot_request = any([
intent == "news_digest",
self._is_news_digest_request(user_input_lower),
intent == "industry_hot",
self._is_industry_hot_request(user_input_lower),
])
if is_hot_request:
# 先检查API是否可用
api_url = config.api_url
print(f"\n[DailyHotSkill] 检查后端服务可用性: {api_url}")
api_available = await check_api_availability(api_url)
if not api_available:
print(f"[DailyHotSkill] ⚠️ 后端服务不可用,触发自动部署...")
deploy_result = await deploy_daily_hot_api()
# 返回部署状态给用户
if deploy_result["success"]:
# 构建部署步骤消息
steps_text = ""
for step in deploy_result.get("steps", []):
steps_text += f"{step}\n"
return {
"action": "show_deploying",
"message": f"""🚀 **正在自动部署后端服务**
{steps_text}
⏳ **请稍候...**
后端服务正在启动,预计需要1-2分钟。
**部署完成后**,请再次发送请求获取热榜数据。
---
💡 如果自动部署失败,请手动执行:
1. `cd /root/.openclaw`
2. `git clone https://github.com/imsyy/DailyHotApi.git`
3. `cd DailyHotApi`
4. `bash deploy.sh`""",
"deploy_success": True,
"steps": deploy_result.get("steps", [])
}
else:
# 部署失败,返回错误和手动部署指南
steps_text = ""
for step in deploy_result.get("steps", []):
steps_text += f"{step}\n"
return {
"action": "show_deploy_failed",
"message": f"""⚠️ **后端服务不可用**
自动部署失败,需要手动部署。
**部署步骤:**
```bash
cd /root/.openclaw
git clone https://github.com/imsyy/DailyHotApi.git
cd DailyHotApi
bash deploy.sh
```""",
"deploy_success": False,
"steps": deploy_result.get("steps", [])
}
# 路由到对应功能
if intent == "news_digest" or self._is_news_digest_request(user_input_lower):
return await self._handle_news_digest(user_input)
elif intent == "industry_hot" or self._is_industry_hot_request(user_input_lower):
return await self._handle_industry_hot(user_input)
elif intent == "personalized" or self._is_personalized_request(user_input_lower):
return await self._handle_personalized(user_input)
else:
# 默认返回功能选择引导
return await self._show_main_menu()
def _is_news_digest_request(self, user_input: str) -> bool:
"""判断是否为新闻摘要请求"""
keywords = ["热点", "摘要", "标签", "科技", "游戏", "娱乐", "财经", "新闻"]
return any(kw in user_input for kw in keywords)
def _is_industry_hot_request(self, user_input: str) -> bool:
"""判断是否为行业热榜请求"""
keywords = ["行业", "汽车", "金融", "医疗", "旅游", "餐饮", "房产"]
return any(kw in user_input for kw in keywords)
def _is_personalized_request(self, user_input: str) -> bool:
"""判断是否为个性化请求"""
keywords = ["配置", "设置", "偏好", "关注", "个性化", "订阅"]
return any(kw in user_input for kw in keywords)
async def _handle_news_digest(self, user_input: str) -> Dict[str, Any]:
"""处理新闻摘要请求"""
if not self.news_digest:
return {"error": "模块未初始化"}
return await self.news_digest.process_user_request(user_input)
async def _handle_industry_hot(self, user_input: str) -> Dict[str, Any]:
"""处理行业热榜请求"""
if not self.industry_hot:
return {"error": "模块未初始化"}
return await self.industry_hot.process_user_request(user_input)
async def _handle_personalized(self, user_input: str) -> Dict[str, Any]:
"""处理个性化订阅请求"""
if not self.personalized:
return {"error": "模块未初始化"}
return await self.personalized.process_user_request(user_input)
async def _show_main_menu(self) -> Dict[str, Any]:
"""显示主菜单"""
# 检查是否有旧数据清理提示
old_data = self.get_old_data_notification()
menu_text = """🎯 **每日热榜 - 功能选择**
请选择您想使用的功能:
1. **📰 热点新闻摘要**
按标签浏览热点新闻(科技、游戏、娱乐、财经等)
2. **🏭 行业热榜垂直**
按行业分类查看热榜(汽车、金融、医疗、旅游等)
3. **⚙️ 个性化订阅**
配置您的偏好,获取定制化热榜
💡 您可以直接告诉我您想做什么,例如:
- "今天有什么科技热点"
- "看看汽车行业热榜"
- "配置个性化热榜"
"""
result = {
"action": "show_menu",
"message": menu_text
}
# 如果有旧数据,添加提示
if old_data:
result["old_data_prompt"] = old_data["message"]
return result
# 便捷方法
async def get_news_digest_tags(self) -> str:
"""获取新闻摘要标签选项"""
if self.news_digest:
return await self.news_digest.get_tag_options()
return "模块未初始化"
async def get_industry_options(self) -> str:
"""获取行业选项"""
if self.industry_hot:
return await self.industry_hot.get_industry_options()
return "模块未初始化"
async def get_personalized_options(self) -> str:
"""获取个性化配置选项"""
if self.personalized:
return await self.personalized.get_config_options()
return "模块未初始化"
def get_current_config(self) -> Optional[UserPreferences]:
"""获取当前个性化配置"""
if self.personalized:
return self.personalized.get_current_config()
return None
# 便捷函数
async def create_skill(api_client=None, formatter=None) -> DailyHotNewsSkill:
"""创建技能实例"""
skill = DailyHotNewsSkill(api_client=api_client, formatter=formatter)
await skill.initialize()
return skill
# 示例使用
if __name__ == "__main__":
async def example():
# 创建技能实例(不传入api_client时的模拟示例)
skill = await create_skill()
# 示例1:展示主菜单
result = await skill.handle_request("帮助")
print(result["message"])
print("\n" + "="*60 + "\n")
# 示例2:展示标签选择
result = await skill.get_news_digest_tags()
print(result)
print("\n" + "="*60 + "\n")
# 示例3:展示行业选择
result = await skill.get_industry_options()
print(result)
print("\n" + "="*60 + "\n")
# 示例4:展示个性化配置选项
result = await skill.get_personalized_options()
print(result)
asyncio.run(example())
# -*- coding: utf-8 -*-
"""
DailyHotApi Skill - 响应格式化
"""
from typing import Dict, List, Any
from api_client import HOT_SOURCES
class ResponseFormatter:
"""响应格式化器"""
# 分类名称映射(中文)
CATEGORY_NAMES = {
"video": "🎬 视频/直播",
"social": "💬 社交媒体",
"news": "📰 新闻资讯",
"tech": "💻 科技/技术",
"game": "🎮 游戏/ACG",
"reading": "📚 阅读/文化",
"tool": "🔧 工具/其他",
}
@staticmethod
def format_hot_list(data: Dict[str, Any]) -> str:
"""格式化热榜列表为文本"""
lines = []
platform = data.get("platform", "未知平台")
update_time = data.get("update_time", "")
# 头部
lines.append(f"🔥 **{platform}**")
if update_time:
lines.append(f"更新时间: {update_time}")
lines.append("")
# 列表
items = data.get("data", [])
if not items:
lines.append("暂无数据")
return "\n".join(lines)
for item in items:
rank = item.get("rank", 0)
title = item.get("title", "")
hot = item.get("hot", "")
url = item.get("url", "")
# 热度处理
hot_str = f" {hot}" if hot else ""
# 标题处理(过长截断)
if len(title) > 40:
title = title[:40] + "..."
lines.append(f"{rank:2d}. {title}{hot_str}")
# 底部
lines.append("")
lines.append(f"共 {len(items)} 条")
return "\n".join(lines)
@staticmethod
def format_hot_list_compact(data: Dict[str, Any], max_items: int = 10) -> str:
"""格式化热榜列表为紧凑格式"""
lines = []
platform = data.get("platform", "未知平台")
lines.append(f"🔥 **{platform}**")
lines.append("-" * 40)
items = data.get("data", [])[:max_items]
for item in items:
rank = item.get("rank", 0)
title = item.get("title", "")
hot = item.get("hot", "")
# 简化标题
title = title.replace("\n", " ")
if len(title) > 30:
title = title[:30] + "..."
hot_str = f" 📈 {hot}" if hot else ""
lines.append(f"{rank:2d}. {title}{hot_str}")
return "\n".join(lines)
@staticmethod
def format_all_sources() -> str:
"""格式化所有热榜源列表"""
from api_client import api_client
sources_by_cat = api_client.get_sources_by_category()
lines = []
lines.append("📊 **支持的热榜源(共 54 个)**")
lines.append("")
for cat_key, cat_name in ResponseFormatter.CATEGORY_NAMES.items():
if cat_key in sources_by_cat:
sources = sources_by_cat[cat_key]
lines.append(f"### {cat_name}")
lines.append(f"共 {len(sources)} 个")
for source in sources:
lines.append(f"• **{source['name']}** (`{source['id']}`)")
lines.append("")
return "\n".join(lines)
@staticmethod
def format_sources_by_category() -> str:
"""按类别格式化热榜源"""
from api_client import api_client
sources_by_cat = api_client.get_sources_by_category()
lines = []
for cat_key, cat_name in ResponseFormatter.CATEGORY_NAMES.items():
if cat_key not in sources_by_cat:
continue
lines.append(f"\n{cat_name}\n{'─' * 30}")
sources = sources_by_cat[cat_key]
for source in sources:
lines.append(f"• {source['name']} (`{source['id']}`)")
return "\n".join(lines)
@staticmethod
def format_search_results(results: List[Dict], query: str) -> str:
"""格式化搜索结果"""
lines = []
if not results:
return f"❌ 没有找到与「{query}」相关的热榜源"
lines.append(f"🔍 搜索「{query}」结果 ({len(results)} 个)")
lines.append("")
for result in results:
lines.append(f"• **{result['name']}** (`{result['id']}`)")
return "\n".join(lines)
@staticmethod
def format_error(message: str, suggestion: str = "") -> str:
"""格式化错误信息"""
lines = [f"❌ {message}"]
if suggestion:
lines.append("")
lines.append(f"💡 {suggestion}")
return "\n".join(lines)
@staticmethod
def format_service_status(is_running: bool, url: str) -> str:
"""格式化服务状态"""
if is_running:
return f"✅ 每日热榜服务运行中\n\n📡 API 地址: {url}"
else:
return f"❌ 每日热榜服务未运行\n\n📡 预期地址: {url}\n\n💡 请使用 `./deploy.sh status` 查看状态"
# 全局格式化器实例
formatter = ResponseFormatter()
"""
行业热榜垂直(增强版) - Industry Hot Module
功能:
- 十大行业分类:科技互联网、游戏、汽车、金融财经、数码消费、娱乐影视、房产家居、医疗健康、旅游出行、餐饮消费
- 用户自主选择行业
- 行业描述和平台标注
"""
from typing import List, Dict, Optional, Any
from dataclasses import dataclass
from enum import Enum
import asyncio
# 十大行业映射
INDUSTRIES: Dict[str, Dict[str, Any]] = {
"科技互联网": {
"platforms": ["ithome", "36kr", "csdn", "juejin", "oschina", "infoq"],
"description": "IT之家、36氪、CSDN、稀土掘金、开源中国、InfoQ"
},
"游戏行业": {
"platforms": ["genshin", "miyoushe", "lol", "bilibili", "douyu", "huya", "netease-game"],
"description": "原神、米游社、英雄联盟、B站、斗鱼、虎牙、网易游戏"
},
"汽车行业": {
"platforms": ["autohome", "car", "懂车帝", "bitauto", "soufun-auto"],
"description": "汽车之家、懂车帝、易车网、苏宁汽车"
},
"金融财经": {
"platforms": ["sina-money", "eastmoney", "xueqiu", "jrj", "cnstock", "wallstreetcn", "money163"],
"description": "新浪财经、东方财富、雪球、金融界、中国财经网、华尔街见闻、网易财经"
},
"数码消费": {
"platforms": ["coolapk", "ithome", "sspai", "geekpark", "少数派", "smzdm"],
"description": "酷安、IT之家、少数派、什么值得买"
},
"娱乐影视": {
"platforms": ["weibo", "douban-group", "douban-movie", "mtime", "movie", "bilibili"],
"description": "微博、豆瓣、豆瓣电影、时光网、B站"
},
"房产家居": {
"platforms": ["lfang", "soufunianjia", "anjuke", "house", "lianjia"],
"description": "链家、安居客、房天下、贝壳找房"
},
"医疗健康": {
"platforms": ["zhihu", "知乎", "sina-health", "health", "baikemy", "丁香园"],
"description": "知乎、新浪健康、丁香园、百度健康"
},
"旅游出行": {
"platforms": ["mafengwo", "ctrip", "qunar", "飞猪", "马蜂窝", "携程"],
"description": "马蜂窝、携程、去哪儿、飞猪"
},
"餐饮消费": {
"platforms": ["dianping", "xiaohongshu", "大众点评", "ele.me", "meituan"],
"description": "大众点评、美团、饿了么、小红书"
}
}
# 所有行业名称
ALL_INDUSTRIES = list(INDUSTRIES.keys())
class IndustryMode(Enum):
"""行业模式"""
SINGLE = "single" # 单行业
MULTI = "multi" # 多行业
@dataclass
class IndustryConfig:
"""行业配置"""
industries: List[str] # 选择的行业列表
mode: IndustryMode = IndustryMode.SINGLE
items_per_platform: int = 10 # 每个平台显示的条目数
total_items: int = 50 # 总条目数限制
include_description: bool = True # 是否包含行业描述
class IndustryHot:
"""行业热榜类"""
def __init__(self, api_client=None, formatter=None):
"""
初始化行业热榜
Args:
api_client: API客户端实例(可选)
formatter: 格式化器实例(可选)
"""
self.api_client = api_client
self.formatter = formatter
self._all_platforms = self._get_all_platforms()
def _get_all_platforms(self) -> List[str]:
"""获取所有行业相关的平台"""
platforms = set()
for industry_info in INDUSTRIES.values():
platforms.update(industry_info.get("platforms", []))
return list(platforms)
async def fetch_all_hot_data(self, limit_per_platform: int = 10) -> List[Dict[str, Any]]:
"""
一次性获取全部平台的热榜数据
Args:
limit_per_platform: 每个平台获取的条目数
Returns:
全部平台的热榜数据列表
"""
all_items = []
if self.api_client:
for platform in self._all_platforms:
try:
items = await self.api_client.get_hot榜单(platform, limit=limit_per_platform)
if items:
for item in items:
item["source_platform"] = platform
all_items.extend(items)
except Exception as e:
print(f"Error fetching {platform}: {e}")
continue
return all_items
async def get_industry_options(self) -> str:
"""
获取行业选择选项(AI引导话术)
Returns:
格式化的行业选择文本
"""
options_text = "🏭 **请选择您关注的行业**\n\n"
for i, (industry, info) in enumerate(INDUSTRIES.items(), 1):
platforms_count = len(info["platforms"])
options_text += f"{i}. **{industry}**\n"
options_text += f" 📋 包含 {platforms_count} 个平台\n"
options_text += f" 🔗 {info['description']}\n\n"
options_text += "-" * 50 + "\n"
options_text += "💡 您可以输入行业名称或数字编号,支持多选(如:1,3或汽车+金融)"
return options_text
def parse_industries_from_input(self, user_input: str) -> List[str]:
"""
解析用户输入的行业
Args:
user_input: 用户输入的文本
Returns:
匹配的行业列表
"""
user_input = user_input.strip().lower()
matched_industries = []
# 处理数字选择
import re
number_pattern = re.findall(r'\d+', user_input)
for num in number_pattern:
idx = int(num) - 1
if 0 <= idx < len(ALL_INDUSTRIES):
matched_industries.append(ALL_INDUSTRIES[idx])
# 行业关键词映射(行业名称 -> 包含的关键词)
industry_keywords = {
"科技互联网": ["科技", "互联网", "IT", "技术"],
"游戏行业": ["游戏", "手游", "网游"],
"汽车行业": ["汽车", "车", "车企", "新能源车"],
"金融财经": ["金融", "财经", "投资", "理财", "股票"],
"数码消费": ["数码", "手机", "电脑", "电子"],
"娱乐影视": ["娱乐", "影视", "电影", "综艺", "明星"],
"房产家居": ["房产", "房", "家居", "装修", "买房"],
"医疗健康": ["医疗", "健康", "医药", "养生"],
"旅游出行": ["旅游", "出行", "旅行", "机票", "酒店"],
"餐饮消费": ["餐饮", "美食", "外卖", "餐厅", "消费"]
}
# 处理行业关键词
for industry, keywords in industry_keywords.items():
for keyword in keywords:
if keyword in user_input:
if industry not in matched_industries:
matched_industries.append(industry)
break
# 处理完整行业名称(向后兼容)
for industry in ALL_INDUSTRIES:
if industry.lower() in user_input or industry in user_input:
if industry not in matched_industries:
matched_industries.append(industry)
return matched_industries if matched_industries else []
async def get_industry_hot(self, industries: List[str], config: Optional[IndustryConfig] = None) -> Dict[str, Any]:
"""
获取行业热榜(新版:先全部获取,再按行业筛选)
Args:
industries: 行业列表
config: 配置对象(可选)
Returns:
行业热榜数据
"""
if config is None:
config = IndustryConfig(industries=industries)
# 步骤1:全部获取
all_items = await self.fetch_all_hot_data(limit_per_platform=config.items_per_platform)
# 步骤2:按行业筛选
# 获取所有相关平台
target_platforms = set()
for industry in industries:
if industry in INDUSTRIES:
target_platforms.update(INDUSTRIES[industry]["platforms"])
filtered_items = []
for item in all_items:
platform = item.get("source_platform", "")
if platform in target_platforms:
item["source_industry"] = next((ind for ind in industries if ind in INDUSTRIES and platform in INDUSTRIES[ind]["platforms"]), industries[0])
filtered_items.append(item)
# 去重和排序
merged_items = self._merge_items(filtered_items)
# 限制总数
merged_items = merged_items[:config.total_items]
return {
"industries": industries,
"industry_descriptions": {ind: INDUSTRIES[ind]["description"] for ind in industries},
"total_items": len(merged_items),
"items": merged_items
}
def _merge_items(self, items: List[Dict]) -> List[Dict]:
"""
合并和去重条目
Args:
items: 条目列表
Returns:
合并后的条目列表
"""
if not items:
return []
# 按标题去重
seen_titles = set()
unique_items = []
for item in items:
title = item.get("title", "").strip().lower()
if title and title not in seen_titles:
seen_titles.add(title)
unique_items.append(item)
# 按热度排序
unique_items.sort(key=lambda x: x.get("hot", 0) or x.get("score", 0), reverse=True)
return unique_items
def format_industry_response(self, hot_data: Dict[str, Any]) -> str:
"""
格式化行业热榜响应
Args:
hot_data: 热榜数据
Returns:
格式化的响应文本
"""
if not hot_data.get("items"):
return "❌ 暂无行业热榜数据"
industries = hot_data["industries"]
items = hot_data["items"]
response = f"🏭 **行业热榜 - {', '.join(industries)}**\n\n"
# 显示行业描述
for industry in industries:
desc = hot_data["industry_descriptions"].get(industry, "")
response += f"📌 **{industry}**: {desc}\n"
response += "-" * 50 + "\n"
response += f"共 {hot_data['total_items']} 条热榜\n\n"
# 按行业分组显示
items_by_industry = {}
for item in items:
industry = item.get("source_industry", "其他")
if industry not in items_by_industry:
items_by_industry[industry] = []
items_by_industry[industry].append(item)
for industry, ind_items in items_by_industry.items():
response += f"\n📊 **{industry}**\n"
for i, item in enumerate(ind_items[:5], 1): # 每个行业显示5条
title = item.get("title", "无标题")
hot = item.get("hot", item.get("score", ""))
platform = item.get("source_platform", "")
response += f"{i}. {title}\n"
if hot:
response += f" 🔥 {hot}"
if platform:
response += f" | 📱 {platform}"
response += "\n"
return response
async def process_user_request(self, user_input: str) -> Dict[str, Any]:
"""
处理用户请求
Args:
user_input: 用户输入
Returns:
处理结果
"""
# 解析行业
industries = self.parse_industries_from_input(user_input)
if not industries:
# 返回引导信息
return {
"action": "ask_industry",
"message": await self.get_industry_options()
}
# 获取热榜
hot_data = await self.get_industry_hot(industries)
# 格式化响应
response_text = self.format_industry_response(hot_data)
return {
"action": "show_industry_hot",
"data": hot_data,
"message": response_text
}
# 便捷函数
async def create_industry_hot(api_client=None, formatter=None) -> IndustryHot:
"""创建行业热榜实例"""
return IndustryHot(api_client=api_client, formatter=formatter)
if __name__ == "__main__":
# 测试代码
async def test():
industry_hot = await create_industry_hot()
print(await industry_hot.get_industry_options())
print("\n" + "="*50 + "\n")
result = await industry_hot.process_user_request("汽车和金融")
print(result["message"])
asyncio.run(test())
MIT License
Copyright (c) 2026 OpenClaw Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
热点新闻摘要(增强版) - News Digest Module
功能:
- 15种标签分类:科技、互联网、游戏、娱乐、社会、财经、汽车、体育、教育、健康、国际、房产、数码、时尚、美食
- AI主动引导用户选择标签
- 按标签获取和合并热榜
"""
from typing import List, Dict, Optional, Any
from dataclasses import dataclass
from enum import Enum
import asyncio
# 标签到平台的映射
TAG_MAPPING: Dict[str, List[str]] = {
"科技": ["ithome", "36kr", "sspai", "csdn", "juejin", "51cto", "oschina", "infoq"],
"互联网": ["sina-news", "netease-news", "qq-news", "sohu-news", "ifeng"],
"游戏": ["genshin", "miyoushe", "lol", "hupu", "bilibili", "douyu", "huya", "netease-game"],
"娱乐": ["weibo", "douban-group", "douban-movie", "mtime", "movie"],
"社会": ["sina-news", "netease-news", "qq-news", "sohu-news", "ifeng", "qq"],
"财经": ["sina-money", "eastmoney", "xueqiu", "jrj", "cnstock", "wallstreetcn"],
"汽车": ["autohome", "car", "懂车帝", "bitauto", "car1"],
"体育": ["hupu", "sports", "sina-sports", "qq-sports", "zhibo8"],
"教育": ["zhihu", "知乎", "bilibili", "jike", "dazhihui"],
"健康": ["zhihu", "知乎", "sina-health", "health", "baikemy"],
"国际": ["sina-news", "netease-news", "qq-news", "ifeng", "cnn", "bbc"],
"房产": ["lfang", "soufunianjia", "anjuke", "house"],
"数码": ["ithome", "coolapk", "sspai", "geekpark", "少数派"],
"时尚": ["mogujie", "meilishuo", "xiaohongshu", "微博时尚", "yoho"],
"美食": ["dianping", "xiaohongshu", "大众点评", "maoyan", "ele.me"]
}
# 所有可用的标签
ALL_TAGS = list(TAG_MAPPING.keys())
class DigestMode(Enum):
"""摘要模式"""
SINGLE = "single" # 单标签
MULTI = "multi" # 多标签
@dataclass
class DigestConfig:
"""摘要配置"""
tags: List[str] # 选择的标签列表
mode: DigestMode = DigestMode.MULTI
items_per_platform: int = 10 # 每个平台显示的条目数
total_items: int = 50 # 总条目数限制
merge_strategy: str = "score" # 合并策略:score(按热度), time(按时间), random(随机)
class NewsDigest:
"""热点新闻摘要类"""
def __init__(self, api_client=None, formatter=None):
"""
初始化新闻摘要
Args:
api_client: API客户端实例(可选,如果不提供则需要外部传入)
formatter: 格式化器实例(可选)
"""
self.api_client = api_client
self.formatter = formatter
self.platforms = self._get_all_platforms()
def _get_all_platforms(self) -> List[str]:
"""获取所有可用的平台"""
platforms = set()
for tag, plats in TAG_MAPPING.items():
platforms.update(plats)
return list(platforms)
async def fetch_all_hot_data(self, limit_per_platform: int = 10) -> List[Dict[str, Any]]:
"""
一次性获取全部54个平台的热榜数据
Args:
limit_per_platform: 每个平台获取的条目数
Returns:
全部平台的热榜数据列表
"""
all_items = []
if self.api_client:
for platform in self.platforms:
try:
items = await self.api_client.get_hot榜单(platform, limit=limit_per_platform)
if items:
for item in items:
item["source_platform"] = platform
all_items.extend(items)
except Exception as e:
print(f"Error fetching {platform}: {e}")
continue
return all_items
async def get_tag_options(self) -> str:
"""
获取标签选择选项(AI引导话术)
Returns:
格式化的标签选择文本
"""
options_text = "📊 **请选择您感兴趣的标签**\n\n"
# 分两列显示
tags_left = ALL_TAGS[:8]
tags_right = ALL_TAGS[8:]
for i, tag in enumerate(tags_left):
right_tag = tags_right[i] if i < len(tags_right) else ""
left_plats = ", ".join(TAG_MAPPING[tag][:3])
right_plats = f"│ {i+8+1}. {right_tag}: {', '.join(TAG_MAPPING[right_tag][:3])}" if right_tag else ""
options_text += f"{i+1}. {tag} ({left_plats}) {right_plats}\n"
options_text += "\n💡 您可以输入标签名称或数字编号,支持多选(如:1,3或科技+游戏)"
return options_text
def parse_tags_from_input(self, user_input: str) -> List[str]:
"""
解析用户输入的标签
Args:
user_input: 用户输入的文本
Returns:
匹配的标签列表
"""
user_input = user_input.strip().lower()
matched_tags = []
# 处理数字选择
numbers = []
import re
number_pattern = re.findall(r'\d+', user_input)
for num in number_pattern:
idx = int(num) - 1
if 0 <= idx < len(ALL_TAGS):
numbers.append(ALL_TAGS[idx])
# 处理标签名称
for tag in ALL_TAGS:
if tag.lower() in user_input or tag in user_input:
if tag not in matched_tags and tag not in numbers:
matched_tags.append(tag)
# 合并数字选择的结果
matched_tags.extend([t for t in numbers if t not in matched_tags])
return matched_tags if matched_tags else []
async def get_digest_by_tags(self, tags: List[str], config: Optional[DigestConfig] = None) -> Dict[str, Any]:
"""
按标签获取新闻摘要(新版:先全部获取,再按标签筛选)
Args:
tags: 标签列表
config: 配置对象(可选)
Returns:
合并后的热榜数据
"""
if config is None:
config = DigestConfig(tags=tags)
# 步骤1:全部获取
all_items = await self.fetch_all_hot_data(limit_per_platform=config.items_per_platform)
# 步骤2:按标签筛选
# 获取所有相关平台
target_platforms = set()
for tag in tags:
if tag in TAG_MAPPING:
target_platforms.update(TAG_MAPPING[tag])
filtered_items = []
for item in all_items:
platform = item.get("source_platform", "")
if platform in target_platforms:
item["source_tag"] = next((t for t in tags if t in TAG_MAPPING and platform in TAG_MAPPING[t]), tags[0])
filtered_items.append(item)
# 去重和合并
merged_items = self._merge_items(filtered_items, config.merge_strategy)
# 限制总数
merged_items = merged_items[:config.total_items]
return {
"tags": tags,
"platforms": list(target_platforms),
"total_items": len(merged_items),
"items": merged_items
}
def _merge_items(self, items: List[Dict], strategy: str = "score") -> List[Dict]:
"""
合并和去重条目
Args:
items: 条目列表
strategy: 合并策略
Returns:
合并后的条目列表
"""
if not items:
return []
# 按标题去重
seen_titles = set()
unique_items = []
for item in items:
title = item.get("title", "").strip().lower()
if title and title not in seen_titles:
seen_titles.add(title)
unique_items.append(item)
# 根据策略排序
if strategy == "score":
# 按热度/分数排序
unique_items.sort(key=lambda x: x.get("hot", 0) or x.get("score", 0), reverse=True)
elif strategy == "time":
# 按时间排序
unique_items.sort(key=lambda x: x.get("time", "") or "", reverse=True)
return unique_items
def format_digest_response(self, digest_data: Dict[str, Any]) -> str:
"""
格式化摘要响应
Args:
digest_data: 摘要数据
Returns:
格式化的响应文本
"""
if not digest_data.get("items"):
return "❌ 暂无热点数据"
tags = digest_data["tags"]
items = digest_data["items"]
response = f"📰 **热点摘要 - {', '.join(tags)}**\n"
response += f"来源平台: {', '.join(digest_data['platforms'])}\n"
response += f"共 {digest_data['total_items']} 条热点\n"
response += "-" * 40 + "\n\n"
for i, item in enumerate(items, 1):
title = item.get("title", "无标题")
hot = item.get("hot", item.get("score", ""))
platform = item.get("source_platform", "")
response += f"{i}. {title}\n"
if hot:
response += f" 🔥 热度: {hot}"
if platform:
response += f" | 📱 {platform}"
response += "\n\n"
return response
async def process_user_request(self, user_input: str) -> Dict[str, Any]:
"""
处理用户请求
Args:
user_input: 用户输入
Returns:
处理结果
"""
# 解析标签
tags = self.parse_tags_from_input(user_input)
if not tags:
# 返回引导信息
return {
"action": "ask_tag",
"message": await self.get_tag_options()
}
# 获取摘要
digest_data = await self.get_digest_by_tags(tags)
# 格式化响应
response_text = self.format_digest_response(digest_data)
return {
"action": "show_digest",
"data": digest_data,
"message": response_text
}
# 便捷函数
async def create_digest(api_client=None, formatter=None) -> NewsDigest:
"""创建新闻摘要实例"""
return NewsDigest(api_client=api_client, formatter=formatter)
if __name__ == "__main__":
# 测试代码
async def test():
digest = await create_digest()
print(await digest.get_tag_options())
print("\n" + "="*50 + "\n")
result = await digest.process_user_request("科技和游戏")
print(result["message"])
asyncio.run(test())
"""
个性化订阅(增强版) - Personalized Subscription Module
功能:
- 用户自主配置:关键词、平台、排除项
- AI主动提供备选项
- 关键词过滤和偏好排序
- 用户配置存储
"""
from typing import List, Dict, Optional, Any, Set
from dataclasses import dataclass, field, asdict
from enum import Enum
from datetime import datetime
import json
import asyncio
import os
# 可选的关键词标签
KEYWORD_OPTIONS = [
"AI", "ChatGPT", "人工智能", "大模型", "机器学习",
"游戏", "原神", "英雄联盟", "王者荣耀", "米哈游",
"科技产品", "iPhone", "华为", "小米", "特斯拉",
"新能源汽车", "比亚迪", "宁德时代", "蔚来", "小鹏",
"互联网", "字节跳动", "腾讯", "阿里巴巴", "美团",
"电商", "直播", "短视频", "网红", "明星八卦",
"影视", "电影", "电视剧", "综艺", "动漫",
"财经", "股票", "基金", "加密货币", "比特币",
"房产", "房价", "房地产", "房贷", "租房",
"美食", "餐厅", "外卖", "网红店", "探店",
"旅游", "出行", "机票", "酒店", "景点",
"时尚", "穿搭", "美妆", "护肤", "奢侈品",
"体育", "足球", "篮球", "NBA", "奥运会",
"教育", "高考", "考研", "留学", "职场"
]
# 可选平台
PLATFORM_OPTIONS = [
"微博", "知乎", "B站", "抖音", "快手",
"原神", "米游社", "IT之家", "36氪", "虎嗅",
"豆瓣", "小红书", "今日头条", "澎湃新闻", "观察者网"
]
# 排除关键词示例
EXCLUDE_OPTIONS = [
"广告", "推广", "营销号", "震惊", "必看",
"流量明星", "网红脸", "擦边", "引战"
]
class SubscriptionMode(Enum):
"""订阅模式"""
INCLUDE = "include" # 包含模式
EXCLUDE = "exclude" # 排除模式
@dataclass
class UserPreferences:
"""用户偏好配置"""
keywords: List[str] = field(default_factory=list) # 关注的关键词
platforms: List[str] = field(default_factory=list) # 关注的平台
exclude_keywords: List[str] = field(default_factory=list) # 排除的关键词
subscription_mode: SubscriptionMode = SubscriptionMode.INCLUDE
items_per_platform: int = 10
total_items: int = 30
sort_by: str = "relevance" # relevance(相关性), hot(热度), time(时间)
created_at: str = field(default_factory=lambda: datetime.now().isoformat())
updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
def to_dict(self) -> Dict:
"""转换为字典"""
data = asdict(self)
data["subscription_mode"] = self.subscription_mode.value
return data
@classmethod
def from_dict(cls, data: Dict) -> "UserPreferences":
"""从字典创建"""
if "subscription_mode" in data and isinstance(data["subscription_mode"], str):
data["subscription_mode"] = SubscriptionMode(data["subscription_mode"])
return cls(**data)
class PersonalizedSubscription:
"""个性化订阅类"""
def __init__(self, api_client=None, formatter=None, storage_path: str = None):
"""
初始化个性化订阅
Args:
api_client: API客户端实例(可选)
formatter: 格式化器实例(可选)
storage_path: 配置存储路径(可选)
"""
self.api_client = api_client
self.formatter = formatter
self.storage_path = storage_path or os.path.join(
os.path.dirname(__file__),
"personalized_config.json"
)
self.current_config = None
self._all_platforms = self._get_all_platforms()
self._load_config()
def _get_all_platforms(self) -> List[str]:
"""获取所有可用的平台"""
return [self._platform_name_to_id(p) for p in PLATFORM_OPTIONS]
def _platform_name_to_id(self, platform_name: str) -> str:
"""将平台名称转换为API ID"""
mapping = {
"微博": "weibo",
"知乎": "zhihu",
"B站": "bilibili",
"抖音": "douyin",
"快手": "kuaishou",
"原神": "genshin",
"米游社": "miyoushe",
"IT之家": "ithome",
"36氪": "36kr",
"虎嗅": "huxiu",
"豆瓣": "douban-group",
"小红书": "xiaohongshu",
"今日头条": "jinritoutiao",
"澎湃新闻": "thepaper",
"观察者网": "guanchazhe"
}
return mapping.get(platform_name, platform_name.lower())
def _load_config(self) -> Optional[UserPreferences]:
"""加载用户配置"""
if os.path.exists(self.storage_path):
try:
with open(self.storage_path, 'r', encoding='utf-8') as f:
data = json.load(f)
self.current_config = UserPreferences.from_dict(data)
return self.current_config
except Exception as e:
print(f"Error loading config: {e}")
return None
def _save_config(self, config: UserPreferences) -> bool:
"""保存用户配置"""
try:
# 更新修改时间
config.updated_at = datetime.now().isoformat()
with open(self.storage_path, 'w', encoding='utf-8') as f:
json.dump(config.to_dict(), f, ensure_ascii=False, indent=2)
self.current_config = config
return True
except Exception as e:
print(f"Error saving config: {e}")
return False
async def fetch_all_hot_data(self, limit_per_platform: int = 10) -> List[Dict[str, Any]]:
"""
一次性获取全部平台的热榜数据
Args:
limit_per_platform: 每个平台获取的条目数
Returns:
全部平台的热榜数据列表
"""
all_items = []
if self.api_client:
for platform in self._all_platforms:
try:
items = await self.api_client.get_hot榜单(platform, limit=limit_per_platform)
if items:
for item in items:
item["source_platform"] = platform
all_items.extend(items)
except Exception as e:
print(f"Error fetching {platform}: {e}")
continue
return all_items
async def get_config_options(self) -> str:
"""
获取配置选项(AI引导话术)
Returns:
格式化的配置引导文本
"""
options_text = "⚙️ **个性化热榜配置**\n\n"
# 关键词选项
options_text += "**【关键词】**\n"
options_text += "可选标签:\n"
# 分组显示关键词
keyword_groups = [
("科技类", ["AI", "人工智能", "大模型", "科技产品", "iPhone", "华为", "小米"]),
("游戏类", ["游戏", "原神", "英雄联盟", "王者荣耀", "米哈游"]),
("汽车类", ["特斯拉", "新能源汽车", "比亚迪", "蔚来", "小鹏"]),
("财经类", ["财经", "股票", "基金", "加密货币", "比特币"]),
("娱乐类", ["影视", "综艺", "明星八卦", "网红"]),
]
for group_name, keywords in keyword_groups:
options_text += f" • {group_name}: {', '.join(keywords)}\n"
options_text += "\n**【平台】**\n"
options_text += f"可选:{', '.join(PLATFORM_OPTIONS)}\n"
options_text += "\n**【排除项】**\n"
options_text += f"可选:{', '.join(EXCLUDE_OPTIONS)}\n"
options_text += "\n" + "-" * 50 + "\n"
options_text += "💡 请告诉我您的偏好设置,我会帮您定制热榜!\n"
options_text += "示例:关注AI和游戏,平台选微博、B站、IT之家,排除广告"
return options_text
def parse_config_from_input(self, user_input: str) -> Dict[str, Any]:
"""
从用户输入解析配置
Args:
user_input: 用户输入
Returns:
解析的配置信息
"""
user_input = user_input.lower()
# 解析关键词
keywords = []
for kw in KEYWORD_OPTIONS:
if kw.lower() in user_input:
keywords.append(kw)
# 解析平台
platforms = []
platform_mapping = {
"微博": ["微博", "weibo"],
"知乎": ["知乎", "zhihu"],
"B站": ["b站", "bilibili", "B站"],
"抖音": ["抖音", "tiktok"],
"快手": ["快手"],
"原神": ["原神", "genshin"],
"米游社": ["米游社", "miyoushe"],
"IT之家": ["it之家", "ithome", "IT之家"],
"36氪": ["36氪", "36kr"],
"虎嗅": ["虎嗅", "huxiu"],
"豆瓣": ["豆瓣", "douban"],
"小红书": ["小红书", "xiaohongshu"],
"今日头条": ["今日头条", "头条"],
"澎湃新闻": ["澎湃", "澎湃新闻"],
"观察者网": ["观察者网", "guanchazhe"],
}
for platform, aliases in platform_mapping.items():
for alias in aliases:
if alias.lower() in user_input:
if platform not in platforms:
platforms.append(platform)
break
# 解析排除关键词
exclude_keywords = []
for ex in EXCLUDE_OPTIONS:
if ex.lower() in user_input:
exclude_keywords.append(ex)
return {
"keywords": keywords,
"platforms": platforms,
"exclude_keywords": exclude_keywords
}
async def configure_subscription(self, user_input: str) -> Dict[str, Any]:
"""
配置个性化订阅
Args:
user_input: 用户输入的配置信息
Returns:
配置结果
"""
parsed = self.parse_config_from_input(user_input)
# 检查是否有配置信息
if not parsed["keywords"] and not parsed["platforms"]:
# 返回引导信息
return {
"action": "ask_config",
"message": await self.get_config_options()
}
# 创建配置
config = UserPreferences(
keywords=parsed["keywords"],
platforms=parsed["platforms"],
exclude_keywords=parsed["exclude_keywords"]
)
# 保存配置
if self._save_config(config):
return {
"action": "config_saved",
"config": config,
"message": self._format_config_confirmation(config)
}
else:
return {
"action": "error",
"message": "❌ 配置保存失败,请重试"
}
def _format_config_confirmation(self, config: UserPreferences) -> str:
"""
格式化配置确认信息
Args:
config: 用户配置
Returns:
确认信息文本
"""
response = "✅ **配置完成!**\n\n"
response += f"**关键词**: {', '.join(config.keywords) if config.keywords else '未设置'}\n"
response += f"**平台**: {', '.join(config.platforms) if config.platforms else '未设置'}\n"
response += f"**排除项**: {', '.join(config.exclude_keywords) if config.exclude_keywords else '无'}\n"
response += "\n" + "-" * 40 + "\n"
response += "📊 您可以输入「查看热榜」或「刷新热榜」来获取个性化热榜内容"
return response
async def get_personalized_hot(self, config: Optional[UserPreferences] = None) -> Dict[str, Any]:
"""
获取个性化热榜(新版:先全部获取,再按用户配置过滤)
Args:
config: 配置对象(可选,默认使用当前配置)
Returns:
个性化热榜数据
"""
if config is None:
config = self.current_config
if not config:
return {
"error": "未配置个性化订阅",
"action": "ask_config",
"message": "请先配置您的个性化热榜偏好"
}
# 步骤1:全部获取(如果用户配置了平台,则获取配置的平台;否则获取全部)
if config.platforms:
platform_ids = [self._platform_name_to_id(p) for p in config.platforms]
else:
platform_ids = self._all_platforms
all_items = []
if self.api_client:
for platform_id in platform_ids:
try:
items = await self.api_client.get_hot榜单(platform_id, limit=config.items_per_platform)
if items:
for item in items:
item["source_platform"] = platform_id
all_items.extend(items)
except Exception as e:
print(f"Error fetching {platform_id}: {e}")
continue
# 步骤2:按用户配置过滤
filtered_items = self._filter_items(all_items, config)
# 限制总数
filtered_items = filtered_items[:config.total_items]
return {
"config": config,
"total_items": len(filtered_items),
"items": filtered_items
}
def _filter_items(self, items: List[Dict], config: UserPreferences) -> List[Dict]:
"""
过滤条目
Args:
items: 条目列表
config: 用户配置
Returns:
过滤后的条目列表
"""
if not items:
return []
filtered = []
for item in items:
title = item.get("title", "").lower()
desc = item.get("description", "").lower()
# 排除关键词过滤
should_exclude = False
for ex_kw in config.exclude_keywords:
if ex_kw.lower() in title or ex_kw.lower() in desc:
should_exclude = True
break
if should_exclude:
continue
# 关键词匹配(如果设置了关键词)
if config.keywords:
matches_keyword = False
for kw in config.keywords:
if kw.lower() in title or kw.lower() in desc:
matches_keyword = True
break
if not matches_keyword:
continue
filtered.append(item)
# 排序
if config.sort_by == "hot":
filtered.sort(key=lambda x: x.get("hot", 0) or x.get("score", 0), reverse=True)
elif config.sort_by == "time":
filtered.sort(key=lambda x: x.get("time", "") or "", reverse=True)
# relevance保持原顺序
return filtered
def format_personalized_response(self, hot_data: Dict[str, Any]) -> str:
"""
格式化个性化热榜响应
Args:
hot_data: 热榜数据
Returns:
格式化的响应文本
"""
if "error" in hot_data:
return hot_data.get("message", "❌ 获取失败")
config = hot_data.get("config")
items = hot_data.get("items", [])
if not items:
return "❌ 暂无符合条件的热榜内容"
response = "🎯 **个性化热榜**\n\n"
if config and config.keywords:
response += f"关键词: {', '.join(config.keywords)}\n"
if config and config.platforms:
response += f"平台: {', '.join(config.platforms)}\n"
response += "-" * 40 + "\n"
response += f"共 {hot_data['total_items']} 条\n\n"
for i, item in enumerate(items, 1):
title = item.get("title", "无标题")
hot = item.get("hot", item.get("score", ""))
platform = item.get("source_platform", "")
response += f"{i}. {title}\n"
if hot:
response += f" 🔥 {hot}"
if platform:
response += f" | 📱 {platform}"
response += "\n"
return response
async def process_user_request(self, user_input: str) -> Dict[str, Any]:
"""
处理用户请求
Args:
user_input: 用户输入
Returns:
处理结果
"""
user_input_lower = user_input.lower()
# 检查是否是查看热榜请求
if "热榜" in user_input or "hot" in user_input_lower:
if self.current_config:
hot_data = await self.get_personalized_hot()
response_text = self.format_personalized_response(hot_data)
return {
"action": "show_hot",
"data": hot_data,
"message": response_text
}
else:
return {
"action": "ask_config",
"message": "请先配置个性化热榜偏好"
}
# 检查是否是配置请求
if "配置" in user_input or "设置" in user_input or "偏好" in user_input:
return await self.configure_subscription(user_input)
# 默认当作配置处理
return await self.configure_subscription(user_input)
def get_current_config(self) -> Optional[UserPreferences]:
"""获取当前配置"""
return self.current_config
def clear_config(self) -> bool:
"""清除配置"""
if self.storage_path and os.path.exists(self.storage_path):
try:
os.remove(self.storage_path)
self.current_config = None
return True
except Exception as e:
print(f"Error clearing config: {e}")
return False
return True
# 便捷函数
async def create_personalized(api_client=None, formatter=None, storage_path: str = None) -> PersonalizedSubscription:
"""创建个性化订阅实例"""
return PersonalizedSubscription(api_client=api_client, formatter=formatter, storage_path=storage_path)
if __name__ == "__main__":
# 测试代码
async def test():
ps = await create_personalized()
print("配置选项:")
print(await ps.get_config_options())
print("\n" + "="*50 + "\n")
# 测试配置
result = await ps.process_user_request("关注AI和游戏,平台选微博、B站、IT之家")
print(result["message"])
asyncio.run(test())
<div align="center">
🔥 Daily Hot News - OpenClaw Skill
  
---
🇺🇸 English Readme | 📖 [中文说明](README.md)
---
A hot news aggregation skill based on DailyHotApi, supporting 54 platform hot search queries, cross-platform aggregation, and sentiment monitoring.
</div>
🎯 Features
Core Features
- Hot Search Query: Query hot search data from any of 54 platforms
- Category Browse: Quickly locate specific platforms by category
- Real-time Fetch: Always get the latest hot search data on each request
- History: Automatically save daily hot search data to local storage
- Smart Cleanup: Automatically check and prompt to clean data older than 7 days on startup
Extended Features
- Hot News Digest: 15-tag classification, AI-guided selection
- Industry Vertical: 10 industry categories (tech, gaming, finance, etc.)
- Personalized Subscription: Custom keywords and platform preferences
- Cross-Platform Aggregation: Top 10 hot search榜单 nationwide
- Sentiment Monitoring: Keyword monitoring and hot alerts
📊 Supported Platforms (54)
🎬 Video/Live Streaming (5)
| Platform | API | Description |
|---|---|---|
| Bilibili | bilibili | Hot Ranking |
| AcFun | acfun | Ranking List |
| Douyin | douyin | Hot Topics |
| Kuaishou | kuaishou | Hot Topics |
| Coolapk | coolapk | Hot Ranking |
💬 Social Media (8)
| Platform | API | Description |
|---|---|---|
| Hot Search | ||
| Zhihu | zhihu | Hot List |
| Zhihu Daily | zhihu-daily | Recommended |
| Tieba | tieba | Hot Discussion |
| Douban Group | douban-group | Discussion Picks |
| V2EX | v2ex | Topic Ranking |
| NGA | ngabbs | Hot Posts |
| Hupu | hupu | Street Hot Posts |
📰 News & Media (10)
| Platform | API | Description |
|---|---|---|
| Baidu | baidu | Hot Search |
| The Paper | thepaper | Hot List |
| Toutiao | toutiao | Hot List |
| 36kr | 36kr | Hot List |
| QQ News | qq-news | Hot Topics |
| Sina | sina | Hot List |
| Sina News | sina-news | Hot Topics |
| NetEase News | netease-news | Hot Topics |
| Huxiu | huxiu | 24 Hours |
| Ifanr | ifanr | Quick News |
💻 Tech/Developer Communities (8)
| Platform | API | Description |
|---|---|---|
| IT Home | ithome | Hot List |
| IT Home Xijiayi | ithome-xijiayi | Latest Updates |
| Sspai | sspai | Hot List |
| CSDN | csdn | Ranking List |
| Juejin | juejin | Hot List |
| 51CTO | 51cto | Recommended |
| NodeSeek | nodeseek | Latest Updates |
| HelloGitHub | hellogithub | Trending |
🎮 Gaming/ACG (5)
| Platform | API | Description |
|---|---|---|
| Genshin | genshin | Latest News |
| MiyouShe | miyoushe | Latest News |
| Honkai 3 | honkai | Latest Updates |
| StarRail | starrail | Latest Updates |
| LOL | lol | Update Notice |
📚 Reading/Culture (4)
| Platform | API | Description |
|---|---|---|
| Jianshu | jianshu | Popular Recommendations |
| Guokr | guokr | Popular Articles |
| WeRead | weread | Rising List |
| Douban Movie | douban-movie | New Movies |
🔧 Tools/Other (5)
| Platform | API | Description |
|---|---|---|
| 52pojie | 52pojie | Ranking List |
| HostLoc | hostloc | Ranking List |
| Weather Alarm | weatheralarm | National Warning |
| Earthquake | earthquake | Earthquake Report |
| History Today | history | Month-Day |
🚀 Quick Start
1. Deploy Backend Service
cd daily-hot-api
./deploy.sh2. Install Dependencies
pip install -r requirements.txt3. Configure Environment Variables
export DAILY_HOT_API_URL=http://localhost:66884. Run
python3 daily_hot_news.py --query "weibo hot"💬 Usage Examples
Example 1: Query Single Platform
# Query Weibo hot search
python3 daily_hot_news.py -q "微博热搜"
# Query Zhihu hot list
python3 daily_hot_news.py -q "知乎热榜"
# Query Bilibili trending
python3 daily_hot_news.py -q "B站热门"
# Query Genshin latest
python3 daily_hot_news.py -q "原神"Output:
🔥 **Weibo Hot Search**
Update Time: 2026-02-05T19:00:00.000Z
1. Wang Yibo Zhongshan Suit 5.2M
2. Xiao Zhan Shy Smile 4.8M
3. Weibo Night Red Carpet 4.5M
...Example 2: Browse All Platforms
# View all supported hot search sources
python3 daily_hot_news.py --listOutput:
📊 **Supported Hot Search Sources (54)**
🎬 Video/Live Streaming
• Bilibili (bilibili)
• Douyin (douyin)
• Kuaishou (kuaishou)
• AcFun (acfun)
• Coolapk (coolapk)
💬 Social Media
• Weibo (weibo)
• Zhihu (zhihu)
• V2EX (v2ex)
• NGA (ngabbs)
...Example 3: Cross-Platform Aggregation
# View nationwide Top 10
python3 daily_hot_news.py --cross-platformOutput:
🌐 Nationwide Hot Topics TOP10
🥇 Genshin Update · 9.52M 【Highest on Bilibili】
Score: 98 - 🔥 Super Popular
Discussed on 5 platforms
🥈 Weibo Hot · 8.76M 【Highest on Weibo】
Score: 95 - Nationwide Discussion
🥉 Trump · 6.54M 【Highest on Weibo】
Score: 89 - International Topic
...Example 4: Set Sentiment Monitoring
# Set monitoring: AI topic, alert when exceeds 5M
python3 daily_hot_news.py --monitor "AI,500万"
# View monitoring configuration
python3 daily_hot_news.py -q "查看我的监控"Output:
✅ **Monitoring Set!**
Monitoring Keyword: AI
Hot Threshold: 5M
Platforms: All 54
I'll notify you when any topic exceeds the threshold!Example 5: Scheduled Push Configuration
# Configure daily 8 AM Weibo hot push
"每天早上 8 点推送微博热搜"Output:
⏰ **Scheduled Push Configured!**
Push Time: Daily 08:00
Hot Source: Weibo Hot Search
Push Method: Feishu Message
I'll automatically push Weibo hot to your Feishu every morning at 8!🎮 Advanced Usage
Query by Category
# View tech hot searches
"有什么科技热榜"
# View gaming hot topics
"游戏有什么热点"Search Specific Topics
# Search for topics containing keywords
"搜索 AI 相关热榜"
"查找 ChatGPT 热点"View Historical Data
# View yesterday's Weibo hot
"微博昨天"
# View history records
"微博历史"View Saved Data
# View saved hot search statistics
"已保存了哪些数据"Cleanup Old Data
# Reply "清理" or "是" to delete data older than 7 days
# Skill will automatically detect and prompt on startup📁 File Structure
daily-hot-news/
├── daily_hot_news.py # Main Entry
├── news_digest.py # Hot News Digest
├── industry_hot.py # Industry Vertical
├── personalized.py # Personalized Subscription
├── cross_platform.py # Cross-Platform Aggregation
├── sentiment_monitor.py # Sentiment Monitoring
├── api_client.py # API Client
├── formatter.py # Formatter
├── storage.py # Data Storage
├── config.py # Configuration
├── requirements.txt # Dependencies
└── README.md # This Document⚙️ Configuration
| Environment Variable | Default | Description |
|---|---|---|
DAILY_HOT_API_URL | http://localhost:6688 | Backend API URL |
DAILY_HOT_CACHE_TTL | 3600 | Cache Time (seconds) |
DAILY_HOT_MAX_ITEMS | 20 | Max Items Returned |
DAILY_HOT_TIMEOUT | 10 | Request Timeout (seconds) |
🔧 Technical Architecture
- API Calls: Each user request triggers a real-time call to DailyHotApi for the latest data
- History: Automatically saved to
data/{platform}/{date}.json - Old Data Cleanup: Automatically detects data older than 7 days on startup and prompts for cleanup
🛡️ Security Note
- ✅ No API keys or passwords included
- ✅ All sensitive configurations managed via environment variables
- ✅ User data stored locally, not uploaded to cloud
📝 Changelog
v2.0.1 (2026-02-06)
- ✨ Optimized data fetching: always fetch latest data on each request
- ✨ Auto-save history records to local storage
- ✨ Auto-check and prompt cleanup for data older than 7 days on startup
- 🔧 Fixed API client compatibility issues
v2.0.0 (2026-02-05)
- ✨ Added 5 extended features
- ✨ Support 15 hot tag classifications
- ✨ Support 10 industry vertical hot searches
- ✨ Added personalized subscription feature
- ✨ Added cross-platform TOP10 aggregation
- ✨ Added sentiment monitoring alerts
📄 License
MIT License
🤝 Acknowledgements
- DailyHotApi - Providing 54 hot search source APIs
- OpenClaw - AI Assistant Platform
<div align="center">
🔥 每日热榜 - OpenClaw Skill
  
---
📖 [中文说明](README.md) | 🇺🇸 [English Readme](README_EN.md)
---
一个基于 DailyHotApi 的热榜聚合查询技能,支持 54 个平台热榜查询、跨平台聚合、舆情监控等功能。
</div>
🎯 功能特性
核心功能
- 热榜查询:查询任意 54 个平台的热榜数据
- 分类浏览:按类别快速定位特定平台
- 实时获取:每次请求都获取最新热榜数据
- 历史记录:自动保存每日热榜数据到本地
- 智能清理:启动时自动检查并提示清理7天前的旧数据
扩展功能
- 热点摘要:15 种标签分类,AI 引导选择
- 行业垂直:十大行业分类,科技/游戏/金融等
- 个性化订阅:自定义关键词和平台偏好
- 跨平台聚合:全网 TOP10 热点榜单
- 舆情监控:关键词监控和热度告警
📊 支持平台(54 个)
🎬 视频/直播平台(5 个)
| 平台 | 接口 | 说明 |
|---|---|---|
| 哔哩哔哩 | bilibili | 热门榜 |
| AcFun | acfun | 排行榜 |
| 抖音 | douyin | 热点榜 |
| 快手 | kuaishou | 热点榜 |
| 酷安 | coolapk | 热榜 |
💬 社交媒体(8 个)
| 平台 | 接口 | 说明 |
|---|---|---|
| 微博 | 热搜榜 | |
| 知乎 | zhihu | 热榜 |
| 知乎日报 | zhihu-daily | 推荐榜 |
| 百度贴吧 | tieba | 热议榜 |
| 豆瓣讨论小组 | douban-group | 讨论精选 |
| V2EX | v2ex | 主题榜 |
| NGA | ngabbs | 热帖 |
| 虎扑 | hupu | 步行街热帖 |
📰 新闻资讯(10 个)
| 平台 | 接口 | 说明 |
|---|---|---|
| 百度 | baidu | 热搜榜 |
| 澎湃新闻 | thepaper | 热榜 |
| 今日头条 | toutiao | 热榜 |
| 36氪 | 36kr | 热榜 |
| 腾讯新闻 | qq-news | 热点榜 |
| 新浪网 | sina | 热榜 |
| 新浪新闻 | sina-news | 热点榜 |
| 网易新闻 | netease-news | 热点榜 |
| 虎嗅 | huxiu | 24小时 |
| 爱范儿 | ifanr | 快讯 |
💻 科技/技术社区(8 个)
| 平台 | 接口 | 说明 |
|---|---|---|
| IT之家 | ithome | 热榜 |
| IT之家「喜加一」 | ithome-xijiayi | 最新动态 |
| 少数派 | sspai | 热榜 |
| CSDN | csdn | 排行榜 |
| 稀土掘金 | juejin | 热榜 |
| 51CTO | 51cto | 推荐榜 |
| NodeSeek | nodeseek | 最新动态 |
| HelloGitHub | hellogithub | Trending |
🎮 游戏/ACG(5 个)
| 平台 | 接口 | 说明 |
|---|---|---|
| 原神 | genshin | 最新消息 |
| 米游社 | miyoushe | 最新消息 |
| 崩坏3 | honkai | 最新动态 |
| 崩坏:星穹铁道 | starrail | 最新动态 |
| 英雄联盟 | lol | 更新公告 |
📚 阅读/文化(4 个)
| 平台 | 接口 | 说明 |
|---|---|---|
| 简书 | jianshu | 热门推荐 |
| 果壳 | guokr | 热门文章 |
| 微信读书 | weread | 飙升榜 |
| 豆瓣电影 | douban-movie | 新片榜 |
🔧 工具/其他(5 个)
| 平台 | 接口 | 说明 |
|---|---|---|
| 吾爱破解 | 52pojie | 榜单 |
| 全球主机交流 | hostloc | 榜单 |
| 中央气象台 | weatheralarm | 全国气象预警 |
| 中国地震台 | earthquake | 地震速报 |
| 历史上的今天 | history | 月-日 |
🚀 快速开始
1. 部署后端服务
cd daily-hot-api
./deploy.sh2. 安装依赖
pip install -r requirements.txt3. 配置环境变量
export DAILY_HOT_API_URL=http://localhost:66884. 运行
python3 daily_hot_news.py --query "微博热搜"💬 使用案例
案例 1:查询单个平台热榜
# 查询微博热搜
python3 daily_hot_news.py -q "微博热搜"
# 查询知乎热榜
python3 daily_hot_news.py -q "知乎热榜"
# 查询 B站热门
python3 daily_hot_news.py -q "B站热门"
# 查询原神最新消息
python3 daily_hot_news.py -q "原神"效果:
🔥 **微博热搜**
更新时间: 2026-02-05T19:00:00.000Z
1. 王一博 中山装 520万
2. 肖战 害羞笑 480万
3. 微博之夜红毯 450万
...案例 2:浏览所有平台
# 查看所有支持的热榜源
python3 daily_hot_news.py --list效果:
📊 **支持的热榜源(共 54 个)**
🎬 视频/直播
• 哔哩哔哩 (bilibili)
• 抖音 (douyin)
• 快手 (kuaishou)
• AcFun (acfun)
• 酷安 (coolapk)
💬 社交媒体
• 微博 (weibo)
• 知乎 (zhihu)
• V2EX (v2ex)
• NGA (ngabbs)
...案例 3:跨平台聚合
# 查看全网热点 TOP10
python3 daily_hot_news.py --cross-platform效果:
🌐 全网热点 TOP10
🥇 原神 · 热度 952万 【B站最高】
评分: 98分 - 🔥 超级爆款
跨 5 平台讨论
🥈 微博热搜 · 热度 876万 【微博最高】
评分: 95分 - 全网讨论
🥉 特朗普 · 热度 654万 【微博最高】
评分: 89分 - 国际话题
...案例 4:设置舆情监控
# 设置监控:AI 话题,热度超过 500 万通知
python3 daily_hot_news.py --monitor "AI,500万"
# 查看监控配置
python3 daily_hot_news.py -q "查看我的监控"效果:
✅ **已设置监控!**
监控关键词:AI
热度阈值:500万
监控平台:全部 54 个
当有话题热度超过阈值时,我会立即通知您!案例 5:定时推送配置
# 配置每天早上 8 点推送微博热搜
"每天早上 8 点推送微博热搜"效果:
⏰ **定时推送已设置!**
推送时间:每天 08:00
热榜源:微博热搜
推送方式:飞书消息
每天早上 8 点会自动推送微博热榜到您的飞书!🎮 高级用法
按类别查询
# 查看科技类热榜
"有什么科技热榜"
# 查看游戏类热榜
"游戏有什么热点"搜索特定话题
# 搜索包含某个关键词的热榜
"搜索 AI 相关热榜"
"查找 ChatGPT 热点"查看历史数据
# 查看昨天的微博热榜
"微博昨天"
# 查看历史记录
"微博历史"查看已保存数据
# 查看已保存的热榜统计
"已保存了哪些数据"清理旧数据
# 回复"清理"或"是"可删除7天前的旧热榜数据
# Skill启动时会自动检测并提示📁 文件结构
daily-hot-news/
├── daily_hot_news.py # 主入口
├── news_digest.py # 热点摘要
├── industry_hot.py # 行业垂直
├── personalized.py # 个性化订阅
├── cross_platform.py # 跨平台聚合
├── sentiment_monitor.py # 舆情监控
├── api_client.py # API 客户端
├── formatter.py # 格式化器
├── storage.py # 数据存储
├── config.py # 配置
├── requirements.txt # 依赖列表
└── README.md # 本说明⚙️ 配置说明
| 环境变量 | 默认值 | 说明 |
|---|---|---|
DAILY_HOT_API_URL | http://localhost:6688 | 后端 API 地址 |
DAILY_HOT_CACHE_TTL | 3600 | 缓存时间(秒) |
DAILY_HOT_MAX_ITEMS | 20 | 返回最大条数 |
DAILY_HOT_TIMEOUT | 10 | 请求超时(秒) |
🔧 技术架构
- API 调用:每次用户请求都实时调用 DailyHotApi,确保数据最新
- 历史记录:自动保存到
data/{平台}/{日期}.json - 旧数据清理:启动时自动检测 7 天前数据,提示用户清理
🛡️ 安全说明
- ✅ 不包含任何 API 密钥或密码
- ✅ 所有敏感配置通过环境变量管理
- ✅ 用户数据存储在本地,不上传云端
📝 更新日志
v2.0.1 (2026-02-06)
- ✨ 优化数据获取策略:每次请求都获取最新数据
- ✨ 自动保存历史记录到本地
- ✨ 启动时自动检查并提示清理7天前旧数据
- 🔧 修复 API 客户端兼容性问题
v2.0.0 (2026-02-05)
- ✨ 新增 5 个扩展功能
- ✨ 支持 15 种热点标签分类
- ✨ 支持十大行业垂直热榜
- ✨ 新增个性化订阅功能
- ✨ 新增跨平台 TOP10 聚合
- ✨ 新增舆情监控告警
📄 许可证
MIT License
🤝 致谢
- DailyHotApi - 提供 54 个热榜源 API
- OpenClaw - AI 助手平台
# DailyHotApi Skill - 依赖列表
# 核心依赖
requests>=2.28.0
aiohttp>=3.8.0
# 可选依赖(用于测试)
pytest>=7.0.0
pytest-asyncio>=0.20.0
"""
舆情监控 - Sentiment Monitoring Module
功能:
- 直接用全量数据做关键词过滤
- 监控特定关键词的舆情
"""
from typing import List, Dict, Optional, Any
from dataclasses import dataclass, field
from enum import Enum
from datetime import datetime
import asyncio
class SentimentType(Enum):
"""舆情类型"""
ALL = "all" # 全部
POSITIVE = "positive" # 正面
NEGATIVE = "negative" # 负面
NEUTRAL = "neutral" # 中性
@dataclass
class SentimentConfig:
"""舆情监控配置"""
keywords: List[str] = field(default_factory=list) # 监控关键词
sentiment_type: SentimentType = SentimentType.ALL # 舆情类型
items_per_platform: int = 10 # 每个平台获取的条目数
total_items: int = 50 # 总条目数限制
include_platforms: List[str] = None # 包含的平台
exclude_platforms: List[str] = None # 排除的平台
class SentimentMonitor:
"""舆情监控类"""
def __init__(self, api_client=None, formatter=None):
"""
初始化舆情监控
Args:
api_client: API客户端实例(可选)
formatter: 格式化器实例(可选)
"""
self.api_client = api_client
self.formatter = formatter
self.all_platforms = self._get_default_platforms()
def _get_default_platforms(self) -> List[str]:
"""获取默认的全部平台列表"""
return [
"weibo", "zhihu", "douban-group", "douban-movie",
"ithome", "36kr", "sspai", "csdn", "juejin",
"genshin", "miyoushe", "bilibili", "hupu",
"sina-news", "netease-news", "qq-news",
"sina-money", "eastmoney", "xueqiu",
"autohome", "懂车帝", "mafengwo", "ctrip",
"dianping", "xiaohongshu", "weibo"
]
async def fetch_all_hot_data(self, limit_per_platform: int = 10) -> List[Dict[str, Any]]:
"""
一次性获取全部平台的热榜数据
Args:
limit_per_platform: 每个平台获取的条目数
Returns:
全部平台的热榜数据列表
"""
all_items = []
if self.api_client:
for platform in self.all_platforms:
try:
items = await self.api_client.get_hot榜单(platform, limit=limit_per_platform)
if items:
for item in items:
item["source_platform"] = platform
all_items.extend(items)
except Exception as e:
print(f"Error fetching {platform}: {e}")
continue
return all_items
async def monitor_keywords(self, keywords: List[str], config: Optional[SentimentConfig] = None) -> Dict[str, Any]:
"""
监控关键词舆情(新版:先全部获取,再关键词过滤)
Args:
keywords: 关键词列表
config: 配置对象(可选)
Returns:
舆情监控数据
"""
if config is None:
config = SentimentConfig(keywords=keywords)
# 步骤1:全部获取
all_items = await self.fetch_all_hot_data(limit_per_platform=config.items_per_platform)
# 步骤2:关键词过滤
filtered_items = self._filter_by_keywords(all_items, config.keywords)
# 平台过滤
if config.include_platforms:
filtered_items = [item for item in filtered_items if item.get("source_platform") in config.include_platforms]
if config.exclude_platforms:
filtered_items = [item for item in filtered_items if item.get("source_platform") not in config.exclude_platforms]
# 限制总数
filtered_items = filtered_items[:config.total_items]
return {
"keywords": config.keywords,
"total_items": len(filtered_items),
"items": filtered_items
}
def _filter_by_keywords(self, items: List[Dict], keywords: List[str]) -> List[Dict]:
"""
按关键词过滤
Args:
items: 条目列表
keywords: 关键词列表
Returns:
过滤后的条目列表
"""
if not keywords:
return items
filtered = []
for item in items:
title = item.get("title", "").lower()
desc = item.get("description", "").lower()
# 检查是否匹配任何关键词
matches = False
for kw in keywords:
if kw.lower() in title or kw.lower() in desc:
matches = True
break
if matches:
filtered.append(item)
# 按热度排序
filtered.sort(key=lambda x: x.get("hot", 0) or x.get("score", 0), reverse=True)
return filtered
def format_monitoring_response(self, monitor_data: Dict[str, Any]) -> str:
"""
格式化监控响应
Args:
monitor_data: 监控数据
Returns:
格式化的响应文本
"""
items = monitor_data.get("items", [])
keywords = monitor_data.get("keywords", [])
if not items:
return f"❌ 暂无关于「{', '.join(keywords)}」的舆情数据"
response = f"🔍 **舆情监控 - {', '.join(keywords)}**\n"
response += f"共 {monitor_data['total_items']} 条相关内容\n"
response += "-" * 40 + "\n\n"
for i, item in enumerate(items, 1):
title = item.get("title", "无标题")
hot = item.get("hot", item.get("score", ""))
platform = item.get("source_platform", "")
response += f"{i}. {title}\n"
if hot:
response += f" 🔥 热度: {hot}"
if platform:
response += f" | 📱 {platform}"
response += "\n\n"
return response
def parse_keywords_from_input(self, user_input: str) -> List[str]:
"""
从用户输入解析关键词
Args:
user_input: 用户输入
Returns:
关键词列表
"""
user_input = user_input.strip()
# 常用监控关键词
common_keywords = [
"AI", "人工智能", "ChatGPT", "大模型",
"特斯拉", "比亚迪", "新能源汽车",
"华为", "iPhone", "小米",
"直播", "电商", "网红",
"房价", "股票", "基金",
"高考", "考研", "留学"
]
matched_keywords = []
for kw in common_keywords:
if kw.lower() in user_input.lower():
matched_keywords.append(kw)
# 如果没有匹配到常用关键词,尝试提取用户输入的词
if not matched_keywords:
# 按逗号、空格等分隔
import re
words = re.split(r'[,,\s]+', user_input)
matched_keywords = [w for w in words if w.strip()]
return matched_keywords
async def process_user_request(self, user_input: str) -> Dict[str, Any]:
"""
处理用户请求
Args:
user_input: 用户输入
Returns:
处理结果
"""
# 解析关键词
keywords = self.parse_keywords_from_input(user_input)
if not keywords:
# 返回引导信息
return {
"action": "ask_keywords",
"message": "🔍 **舆情监控**\n\n"
"请输入您想监控的关键词,例如:\n"
"• AI、人工智能、ChatGPT\n"
"• 特斯拉、比亚迪、新能源汽车\n"
"• 华为、iPhone、小米\n"
"• 直播、电商、网红\n"
"• 房价、股票、基金\n\n"
"💡 您可以直接输入任意关键词进行监控"
}
# 获取监控数据
monitor_data = await self.monitor_keywords(keywords)
# 格式化响应
response_text = self.format_monitoring_response(monitor_data)
return {
"action": "show_monitoring",
"data": monitor_data,
"message": response_text
}
# 便捷函数
async def create_sentiment_monitor(api_client=None, formatter=None) -> SentimentMonitor:
"""创建舆情监控实例"""
return SentimentMonitor(api_client=api_client, formatter=formatter)
if __name__ == "__main__":
# 测试代码
async def test():
monitor = await create_sentiment_monitor()
print("测试舆情监控...")
result = await monitor.process_user_request("监控AI和特斯拉")
print(result["message"])
asyncio.run(test())
Related skills
FAQ
How many platforms does daily-hot-news support?
daily-hot-news queries hot-list data from 54 Chinese platforms including Weibo, Zhihu, Bilibili, Douyin, and Toutiao. The skill wraps a local DailyHotApi service and supports categorized browsing, history storage, and scheduled Feishu delivery.
What dependencies does daily-hot-news require?
daily-hot-news requires python3 and installs requests and aiohttp via pip inside the skill directory. OpenClaw metadata declares the python-deps install step, and the skill routes user requests through the local DailyHotApi service for hot-rank retrieval.
Is Daily Hot News safe to install?
skills.sh reports 1 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.