
Bili Ai Feed
- 181 installs
- 316 repo stars
- Updated August 4, 2026
- redfox-data/redfox-community
Helps with ai & agent building tasks during AI-assisted development.
About
bili-ai-feed is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- bili-ai-feed
- AI & Agent Building
- AI-coding skill
Bili Ai Feed by the numbers
- 181 all-time installs (skills.sh)
- +14 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #3,045 of 16,546 AI & Agent Building 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 bili-ai-feedAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 181 |
|---|---|
| repo stars | ★ 316 |
| Last updated | August 4, 2026 |
| Repository | redfox-data/redfox-community ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
B站AI信息源
📝 简介
每日自动扫描B站AI创作内容,按点赞量筛选爆款视频,智能聚类后生成HTML日报。同步基于热门话题执行AI情报调查,使用多搜索引擎交叉验证,输出结构化调查报告。
重要:数据每日15:00更新前一天数据,目标日期无数据时禁止自动调用接口,必须先告知用户并等待确认。
✨ 功能特性
| 功能模块 | 能力描述 | 核心价值 |
|---|---|---|
| 爆款发现 | 从B站AI相关视频中按点赞量筛选热门内容 | 精准定位高热度AI视频 |
| 智能查询 | 默认只查“AI”,数据不足时自动扩展关键词批量查询 | 节省接口额度,高效获取数据 |
| 自定义查询 | 用户可指定任意关键词组合定向查询与分析 | 灵活覆盖任意AI细分方向 |
| 智能聚类 | 自动识别话题方向(AI教程、大模型、AI绘画等) | 每天分类由内容动态决定 |
| AI情报调查 | 基于热门话题执行多引擎搜索+交叉验证 | 深度挖掘话题背后的情报 |
| 可视化日报 | 深色主题HTML,封面图+互动数据+视频直链 | 直观展示每日AI热点 |
| 一键订阅 | --subscribe 开启每日自动产出 | 日报自动攒在本地文件夹 |
🔑 鉴权
数据查询接口通过请求头 X-API-KEY 鉴权,Key 从环境变量 REDFOX_API_KEY 获取。
API Key 获取:前往 红狐Hub 官网 注册,登录后在个人中心获取,格式为 ak_xxxxxxxx。新注册用户获赠免费积分。
配置方式:
- macOS/Linux:将
export REDFOX_API_KEY=<值>追加到~/.zshrc或~/.bashrc,然后source使其生效 - Windows:
[Environment]::SetEnvironmentVariable("REDFOX_API_KEY", "<值>", "User")(需重启终端) - 配置后验证:
echo $REDFOX_API_KEY(macOS/Linux)或echo %REDFOX_API_KEY%(Windows)
查询接口调用时通过 source 字段同步记录,无需额外请求保存接口。
🔄 工作流程
第零步:日期有效性预检(必须执行,先于任何接口调用)
⛔ 核心规则:未经用户确认,禁止调用任何数据接口,禁止自动执行 `--latest`
>
绝对不能在用户未确认的情况下自动执行脚本获取数据。
数据更新规则:每日15:00更新前一天的数据
- 15:00前:最新可用日期 = T-2(前天)
- 15:00后:最新可用日期 = T-1(昨天)
执行流程(每次查询前强制执行):
1. 获取当前系统日期 T 和当前时间,按15:00规则计算最新可用日期 2. 判断用户请求的目标日期是否在无数据区间(即 > 最新可用日期) 3. 若目标日期有数据(≤ 最新可用日期):直接执行第一步,无需额外确认 4. 若目标日期无数据(> 最新可用日期),向用户输出以下提示:
**⚠️{查询日期}数据尚未更新**
数据更新规则:每日15:00更新前一天的数据
当前可查询的最新日期:{最新可查询到数据的日期}
是否需要查询{最新可查询到数据的日期}的数据?5. 等待用户明确确认后,才能执行第一步(带 --latest 参数) 6. 若用户拒绝,则不执行任何接口调用
示例对话:
用户:查询今天的AI日报
Agent:⚠️2026-06-16数据尚未更新
数据更新规则:每日15:00更新前一天的数据
当前可查询的最新日期:2026-06-14
是否需要查询2026-06-14的数据?
用户:好的
Agent:(执行 python3 daily_report.py --latest)第一步:生成爆款日报
# 生成最新一期日报(用户确认后,自动跳过无数据日期,不扣积分)
python3 "$SKILL_PATH/assets/daily_report.py" --latest
# 生成指定日期日报(历史日期已有数据,无需确认)
python3 "$SKILL_PATH/assets/daily_report.py" --date 2026-06-10
# 自定义关键词查询(用户指定方向,数据不足时按顺序逐个扩展)
python3 "$SKILL_PATH/assets/daily_report.py" --keywords "AI教程,AI绘画,ChatGPT" --latest
# 订阅 / 取消订阅
python3 "$SKILL_PATH/assets/daily_report.py" --subscribe
python3 "$SKILL_PATH/assets/daily_report.py" --unsubscribe查询策略:默认使用主关键词 AI 查询(pageSize=200),数据不足时自动追加扩展词(人工智能→大模型→GPT→Agent→AI绘画→AI教程),所有关键词通过 batch 接口一次性批量查询,无需逐个调用。用户自定义关键词时仅使用用户提供的列表,同样批量查询。日期智能判断:脚本内置 DATA_UPDATE_HOUR = 15 常量(每日15:00更新前一天数据),调用接口前自动检测目标日期是否在无数据区间。作为双保险,Agent 在第零步已提前拦截,避免脚本层的交互提示被忽略。第二步:执行AI情报调查
日报生成后,必须基于聚类结果中的TOP话题自动执行AI情报调查:
1. 读取聚类结果,选取TOP 3热门话题 2. 为每个话题自动匹配调查模式(竞品/舆情/人物/验证) 3. 按三轮搜索策略执行调查(广域扫描→深度挖掘→交叉验证) 4. 输出结构化调查报告(参照 investigation-templates.md)
生成的HTML日报保存在 ~/Downloads/QoderReports/,自动浏览器打开。终端同步输出分类视频表格 + AI情报调查报告。
🔍 调查模式
| 模式 | 调查目标 | 推荐引擎 | 适用话题 |
|---|---|---|---|
| 竞品情报调查 | 分析竞品产品、市场策略、用户口碑 | Baidu + Google + WeChat + DuckDuckGo | AI产品、AI工具类话题 |
| 舆情事件调查 | 热点事件追踪、舆论走向分析、危机监测 | Baidu + Toutiao + Google + WeChat | AI热点事件、争议话题 |
| 人物背景调查 | 商务合作前的背景调查、行业人物了解 | Baidu + Google + DuckDuckGo | 核心达人、关键人物 |
| 信息交叉验证 | 验证信息真实性、对比不同来源说法 | Google + DuckDuckGo + Brave + Startpage | 待验证的AI技术/数据 |
🌐 引擎选择策略
按调查目标选引擎
| 调查目标 | 首选引擎 | 备选引擎 |
|---|---|---|
| 中文舆情 | Baidu + WeChat + Toutiao | Sogou, 360 |
| 国际视野 | Google + Brave + Yahoo | Bing INT, Ecosia |
| 隐私敏感 | DuckDuckGo + Startpage | Brave, Qwant |
| 学术验证 | Google Scholar + WolframAlpha | |
| 技术调查 | DuckDuckGo(!gh !so) + Google | Brave |
| 交叉验证 | 多引擎同时搜索 | 全引擎 |
详细引擎能力与高级搜索策略详见 engine-strategy.md。
⚠️ 可信度标注规范
| 标识 | 含义 | 判定标准 |
|---|---|---|
| ✅ 已确认 | 信息可靠 | 2+个独立来源一致 |
| ⚠️ 待确认 | 有争议 | 来源说法矛盾 |
| ❌ 已否定 | 信息不实 | 权威信源反驳 |
| 🔍 单一来源 | 仅1个来源 | 需进一步验证 |
信息源分级:
| 级别 | 类型 | 示例 |
|---|---|---|
| A级 | 官方/政府/权威媒体 | gov.cn, reuters.com, xinhua.net |
| B级 | 行业媒体/专业平台 | 36kr, techcrunch.com |
| C级 | 社交媒体/自媒体 | weibo, zhihu, reddit |
| D级 | 匿名/未验证来源 | 贴吧, 4chan |
📊 输出格式(强制执行)
⛔ 严格执行规则:
- 以下模板是唯一合法输出格式,禁止任何自由发挥、省略、简化或重新组织
- 禁止输出模板中未定义的额外内容(如“我来帮你…”“以下是…”等口语化文字)
- 禁止合并、跳过任何板块,即使某板块数据为“暂无”也必须保留该板块标题
- 日报生成后,对话回复只能包含以下内容,不得包含其他任何文字
每次运行日报后,对话输出必须严格按以下模板原样输出(仅替换 {...} 占位符):
## B站AI信息源 · {日期} 日报
**扫描 {N} 条热门视频,聚类 {M} 个分类**
---
### 分类概览
| 分类 | 数量 | 占比 | 亮点 |
|------|------|------|------|
| #{分类名} | {N}条 | {X}% | 头部视频亮点描述 |
| ... | ... | ... | ... |
---
### AI情报调查报告
**一、新兴起量信号**
- 🔥 **#{话题}** — 仅{N}条但均互动{X}+,描述
(若无新兴话题,输出:暂无新兴起量信号)
**二、核心达人**
| 达人 | 作品数 | 总赞 | 亮点 |
|------|--------|------|------|
| @{作者} | {N}条 | {X}w | 描述 |
| ... | ... | ... | ... |
(若无达人数据,输出:暂无核心达人数据)
**三、{话题1} 调查报告**
**调查模式**:{竞品情报调查/舆情事件调查/人物背景调查/信息交叉验证}
**引擎组合**:{引擎1} + {引擎2} + {引擎3}
| 维度 | 发现 | 来源 | 可信度 |
|------|------|------|--------|
| {维度1} | {内容} | {来源} | {A/B/C/D级} |
| ... | ... | ... | ... |
**关键结论**:{已确认/待确认/已否定的信息汇总}
**四、{话题2} 调查报告**
(同上格式)
**五、{话题3} 调查报告**
(同上格式)
**六、跨平台对比建议**
- **{话题}** — 建议同步关注抖音、小红书、公众号同话题热度,用 引擎组合 追踪国内全平台动态
(若无建议,输出:暂无跨平台对比建议)
---
**日报地址**:{HTML文件绝对路径}以上格式为强制规范,所有字段不可省略,板块标题(一、二、三、四、五、六)必须保留。若某模块无数据则在该板块内标注“暂无”,不得删除板块本身。
参数说明
| 参数 | 说明 | 默认值 |
|---|---|---|
--keywords | 自定义搜索关键词,逗号分隔。默认只查“AI”,数据不足时自动扩展词;所有关键词通过 batch 接口批量查询 | AI |
--count | 扫描视频数量,满足即停 | 200 |
--date | 指定日期 YYYY-MM-DD(若无数据会提示并询问切换) | 今天 |
--start-time | 自定义开始时间 YYYY-MM-DD HH:MM:SS(覆盖 --date 推算) | — |
--end-time | 自定义结束时间 YYYY-MM-DD HH:MM:SS(覆盖 --date 推算) | — |
--latest | 自动使用最新有数据的日期,跳过无数据区间,不扣积分 | — |
--output-dir | 输出目录 | ~/Downloads/QoderReports |
--api-key | 指定 API Key | — |
--subscribe | 开启每日订阅 | — |
--unsubscribe | 关闭每日订阅 | — |
--no-open | ~~已移除~~ 生成后始终自动预览 | — |
💬 自定义关键词查询场景
除默认 AI 日报外,用户可指定任意关键词组合进行定向查询与分析:
# 查询 AI 绘画方向热门内容
python3 "$SKILL_PATH/assets/daily_report.py" --keywords "Stable Diffusion,ComfyUI,Midjourney,AI绘画"
# 查询 AI 工具/产品方向
python3 "$SKILL_PATH/assets/daily_report.py" --keywords "AI工具,ChatGPT,Claude,Coze"
# 查询特定技术方向
python3 "$SKILL_PATH/assets/daily_report.py" --keywords "RAG,向量数据库,微调,LoRA"自定义查询逻辑:
- 用户提供的所有关键词通过 batch 接口一次性批量查询,无需逐个调用
- 查询结果自动去重,聚类、情报调查报告均基于查询结果生成,与用户关注方向强关联
📚 参考文档
- engine-strategy.md — 引擎选择策略、独有能力与高级搜索方法
- investigation-modes.md — 四种调查模式的搜索策略编排与输出模板
- investigation-templates.md — 调查报告完整模板集
#!/usr/bin/env python3
"""
B站AI信息源 — 每日热门内容聚类
====================================
每天扫描B站平台 AI 相关热门视频,自动聚类后生成 HTML 日报。
Usage:
python3 daily_report.py
python3 daily_report.py --keywords "AI教程,AI绘画,ChatGPT"
python3 daily_report.py --subscribe
"""
import argparse
import json
import os
import re
import subprocess
import sys
import time
from collections import Counter, defaultdict
from datetime import datetime, timedelta
from pathlib import Path
try:
import requests
HAS_REQUESTS = True
except ImportError:
HAS_REQUESTS = False
# ─── 配置 ─────────────────────────────────────────────────────────────────────────
API_URL = "https://redfox.hk/story/api/parseWork//queryBiliAiMsgs/batch"
CONFIG_DIR = Path.home() / ".qoder" / "apis"
CONFIG_FILE = CONFIG_DIR / "redfox.json"
ENV_KEY = "REDFOX_API_KEY"
SOURCE = "B站AI信息源-GitHub"
DEFAULT_KEYWORDS = ["AI"]
EXPAND_KEYWORDS = ["人工智能", "大模型", "GPT", "Agent", "AI绘画", "AI教程"]
DEFAULT_OUTPUT_DIR = Path.home() / "Downloads" / "QoderReports"
PAGE_SIZE = 200
# 数据更新规则:每日15:00更新前一天的数据
# 15:00前:最新可用日期 = T-2;15:00后:最新可用日期 = T-1
DATA_UPDATE_HOUR = 15 # 数据更新时间(小时)
PLIST_LABEL = "com.qoder.bili-ai-feed"
PLIST_DIR = Path.home() / "Library" / "LaunchAgents"
def get_latest_available_date():
"""获取最新有数据的日期
规则:每日15:00更新前一天的数据
- 15:00前:最新可用 = T-2
- 15:00后:最新可用 = T-1
"""
now = datetime.now()
if now.hour >= DATA_UPDATE_HOUR:
# 15:00后,前一天的数据已更新
return (now - timedelta(days=1)).strftime("%Y-%m-%d")
else:
# 15:00前,前一天的数据尚未更新
return (now - timedelta(days=2)).strftime("%Y-%m-%d")
def is_date_likely_empty(date_str):
"""判断指定日期是否可能没有数据"""
try:
target = datetime.strptime(date_str, "%Y-%m-%d")
except ValueError:
return False
latest = get_latest_available_date()
latest_dt = datetime.strptime(latest, "%Y-%m-%d")
latest_dt = latest_dt.replace(hour=23, minute=59, second=59)
return target > latest_dt
def is_timerange_likely_empty(start_time, end_time):
"""判断自定义时间段是否完全落在无数据区间内"""
try:
end_dt = datetime.strptime(end_time, "%Y-%m-%d %H:%M:%S")
except ValueError:
return False
latest = get_latest_available_date()
latest_dt = datetime.strptime(latest, "%Y-%m-%d")
latest_dt = latest_dt.replace(hour=23, minute=59, second=59)
# 整个时间段都在无数据区间内
return end_dt > latest_dt and (datetime.now() - end_dt).total_seconds() < 0
# ─── 终端颜色 ──────────────────────────────────────────────────────────────────────
GREEN = "\033[92m"
YELLOW = "\033[93m"
RED = "\033[91m"
CYAN = "\033[96m"
BOLD = "\033[1m"
RESET = "\033[0m"
def info(msg):
print(f"{GREEN}[✓]{RESET} {msg}")
def warn(msg):
print(f"{YELLOW}[!]{RESET} {msg}")
def error(msg):
print(f"{RED}[✗]{RESET} {msg}")
def step(msg):
print(f"{CYAN}[→]{RESET} {msg}")
# ─── API Key 管理 ──────────────────────────────────────────────────────────────────
def _read_key_from_shell_configs():
"""从常见 shell 配置文件中读取 REDFOX_API_KEY 的值。"""
shell_configs = [
Path.home() / ".zshrc",
Path.home() / ".bashrc",
Path.home() / ".bash_profile",
Path.home() / ".profile",
]
pattern = re.compile(
r'^\s*export\s+' + re.escape(ENV_KEY) + r'\s*=\s*["\']?([^"\' \n]+)["\']?',
re.MULTILINE,
)
for cfg in shell_configs:
if not cfg.exists():
continue
try:
content = cfg.read_text(encoding="utf-8", errors="ignore")
m = pattern.search(content)
if m:
return m.group(1).strip()
except OSError:
pass
return None
def get_api_key(cli_key=None):
"""Get API key: CLI arg > env var > shell config > config file > prompt user."""
if cli_key:
return cli_key
# 1. 环境变量
env_key = os.environ.get(ENV_KEY)
if env_key:
return env_key
# 2. shell 配置文件(~/.zshrc / ~/.bashrc 等)
shell_key = _read_key_from_shell_configs()
if shell_key:
return shell_key
# 3. Qoder 配置文件
if CONFIG_FILE.exists():
try:
data = json.loads(CONFIG_FILE.read_text())
key = data.get("api_key")
if key:
return key
except (json.JSONDecodeError, OSError):
pass
# 4. 未找到 Key,提示用户配置并退出
error(f"未检测到 {ENV_KEY},请先配置 API Key:")
if sys.platform == "win32":
print(f" Windows PowerShell: [Environment]::SetEnvironmentVariable('{ENV_KEY}', 'ak_你的密钥', 'User')")
else:
print(f" macOS/Linux (zsh): echo 'export {ENV_KEY}=ak_你的密钥' >> ~/.zshrc && source ~/.zshrc")
print(f" macOS/Linux (bash): echo 'export {ENV_KEY}=ak_你的密钥' >> ~/.bashrc && source ~/.bashrc")
print(f" 免费注册获取 Key: https://redfox.hk/login")
print()
sys.exit(1)
# ─── 数据获取 ──────────────────────────────────────────────────────────────────────
def fetch_batch(session, keywords, keyword, start_time=None, end_time=None):
"""批量获取多关键词的视频数据(新 batch 接口)
Args:
keywords: 关键词列表,如 ["AI", "seedance", "智能"]
keyword: 主关键词(用于接口 keyword 字段)
start_time / end_time: 时间范围
"""
payload = {
"keywords": keywords,
"keyword": keyword,
"pageNum": 1,
"pageSize": PAGE_SIZE,
"source": SOURCE,
}
if start_time:
payload["startTime"] = start_time
if end_time:
payload["endTime"] = end_time
try:
resp = session.post(API_URL, json=payload, timeout=30)
result = resp.json()
except Exception as e:
warn(f"请求失败 (keywords={keywords}): {e}")
return []
code = result.get("code")
if code == 3108:
warn("限频,等待 5s...")
time.sleep(5)
try:
resp = session.post(API_URL, json=payload, timeout=30)
result = resp.json()
code = result.get("code")
except Exception:
return []
if code not in (200, 2000):
if code in (3106, 3107):
error(f"API Key 错误 (code {code}): {result.get('msg', '')}")
return []
data = result.get("data", {})
return data.get("list", [])
def fetch_articles(session, keywords, target_count, start_time=None, end_time=None):
"""多关键词批量抓取,利用 batch 接口一次性传入所有关键词,去重后返回视频列表"""
articles = []
seen_ids = set()
# 新 batch 接口支持一次传入多个关键词
primary_keyword = keywords[0] if keywords else "AI"
batch = fetch_batch(
session,
keywords=keywords,
keyword=primary_keyword,
start_time=start_time,
end_time=end_time,
)
if not batch:
warn(f"关键词 {keywords} 暂无内容(当前仅搜索 AI 相关B站视频,更多内容请访问 redfox.hk)")
else:
for article in batch:
pid = article.get("photoId", "")
if pid and pid not in seen_ids:
seen_ids.add(pid)
# B站API不返回url,用photoId(BV号)拼接视频链接
if not article.get("url") and pid:
article["url"] = f"https://www.bilibili.com/video/{pid}"
articles.append(article)
print(f" {CYAN}[→]{RESET} 批量扫描关键词: {keywords} 共{len(articles)}条")
return articles
# ─── 自动聚类 ──────────────────────────────────────────────────────────────────────
STOP_WORDS = set("的了是在和与及或但对于从到被将把让给用有这那个也都还又不没"
"就才能会要可以怎么什么为什么怎样如何哪些多少一个一些这些那些"
"已经正在可能应该必须需要通过进行使用利用根据关于对于由于因为所以"
"虽然但是然而因此所以如果那么只要只有无论不管即使不仅而且")
# 过于宽泛的标签,聚类时跳过
GENERIC_TAGS = {"#AI", "#人工智能", "#ai", "AI", "人工智能", "#科技", "#技术",
"#人工智能应用", "#智能", "科技", "技术"}
def extract_keywords(title):
"""从标题中提取中文关键词片段"""
if not title:
return []
# 移除标点和特殊字符
cleaned = re.sub(r'[^\u4e00-\u9fff\w]', ' ', title)
# 提取 2-4 字中文片段
segments = re.findall(r'[\u4e00-\u9fff]{2,4}', cleaned)
# 过滤停用词
keywords = [s for s in segments if not all(c in STOP_WORDS for c in s)]
return keywords[:5]
def get_article_tags(article):
"""提取视频的所有有效标签(去除泛标签),优先 type 再 topic"""
tags = []
# type 字段通常更细致(如 #AI热点、#AI教程、#AI大模型)
atype = (article.get("type") or "").strip()
if atype:
for t in re.split(r'[,,]+', atype):
t = t.strip()
if t and t not in GENERIC_TAGS:
tags.append(t)
# topic 字段作为补充(跳过泛标签)
topic = (article.get("topic") or "").strip()
if topic:
for t in re.split(r'[,,\s]+', topic):
t = t.strip()
if t and t not in GENERIC_TAGS and t not in tags:
tags.append(t)
return tags
def cluster_articles(articles):
"""基于 type + topic 标签自动聚类,确保分类细致且至少 5 个"""
# 第一步:为每个视频提取标签,按首个有效标签分组
topic_groups = defaultdict(list)
for article in articles:
tags = get_article_tags(article)
if tags:
# 使用第一个非泛标签作为主分类
topic_groups[tags[0]].append(article)
else:
topic_groups["其他"].append(article)
# 第二步:如果大组过大(>20%文章),尝试拆分
total = len(articles)
split_threshold = max(total * 0.2, 25)
groups_to_split = {}
for topic, arts in list(topic_groups.items()):
if len(arts) > split_threshold and topic != "其他":
groups_to_split[topic] = arts
for topic, arts in groups_to_split.items():
del topic_groups[topic]
# 用视频的第二标签进行二次拆分
for article in arts:
tags = get_article_tags(article)
if len(tags) >= 2:
topic_groups[tags[1]].append(article)
else:
topic_groups[topic].append(article)
# 第三步:合并小组(< 3 篇)
final_groups = {}
small_articles = []
for topic, arts in topic_groups.items():
if len(arts) >= 3:
final_groups[topic] = arts
else:
small_articles.extend(arts)
# 小组视频尝试用标签匹配到已有大组
still_orphan = []
for article in small_articles:
tags = get_article_tags(article)
placed = False
for tag in tags:
if tag in final_groups:
final_groups[tag].append(article)
placed = True
break
if not placed:
still_orphan.append(article)
if still_orphan:
if "其他" in final_groups:
final_groups["其他"].extend(still_orphan)
else:
final_groups["其他"] = still_orphan
# 第四步:对过大的组用标题关键词进一步拆分
MAX_GROUP_SIZE = max(total * 0.3, 40)
for _ in range(3): # 最多拆 3 轮
oversized = [(t, a) for t, a in final_groups.items() if len(a) > MAX_GROUP_SIZE]
if not oversized:
break
for topic, arts in oversized:
# 用标题中的高频关键词拆分
kw_counter = Counter()
article_kw_map = {}
for article in arts:
title = article.get("title", "")
kws = extract_keywords(title)
article_kw_map[id(article)] = kws
for kw in kws:
kw_counter[kw] += 1
# 找出频次够高的关键词作为子分类
common_kws = [kw for kw, cnt in kw_counter.most_common(5)
if cnt >= 5 and kw not in topic
and f"#{kw}" not in GENERIC_TAGS
and kw not in ("人工智能", "智能", "模型", "技术", "应用")]
if not common_kws:
continue
# 用第一个高频词拆出子组
split_kw = common_kws[0]
new_group = []
remaining = []
for article in arts:
kws = article_kw_map.get(id(article), [])
if split_kw in kws:
new_group.append(article)
else:
remaining.append(article)
if len(new_group) >= 5:
final_groups[f"#{split_kw}"] = new_group
final_groups[topic] = remaining
# 第五步:确保至少 5 个分类(如果不够,对最大组继续拆分)
while len(final_groups) < 5 and final_groups:
largest_topic = max(final_groups, key=lambda k: len(final_groups[k]))
largest_arts = final_groups[largest_topic]
if len(largest_arts) < 6:
break # 最大组也太小了,无法再拆
# 从最大组中按第二标签拆出子组
sub_groups = defaultdict(list)
remain = []
for article in largest_arts:
tags = get_article_tags(article)
second_tag = None
for t in tags:
if t != largest_topic:
second_tag = t
break
if second_tag:
sub_groups[second_tag].append(article)
else:
remain.append(article)
# 找出最大的子组拆出来
if sub_groups:
best_sub = max(sub_groups, key=lambda k: len(sub_groups[k]))
if len(sub_groups[best_sub]) >= 3:
final_groups[best_sub] = sub_groups[best_sub]
# 更新原组
new_arts = remain
for k, v in sub_groups.items():
if k != best_sub:
new_arts.extend(v)
final_groups[largest_topic] = new_arts
continue
break # 无法继续拆分
# 第六步:构建输出,按条数降序
clusters = []
for category, arts in sorted(final_groups.items(), key=lambda x: -len(x[1])):
# 按点赞量排序取 top 5
sorted_arts = sorted(arts, key=lambda a: (a.get("likeCount") or 0), reverse=True)
clusters.append({
"category": category,
"count": len(arts),
"articles": sorted_arts[:5],
})
return clusters
# ─── AI 情报调查分析 ────────────────────────────────────────────────────────────────
# 情报调查引擎配置(来自智能情报调查员 skill)
INVESTIGATION_ENGINES = {
"Baidu": {"url": "https://www.baidu.com/s?wd={keyword}", "region": "cn", "strength": "中文生态覆盖最广"},
"WeChat": {"url": "https://wx.sogou.com/weixin?type=2&query={keyword}", "region": "cn", "strength": "微信公众号文章"},
"Toutiao": {"url": "https://so.toutiao.com/search?keyword={keyword}", "region": "cn", "strength": "自媒体/热点追踪"},
"Google": {"url": "https://www.google.com/search?q={keyword}", "region": "global", "strength": "全球索引最全+高级操作符"},
"DuckDuckGo": {"url": "https://duckduckgo.com/html/?q={keyword}", "region": "global", "strength": "无追踪+Bangs直达"},
"Brave": {"url": "https://search.brave.com/search?q={keyword}", "region": "global", "strength": "独立索引+无偏见"},
"Sogou": {"url": "https://sogou.com/web?query={keyword}", "region": "cn", "strength": "微信+知乎内容"},
"Bing INT": {"url": "https://cn.bing.com/search?q={keyword}&ensearch=1", "region": "cn", "strength": "中文界面+国际结果"},
}
# 按调查场景推荐引擎组合
SCENARIO_ENGINES = {
"产品竞品分析": ["Baidu", "Google", "WeChat", "DuckDuckGo"],
"热点事件追踪": ["Baidu", "Toutiao", "Google", "WeChat"],
"人物背景验证": ["Baidu", "Google", "DuckDuckGo"],
"用户口碑收集": ["WeChat", "Toutiao", "DuckDuckGo", "Brave"],
"技术趋势调查": ["DuckDuckGo", "Google", "Brave"],
"市场数据验证": ["Google", "Baidu", "Bing INT"],
}
# 信源可信度分级
CREDIBILITY_LEVELS = {
"A": "官方/政府/权威媒体",
"B": "行业媒体/专业平台",
"C": "社交媒体/自媒体",
"D": "匿名/未验证来源",
}
# 按调查模式匹配
TOPIC_MODE_MAP = {
"大模型": ("竞品情报调查", "产品竞品分析"),
"GPT": ("竞品情报调查", "产品竞品分析"),
"ChatGPT": ("竞品情报调查", "产品竞品分析"),
"Agent": ("竞品情报调查", "技术趋势调查"),
"绘画": ("竞品情报调查", "用户口碑收集"),
"创作": ("舆情事件调查", "用户口碑收集"),
"动画": ("竞品情报调查", "用户口碑收集"),
"教程": ("技术趋势调查", "技术趋势调查"),
"教学": ("技术趋势调查", "技术趋势调查"),
"Prompt": ("技术趋势调查", "产品竞品分析"),
}
def _match_mode(topic_name):
"""根据话题名匹配调查模式"""
for kw, (mode, scenario) in TOPIC_MODE_MAP.items():
if kw in topic_name:
return mode, scenario
return "舆情事件调查", "热点事件追踪"
def _derive_findings(cluster, articles, scenario):
"""从视频数据中提取调查发现"""
findings = []
arts = cluster["articles"]
# 头部内容发现
if arts:
top = arts[0]
title = top.get("title", "无标题")[:40]
likes = format_number(top.get("likeCount") or 0)
comments = format_number(top.get("commentCount") or 0)
findings.append({
"dimension": "头部内容",
"discovery": f"{title} — {likes}赞 {comments}评",
"source": "B站视频数据",
"credibility": "B",
})
# 用户关注方向
title_kw = Counter()
for a in arts:
title = a.get("title", "")
for seg in re.findall(r'[\u4e00-\u9fff]{2,4}', title):
if seg not in STOP_WORDS and len(seg) >= 2:
title_kw[seg] += 1
if title_kw:
top_kws = "、".join(kw for kw, _ in title_kw.most_common(3))
findings.append({
"dimension": "用户关注",
"discovery": f"高频关键词:{top_kws}",
"source": "标题关键词分析",
"credibility": "C",
})
# 互动特征
total_likes = sum(a.get("likeCount") or 0 for a in arts)
total_comments = sum(a.get("commentCount") or 0 for a in arts)
if total_likes > 0:
comment_ratio = f"{total_comments / total_likes * 100:.1f}%" if total_likes else "0%"
findings.append({
"dimension": "互动特征",
"discovery": f"总赞{format_number(total_likes)},评论率{comment_ratio},"
+ ("讨论活跃" if total_comments / max(total_likes, 1) > 0.15 else "以点赞为主"),
"source": "互动数据分析",
"credibility": "B",
})
# 核心作者
author_counter = Counter(a.get("userName", "") for a in arts if a.get("userName"))
if author_counter:
top_author = author_counter.most_common(1)[0]
findings.append({
"dimension": "核心作者",
"discovery": f"@{top_author[0]} 贡献{top_author[1]}条作品",
"source": "作者统计",
"credibility": "B",
})
return findings
def _derive_conclusions(cluster, findings):
"""从调查发现中推导结论"""
conclusions = []
topic_name = cluster["category"].lstrip("#")
arts = cluster["articles"]
# 基于互动数据确认
if arts:
top_likes = arts[0].get("likeCount") or 0
if top_likes > 50000:
conclusions.append(("confirmed", f"{topic_name}话题有强流量表现,头部内容互动量{format_number(top_likes)}+"))
# 基于发现提取待确认
for f in findings:
if f["dimension"] == "用户关注" and "高频关键词" in f["discovery"]:
conclusions.append(("pending", f"用户关注方向需跨平台验证:{f['discovery'].replace('高频关键词:', '')}"))
# 基于低占比高互动
total_in_cluster = cluster["count"]
if total_in_cluster <= 15:
conclusions.append(("single", f"{topic_name}话题样本量较少({total_in_cluster}条),趋势待观察"))
if not conclusions:
conclusions.append(("confirmed", f"{topic_name}话题内容稳定,无异常信号"))
return conclusions
def generate_intelligence_briefing(clusters, articles):
"""基于当日聚类结果生成AI情报调查报告"""
if not clusters:
return None
total = len(articles)
# 1. 提取热度TOP话题
top_topics = []
for cluster in clusters[:5]:
top_topics.append({
"topic": cluster["category"],
"count": cluster["count"],
"ratio": round(cluster["count"] / total * 100, 1) if total > 0 else 0,
"top_article": cluster["articles"][0] if cluster["articles"] else None,
})
# 2. 识别新兴起量话题(占比小但互动高)
emerging_topics = []
for cluster in clusters:
if cluster["count"] < total * 0.1 and cluster["articles"]:
avg_engagement = sum(
(a.get("likeCount") or 0) + (a.get("commentCount") or 0)
for a in cluster["articles"]
) / max(len(cluster["articles"]), 1)
if avg_engagement > 1000:
emerging_topics.append({
"topic": cluster["category"],
"count": cluster["count"],
"avg_engagement": int(avg_engagement),
})
# 3. 核心达人分析
author_counter = Counter()
author_articles = defaultdict(list)
for article in articles:
author = article.get("userName", "未知")
author_counter[author] += 1
author_articles[author].append(article)
top_authors = []
for author, count in author_counter.most_common(5):
arts = author_articles[author]
total_likes = sum(a.get("likeCount") or 0 for a in arts)
total_shares = sum(a.get("shareCount") or 0 for a in arts)
top_authors.append({
"name": author,
"article_count": count,
"total_likes": total_likes,
"total_shares": total_shares,
})
# 4. 为每个TOP话题生成调查报告
investigation_reports = []
for cluster in clusters[:3]:
topic_name = cluster["category"].lstrip("#")
mode, scenario = _match_mode(topic_name)
engines = SCENARIO_ENGINES.get(scenario, SCENARIO_ENGINES["热点事件追踪"])
findings = _derive_findings(cluster, articles, scenario)
conclusions = _derive_conclusions(cluster, findings)
investigation_reports.append({
"topic": cluster["category"],
"mode": mode,
"scenario": scenario,
"engines": engines,
"findings": findings,
"conclusions": conclusions,
})
# 5. 跨平台对比建议
cross_platform_tips = []
for topic_info in top_topics[:3]:
topic_name = topic_info["topic"].lstrip("#")
cross_platform_tips.append(
f"「{topic_name}」— 建议同步关注抖音、小红书、公众号同话题热度,"
f"用 Baidu+WeChat+Toutiao 三引擎追踪国内全平台动态"
)
briefing = {
"top_topics": top_topics,
"emerging_topics": emerging_topics,
"top_authors": top_authors,
"investigation_reports": investigation_reports,
"cross_platform_tips": cross_platform_tips,
}
return briefing
def print_intelligence_briefing(briefing):
"""在终端输出情报调查"""
if not briefing:
return
print(f"\n{BOLD}{'='*78}{RESET}")
print(f"{BOLD} AI情报调查 · 深度调查指引{RESET}")
print(f"{BOLD}{'='*78}{RESET}\n")
# 新兴起量话题
if briefing["emerging_topics"]:
print(f" {CYAN}{BOLD}【新兴起量信号】{RESET}")
for topic in briefing["emerging_topics"]:
print(f" 🔥 {topic['topic']} — 虽仅{topic['count']}条但均互动{topic['avg_engagement']}+,"
f"值得深挖")
print()
# 核心达人
if briefing["top_authors"]:
print(f" {CYAN}{BOLD}【核心达人】{RESET}")
for author in briefing["top_authors"]:
print(f" @{author['name']} — {author['article_count']}条作品, "
f"总赞{format_number(author['total_likes'])}, "
f"总分享{format_number(author['total_shares'])}")
print()
# 调查报告
print(f" {CYAN}{BOLD}【TOP话题调查报告】{RESET}")
for report in briefing["investigation_reports"]:
engines_str = " + ".join(report["engines"])
print(f" ▸ {report['topic']} — {report['mode']} | {engines_str}")
for f in report.get("findings", []):
print(f" [{f['credibility']}级] {f['dimension']}: {f['discovery']}")
for ctype, ctext in report.get("conclusions", []):
icons = {"confirmed": "✅", "pending": "⚠️", "denied": "❌", "single": "🔍"}
print(f" {icons.get(ctype, '·')} {ctext}")
print()
# 跨平台对比
if briefing["cross_platform_tips"]:
print(f" {CYAN}{BOLD}【跨平台对比建议】{RESET}")
for tip in briefing["cross_platform_tips"]:
print(f" • {tip}")
print()
# ─── HTML 报告生成 ──────────────────────────────────────────────────────────────────
def compute_stats(articles):
"""计算统计数据"""
total = len(articles)
if total == 0:
return {"total": 0, "avg_likes": 0, "top_author": "-", "total_likes": 0}
likes_list = [a.get("likeCount") or 0 for a in articles]
avg_likes = sum(likes_list) // total if total > 0 else 0
author_counter = Counter(a.get("userName", "未知") for a in articles)
top_author = author_counter.most_common(1)[0][0] if author_counter else "-"
total_likes = sum(a.get("likeCount") or 0 for a in articles)
return {
"total": total,
"avg_likes": avg_likes,
"top_author": top_author,
"total_likes": total_likes,
}
def format_number(n):
"""格式化数字: 1234 -> 1.2k"""
if n is None:
return "0"
if n >= 10000:
return f"{n/10000:.1f}w"
if n >= 1000:
return f"{n/1000:.1f}k"
return str(n)
def print_article_table(clusters):
"""在终端打印分类视频表格"""
print(f"\n{BOLD}{'='*78}{RESET}")
print(f"{BOLD} B站AI信息源 · 分类视频一览{RESET}")
print(f"{BOLD}{'='*78}{RESET}\n")
for i, cluster in enumerate(clusters, 1):
category = cluster["category"]
arts = cluster["articles"]
# 分类标题
print(f" {CYAN}{BOLD}【{category}】{RESET} "
f"共 {len(arts)} 条展示 / {cluster['count']} 条总计")
# 表头
header = (f" {'序号':<4}{'标题':<36}{'作者':<14}"
f"{'分享':>8}{'点赞':>8}{'评论':>8}")
print(f" {YELLOW}{'─'*76}{RESET}")
print(f" {YELLOW}{header}{RESET}")
print(f" {YELLOW}{'─'*76}{RESET}")
for j, article in enumerate(arts, 1):
title = article.get("title", "无标题")
author = article.get("userName", "-")
shares = format_number(article.get("shareCount"))
likes = format_number(article.get("likeCount"))
comments = format_number(article.get("commentCount"))
# 截断过长的标题和作者
display_title = title[:34] + ".." if len(title) > 36 else title
display_author = author[:12] + ".." if len(author) > 14 else author
print(f" {j:<4}{display_title:<36}{display_author:<14}"
f"{shares:>8}{likes:>8}{comments:>8}")
print() # 分类之间空行
def generate_category_cards(clusters):
"""生成分类卡片 HTML"""
cards_html = ""
for i, cluster in enumerate(clusters, 1):
articles_html = ""
for article in cluster["articles"]:
title = article.get("title", "无标题")
url = article.get("url") or "#"
author = article.get("userName", "")
cover = article.get("coverUrl") or ""
shares = format_number(article.get("shareCount"))
likes = format_number(article.get("likeCount"))
comments = format_number(article.get("commentCount"))
cover_html = ""
if cover:
cover_html = f'<img class="article-cover" src="{cover}" alt="" loading="lazy" referrerpolicy="no-referrer">'
articles_html += f'''
<div class="article-item">
{cover_html}
<div class="article-info">
<a href="{url}" target="_blank" class="article-title">{title}</a>
<div class="article-meta">
<span class="author">{author}</span>
<span class="metrics">
<span class="metric">🔗 {shares}</span>
<span class="metric">👍 {likes}</span>
<span class="metric">💬 {comments}</span>
</span>
</div>
</div>
</div>'''
cards_html += f'''
<div class="category-card reveal">
<div class="card-header">
<span class="card-number">{i:02d}</span>
<h3 class="card-category">{cluster["category"]}</h3>
<span class="card-count">{cluster["count"]} 条</span>
</div>
<div class="card-body">{articles_html}
</div>
</div>'''
return cards_html
def generate_intelligence_html(briefing):
"""生成情报调查板块 HTML"""
if not briefing:
return ""
# 热度TOP话题
topics_html = ""
for i, topic in enumerate(briefing["top_topics"], 1):
top_art = topic.get("top_article")
top_title = (top_art.get("title", "-")[:50] if top_art else "-")
top_likes = format_number(top_art.get("likeCount", 0)) if top_art else "-"
topics_html += f'''
<div class="intel-rank-item">
<span class="intel-rank-num">{i}</span>
<div class="intel-rank-info">
<span class="intel-rank-topic">{topic['topic']}</span>
<span class="intel-rank-detail">占比 {topic['ratio']}% · {topic['count']}条 · 头部: {top_title}</span>
</div>
<span class="intel-rank-metric">{top_likes} 点赞</span>
</div>'''
# 新兴起量话题
emerging_html = ""
for topic in briefing.get("emerging_topics", []):
emerging_html += f'''
<div class="intel-emerging-item">
<span class="intel-emerging-badge">起量信号</span>
<span class="intel-emerging-topic">{topic['topic']}</span>
<span class="intel-emerging-detail">{topic['count']}条 · 均互动{topic['avg_engagement']}+</span>
</div>'''
emerging_section = ""
if emerging_html:
emerging_section = f'''
<div class="intel-subsection">
<h4 class="intel-subtitle">新兴起量信号</h4>
<div class="intel-emerging-list">{emerging_html}
</div>
</div>'''
# 核心达人
authors_html = ""
for author in briefing.get("top_authors", []):
authors_html += f'''
<div class="intel-author-item">
<span class="intel-author-name">@{author['name']}</span>
<span class="intel-author-stats">{author['article_count']}条 · 总赞{format_number(author['total_likes'])} · 总分享{format_number(author['total_shares'])}</span>
</div>'''
authors_section = ""
if authors_html:
authors_section = f'''
<div class="intel-subsection">
<h4 class="intel-subtitle">核心达人</h4>
<div class="intel-author-list">{authors_html}
</div>
</div>'''
# 调查报告卡片
reports_html = ""
for report in briefing.get("investigation_reports", []):
# 发现表
findings_rows = ""
for f in report.get("findings", []):
cred = f["credibility"]
cred_class = f"intel-cred-{cred.lower()}"
findings_rows += f'''
<tr>
<td>{f['dimension']}</td>
<td>{f['discovery']}</td>
<td>{f['source']}</td>
<td><span class="intel-cred-badge {cred_class}">{cred}级</span></td>
</tr>'''
# 结论
conclusion_items = ""
for ctype, ctext in report.get("conclusions", []):
icons = {"confirmed": "✅", "pending": "⚠️", "denied": "❌", "single": "🔍"}
css_class = {"confirmed": "intel-conclusion-confirmed", "pending": "intel-conclusion-pending",
"denied": "intel-conclusion-denied", "single": "intel-conclusion-single"}
conclusion_items += f'''<li class="intel-conclusion-item {css_class.get(ctype, '')}">{icons.get(ctype, "·")} {ctext}</li>'''
engines_str = " + ".join(report["engines"])
scenario_tag = f'<div class="intel-report-scenario">📋 调查场景: {report.get("scenario", "")}</div>'
reports_html += f'''
<div class="intel-report-card reveal">
<div class="intel-report-head">
<span class="intel-report-topic">{report['topic']}</span>
<span class="intel-report-mode">{report['mode']}</span>
<span class="intel-report-engines">{engines_str}</span>
</div>
<div class="intel-report-body">
{scenario_tag}
<table class="intel-findings-table">
<thead><tr><th>维度</th><th>发现</th><th>来源</th><th>可信度</th></tr></thead>
<tbody>{findings_rows}
</tbody>
</table>
<div class="intel-conclusion-title">关键结论</div>
<ul class="intel-conclusion-list">{conclusion_items}
</ul>
</div>
</div>'''
# 跨平台对比
cross_html = ""
for tip in briefing.get("cross_platform_tips", []):
cross_html += f'<div class="intel-cross-tip">{tip}</div>'
cross_section = ""
if cross_html:
cross_section = f'''
<div class="intel-subsection">
<h4 class="intel-subtitle">跨平台对比建议</h4>
<div class="intel-cross-list">{cross_html}
</div>
</div>'''
# 可信度标注规范(紧凑版)
cred_ref_items = ""
for level, desc in CREDIBILITY_LEVELS.items():
cred_class = f"intel-cred-{level.lower()}"
cred_ref_items += f'''
<div class="intel-cred-ref-item">
<span class="intel-cred-badge {cred_class}">{level}级</span>
<span class="intel-cred-ref-label">{desc}</span>
</div>'''
html = f'''
<div class="intelligence-section reveal">
<div class="intel-header">
<h2 class="intel-title">AI情报调查报告</h2>
<span class="intel-subtitle-badge">基于智能情报调查员 · 多源交叉验证</span>
</div>
<div class="intel-body">
<div class="intel-subsection">
<h4 class="intel-subtitle">热度TOP话题</h4>
<div class="intel-rank-list">{topics_html}
</div>
</div>
{emerging_section}
{authors_section}
<div class="intel-subsection">
<h4 class="intel-subtitle">TOP话题调查报告</h4>
{reports_html}
</div>
{cross_section}
<div class="intel-subsection">
<h4 class="intel-subtitle">可信度标注规范</h4>
<div class="intel-cred-ref">{cred_ref_items}
</div>
</div>
</div>
</div>'''
return html
def generate_report(clusters, articles, date_str, api_key=None, briefing=None):
"""生成完整 HTML 报告"""
stats = compute_stats(articles)
topic_count = len(clusters)
# 尝试从模板文件读取
template_path = Path(__file__).parent / "report_template.html"
if template_path.exists():
template = template_path.read_text(encoding="utf-8")
else:
warn("模板文件未找到,使用内置模板")
template = get_fallback_template()
# 生成日期显示
try:
dt = datetime.strptime(date_str, "%Y-%m-%d")
weekdays = ["一", "二", "三", "四", "五", "六", "日"]
date_cn = f"{dt.year}年{dt.month}月{dt.day}日 星期{weekdays[dt.weekday()]}"
except ValueError:
date_cn = date_str
category_cards = generate_category_cards(clusters)
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# 生成情报调查 HTML
intelligence_html = generate_intelligence_html(briefing) if briefing else ""
html = template
html = html.replace("{{DATE}}", date_str)
html = html.replace("{{DATE_CN}}", date_cn)
html = html.replace("{{TOTAL_COUNT}}", str(stats["total"]))
html = html.replace("{{TOPIC_COUNT}}", str(topic_count))
html = html.replace("{{TOP_AUTHOR}}", stats["top_author"])
html = html.replace("{{AVG_LIKES}}", format_number(stats["avg_likes"]))
html = html.replace("{{TOTAL_LIKES}}", format_number(stats["total_likes"]))
html = html.replace("{{CATEGORY_CARDS}}", category_cards)
html = html.replace("{{INTELLIGENCE_SECTION}}", intelligence_html)
html = html.replace("{{TIMESTAMP}}", timestamp)
html = html.replace("{{API_KEY}}", api_key or "")
html = html.replace("{{SOURCE}}", SOURCE)
return html
def get_fallback_template():
"""内置最小 HTML 模板(当模板文件缺失时使用)"""
return '''<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>B站AI信息源 - {{DATE}}</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, sans-serif; background: #1a1a1a; color: #e8e4df; padding: 2rem; }
.header { text-align: center; padding: 2rem 0; }
.header h1 { font-size: 2rem; color: #FB7299; }
.header p { color: #9a9590; margin-top: 0.5rem; }
.stats { display: flex; justify-content: center; gap: 2rem; padding: 1rem; margin: 1rem 0; }
.stat-item { text-align: center; }
.stat-value { font-size: 1.5rem; font-weight: bold; color: #FB7299; }
.stat-label { font-size: 0.8rem; color: #9a9590; }
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); gap: 1.5rem; max-width: 1200px; margin: 2rem auto; }
.category-card { background: #2d2d2d; border-radius: 12px; padding: 1.5rem; }
.card-header { display: flex; align-items: center; gap: 0.8rem; margin-bottom: 1rem; padding-bottom: 0.8rem; border-bottom: 1px solid #3d3d3d; }
.card-number { font-size: 1.5rem; font-weight: bold; color: #FB7299; }
.card-category { flex: 1; font-size: 1.1rem; }
.card-count { color: #9a9590; font-size: 0.9rem; }
.article-item { padding: 0.6rem 0; border-bottom: 1px solid #3d3d3d; display: flex; gap: 0.8rem; }
.article-item:last-child { border-bottom: none; }
.article-cover { width: 60px; height: 60px; border-radius: 6px; object-fit: cover; flex-shrink: 0; }
.article-info { flex: 1; min-width: 0; }
.article-title { color: #e8e4df; text-decoration: none; font-size: 0.9rem; line-height: 1.4; display: block; }
.article-title:hover { color: #FB7299; }
.article-meta { display: flex; justify-content: space-between; margin-top: 0.3rem; font-size: 0.75rem; color: #9a9590; }
.metrics { display: flex; gap: 0.8rem; }
.footer { text-align: center; padding: 2rem; color: #666; font-size: 0.8rem; }
</style>
</head>
<body>
<div class="header">
<h1>B站AI信息源</h1>
<p>{{DATE_CN}} | 共 {{TOTAL_COUNT}} 条热门视频</p>
</div>
<div class="stats">
<div class="stat-item"><div class="stat-value">{{TOPIC_COUNT}}</div><div class="stat-label">分类</div></div>
<div class="stat-item"><div class="stat-value">{{TOTAL_COUNT}}</div><div class="stat-label">视频</div></div>
<div class="stat-item"><div class="stat-value">{{AVG_READS}}</div><div class="stat-label">平均播放</div></div>
<div class="stat-item"><div class="stat-value">{{TOTAL_LIKES}}</div><div class="stat-label">总点赞</div></div>
</div>
<div class="cards">{{CATEGORY_CARDS}}</div>
<div class="footer">Generated at {{TIMESTAMP}} by B站AI信息源 Skill</div>
</body>
</html>'''
# ─── 订阅机制 ──────────────────────────────────────────────────────────────────────
def install_subscription():
"""安装定时任务,每天自动生成日报"""
if sys.platform == "darwin":
PLIST_DIR.mkdir(parents=True, exist_ok=True)
plist_path = PLIST_DIR / f"{PLIST_LABEL}.plist"
script_path = os.path.abspath(__file__)
log_path = str(Path.home() / "Library" / "Logs" / "qoder-bili-ai-hot-articles.log")
# 传递 API Key 环境变量
env_section = ""
api_key = os.environ.get(ENV_KEY)
if api_key:
env_section = f"""
<key>EnvironmentVariables</key>
<dict>
<key>{ENV_KEY}</key>
<string>{api_key}</string>
</dict>"""
plist_content = f'''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>{PLIST_LABEL}</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/python3</string>
<string>{script_path}</string>
<string>--date</string>
<string>today</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>9</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
<key>StandardOutPath</key>
<string>{log_path}</string>
<key>StandardErrorPath</key>
<string>{log_path}</string>
<key>RunAtLoad</key>
<false/>{env_section}
</dict>
</plist>'''
plist_path.write_text(plist_content, encoding="utf-8")
try:
subprocess.run(["launchctl", "load", str(plist_path)], check=True, capture_output=True)
info("订阅成功! 每天 09:00 自动生成B站爆款日报")
info(f"日报目录: ~/Downloads/QoderReports/")
info(f"日志: {log_path}")
return True
except subprocess.CalledProcessError as e:
error(f"订阅安装失败: {e.stderr.decode()}")
return False
else:
# Linux / Windows: 使用 crontab
script_path = os.path.abspath(__file__)
cron_line = f"0 9 * * * /usr/bin/python3 {script_path} --no-open"
try:
subprocess.run(
f'(crontab -l 2>/dev/null; echo "{cron_line}") | crontab -',
shell=True, check=True, capture_output=True
)
info("订阅成功! 每天 09:00 自动生成B站爆款日报 (crontab)")
info(f"日报目录: ~/Downloads/QoderReports/")
return True
except subprocess.CalledProcessError:
warn("自动配置 crontab 失败,请手动添加:")
print(f" {cron_line}")
return False
def remove_subscription():
"""卸载定时任务"""
if sys.platform == "darwin":
plist_path = PLIST_DIR / f"{PLIST_LABEL}.plist"
if not plist_path.exists():
warn("未找到订阅配置,无需取消")
return False
try:
subprocess.run(["launchctl", "unload", str(plist_path)], check=True, capture_output=True)
except subprocess.CalledProcessError:
pass
plist_path.unlink(missing_ok=True)
info("已取消订阅,定时任务已移除")
return True
else:
script_path = os.path.abspath(__file__)
try:
subprocess.run(
f'crontab -l 2>/dev/null | grep -v "{script_path}" | crontab -',
shell=True, check=True, capture_output=True
)
info("已取消订阅,crontab 任务已移除")
return True
except subprocess.CalledProcessError:
warn("自动移除 crontab 失败,请手动执行: crontab -e")
return False
# ─── 辅助函数 ─────────────────────────────────────────────────────────────
def open_in_browser(filepath):
"""在浏览器中打开 HTML 文件"""
if sys.platform == "darwin":
subprocess.run(["open", str(filepath)], check=False)
elif sys.platform == "linux":
subprocess.run(["xdg-open", str(filepath)], check=False)
elif sys.platform == "win32":
os.startfile(str(filepath))
# ─── 主流程 ────────────────────────────────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser(
description="B站AI信息源 — 每日热门内容聚类日报",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python3 daily_report.py
python3 daily_report.py --keywords "AI教程,AI绘画,ChatGPT"
python3 daily_report.py --subscribe
python3 daily_report.py --unsubscribe
""",
)
parser.add_argument("--keywords", default=",".join(DEFAULT_KEYWORDS),
help="搜索关键词,逗号分隔 (默认: AI;数据不足时自动扩展;所有关键词通过 batch 接口批量查询)")
parser.add_argument("--count", type=int, default=200, help="目标视频数 (默认: 200)")
parser.add_argument("--date", default=datetime.now().strftime("%Y-%m-%d"),
help="指定日期 YYYY-MM-DD (默认: 今天)")
parser.add_argument("--start-time", help="自定义开始时间,格式 YYYY-MM-DD HH:MM:SS (覆盖--date推算)")
parser.add_argument("--end-time", help="自定义结束时间,格式 YYYY-MM-DD HH:MM:SS (覆盖--date推算)")
parser.add_argument("--output-dir", help=f"输出目录 (默认: ~/Downloads/QoderReports)")
parser.add_argument("--api-key", help="API Key (不传则读取环境变量 REDFOX_API_KEY)")
parser.add_argument("--subscribe", action="store_true", help="安装每日定时任务 (09:00)")
parser.add_argument("--unsubscribe", action="store_true", help="卸载定时任务")
parser.add_argument("--latest", action="store_true",
help="自动使用最新有数据的日期(跳过无数据区间,不扣积分)")
args = parser.parse_args()
# ── Banner ──
banner = f"""{CYAN}{BOLD}
╔══════════════════════════════════════╗
║ B站AI信息源 · 日报生成 ║
║ 每日热门内容聚类 · 爆款一网打尽 ║
╚══════════════════════════════════════╝{RESET}
"""
print(banner)
# ── 订阅/取消 ──
if args.subscribe:
install_subscription()
return
if args.unsubscribe:
remove_subscription()
return
# ── 检查依赖 ──
if not HAS_REQUESTS:
error("缺少 requests 库,请安装: pip3 install requests")
sys.exit(1)
# ── API Key ──
api_key = get_api_key(cli_key=args.api_key)
# ── Session ──
session = requests.Session()
session.verify = True
session.headers.update({
"Content-Type": "application/json",
"X-API-KEY": api_key,
})
# ── 时间范围推算 ──(始终传入时间范围,不再支持全量查询)
if args.start_time and args.end_time:
# 用户自定义时间段,直接使用
start_time = args.start_time
end_time = args.end_time
# 若未显式指定 --date,则从 start-time 中提取查询日期
if not any(a.startswith('--date') for a in sys.argv[1:]):
args.date = start_time[:10] # "YYYY-MM-DD"
# ── 日期有效性检查(自定义时间段)──
if is_timerange_likely_empty(start_time, end_time):
latest_date = get_latest_available_date()
print(f"\n {YELLOW}{BOLD}⚠️ {args.date} 数据尚未更新{RESET}")
print(f" 数据更新规则:每日15:00更新前一天的数据")
print(f" 当前可查询的最新日期:{latest_date}")
print(f" {YELLOW}调用接口查询无数据日期会扣除积分但无返回结果{RESET}")
if args.latest:
info(f"--latest 模式:自动切换到 {latest_date}")
args.date = latest_date
start_time = latest_date + " 00:00:00"
end_time = latest_date + " 24:00:00"
else:
try:
answer = input(f"\n {CYAN}是否查询 {latest_date} 的数据?(Y/n): {RESET}").strip().lower()
except (EOFError, KeyboardInterrupt):
answer = "n"
if answer in ("", "y", "yes"):
info(f"已切换到 {latest_date}")
args.date = latest_date
start_time = latest_date + " 00:00:00"
end_time = latest_date + " 24:00:00"
else:
warn("保持原始时间范围,可能无数据返回")
else:
# 根据 --date 推算当天 00:00:00 ~ 24:00:00
try:
dt = datetime.strptime(args.date, "%Y-%m-%d")
except ValueError:
warn(f"日期格式错误: {args.date},回退到今天")
dt = datetime.now()
# ── 日期有效性检查(单日期)──
if is_date_likely_empty(args.date):
latest_date = get_latest_available_date()
print(f"\n {YELLOW}{BOLD}⚠️ {args.date} 数据尚未更新{RESET}")
print(f" 数据更新规则:每日15:00更新前一天的数据")
print(f" 当前可查询的最新日期:{latest_date}")
print(f" {YELLOW}调用接口查询无数据日期会扣除积分但无返回结果{RESET}")
if args.latest:
info(f"--latest 模式:自动切换到 {latest_date}")
args.date = latest_date
dt = datetime.strptime(latest_date, "%Y-%m-%d")
else:
try:
answer = input(f"\n {CYAN}是否查询 {latest_date} 的数据?(Y/n): {RESET}").strip().lower()
except (EOFError, KeyboardInterrupt):
answer = "n"
if answer in ("", "y", "yes"):
info(f"已切换到 {latest_date}")
args.date = latest_date
dt = datetime.strptime(latest_date, "%Y-%m-%d")
else:
warn("保持原始日期,可能无数据返回")
start_time = dt.strftime("%Y-%m-%d") + " 00:00:00"
end_time = dt.strftime("%Y-%m-%d") + " 24:00:00"
time_desc = f"时间段: {start_time} ~ {end_time}"
# ── 构建关键词列表 ──
user_keywords = [k.strip() for k in args.keywords.split(",") if k.strip()]
# 用户自定义关键词:仅使用用户提供的列表
# 默认关键词:先用 "AI",不足时自动追加扩展词
if args.keywords == ",".join(DEFAULT_KEYWORDS):
keywords = list(DEFAULT_KEYWORDS) + [kw for kw in EXPAND_KEYWORDS if kw not in DEFAULT_KEYWORDS]
step(f"主关键词: {DEFAULT_KEYWORDS},数据不足时自动扩展: {EXPAND_KEYWORDS}(batch 批量查询)")
else:
keywords = user_keywords
step(f"自定义关键词: {keywords}(batch 批量查询)")
step(f"目标: {args.count} 条, {time_desc}")
print()
articles = fetch_articles(session, keywords, args.count, start_time=start_time, end_time=end_time)
if not articles:
error("未获取到任何视频")
print(f"\n{YELLOW} 提示:当前仅搜索 AI 相关B站视频。{RESET}")
print(f"{YELLOW} 如需搜索全量B站内容,请访问 redfox.hk{RESET}")
sys.exit(1)
info(f"扫描完成: {len(articles)} 条热门视频")
# ── 自动聚类 ──
step("正在自动聚类...")
clusters = cluster_articles(articles)
info(f"聚类完成: 发现 {len(clusters)} 个分类")
for c in clusters[:10]:
print(f" {c['category']}: {c['count']} 条")
# ── 终端表格展示 ──
print_article_table(clusters)
# ── AI情报调查报告 ──
step("正在生成AI情报调查报告...")
briefing = generate_intelligence_briefing(clusters, articles)
if briefing:
info(f"情报调查完成: {len(briefing['investigation_reports'])}个调查报告")
print_intelligence_briefing(briefing)
# ── 生成报告 ──
step("生成 HTML 日报...")
html_content = generate_report(clusters, articles, args.date, api_key=api_key, briefing=briefing)
# ── 保存文件 ──
output_dir = Path(args.output_dir) if args.output_dir else DEFAULT_OUTPUT_DIR
output_dir.mkdir(parents=True, exist_ok=True)
filename = f"AI-B站日报_{args.date}_{datetime.now().strftime('%H%M%S')}.html"
output_path = output_dir / filename
output_path.write_text(html_content, encoding="utf-8")
info(f"日报已生成: {output_path}")
open_in_browser(output_path)
info(f"浏览器已打开: {output_path}")
# ── 结构化摘要(对齐固定输出模式)──
print(f"\n{GREEN}{BOLD}✓ 完成!{RESET}")
# 分类概览表
total = len(articles)
print(f"\n {BOLD}分类概览{RESET}")
print(f" {'分类':<16}{'数量':>6}{'占比':>8}{'亮点'}")
print(f" {'─'*60}")
for c in clusters:
ratio = f"{c['count']/total*100:.1f}%" if total > 0 else "0%"
# 亮点:头部视频标题截断 + 点赞
highlight = "暂无"
if c["articles"]:
top = c["articles"][0]
title = (top.get("title") or "无标题")[:20]
likes = format_number(top.get("likeCount") or 0)
highlight = f"{title} {likes}赞"
print(f" {c['category']:<16}{c['count']:>5}条{ratio:>8} {highlight}")
# AI情报调查报告摘要
if briefing:
print(f"\n {BOLD}AI情报调查报告{RESET}")
if briefing["emerging_topics"]:
emerging_str = ", ".join(f"{e['topic']}({e['avg_engagement']}+互动)" for e in briefing["emerging_topics"][:3])
print(f" 起量信号: {emerging_str}")
if briefing["top_authors"]:
authors_str = ", ".join(f"@{a['name']}({a['article_count']}条)" for a in briefing["top_authors"][:3])
print(f" 核心达人: {authors_str}")
reports_count = len(briefing['investigation_reports'])
findings_count = sum(len(r.get('findings', [])) for r in briefing['investigation_reports'])
print(f" 调查报告: {reports_count}个话题 / {findings_count}项发现")
# 日报地址
print(f"\n {BOLD}日报地址{RESET}: {output_path}")
print(f"\n 分类: {len(clusters)} 个")
print(f" 视频: {len(articles)} 条")
if __name__ == "__main__":
main()
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>B站AI信息源 - {{DATE}}</title>
<!-- Fonts: Archivo Black (display) + Space Grotesk (body) -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Archivo+Black&family=Space+Grotesk:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
/* ═══════════════════════════════════════════════
CSS CUSTOM PROPERTIES — BILIBILI BRAND PRESET
═══════════════════════════════════════════════ */
:root {
--bg-primary: #0f0f0f;
--bg-secondary: #1a1a1a;
--bg-card: #1e1e1e;
--bg-card-hover: #252525;
--text-primary: #f0ece6;
--text-secondary: #9a9590;
--text-muted: #666;
--accent: #FB7299;
--accent-light: #FB93B5;
--accent-glow: rgba(251, 114, 153, 0.15);
--accent-border: rgba(251, 114, 153, 0.3);
--border: rgba(255, 255, 255, 0.06);
--font-display: 'Archivo Black', Impact, sans-serif;
--font-body: 'Space Grotesk', -apple-system, sans-serif;
--title-size: clamp(1.8rem, 4vw, 3.5rem);
--h2-size: clamp(1.2rem, 2.5vw, 1.8rem);
--h3-size: clamp(1rem, 1.8vw, 1.3rem);
--body-size: clamp(0.8rem, 1.2vw, 0.95rem);
--small-size: clamp(0.7rem, 0.9vw, 0.8rem);
--radius: 12px;
--radius-sm: 8px;
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
}
/* ═══════════════════════════════════════════════
RESET & BASE
═══════════════════════════════════════════════ */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
font-family: var(--font-body);
font-size: var(--body-size);
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
min-height: 100vh;
padding: clamp(1rem, 3vw, 3rem);
}
/* ═══════════════════════════════════════════════
HEADER — DATE PROMINENT
═══════════════════════════════════════════════ */
.report-header {
text-align: center;
padding: clamp(2rem, 5vw, 4rem) 0 clamp(1rem, 2vw, 1.5rem);
max-width: 900px;
margin: 0 auto;
}
.report-header h1 {
font-family: var(--font-display);
font-size: var(--title-size);
color: var(--accent);
letter-spacing: -0.02em;
margin-bottom: 0.3rem;
}
.report-header .subtitle {
font-size: clamp(0.9rem, 1.5vw, 1.1rem);
color: var(--text-secondary);
font-weight: 300;
margin-bottom: 1.2rem;
}
/* 大日期显示 */
.date-display {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
margin: 1.2rem 0;
}
.date-display .date-text {
font-family: var(--font-display);
font-size: clamp(1.4rem, 3vw, 2.4rem);
color: var(--text-primary);
letter-spacing: 0.02em;
}
.date-nav-btn {
background: var(--bg-secondary);
border: 1px solid var(--border);
color: var(--text-secondary);
width: 36px;
height: 36px;
border-radius: 50%;
cursor: pointer;
font-size: 1.1rem;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.date-nav-btn:hover {
border-color: var(--accent-border);
color: var(--accent);
background: var(--accent-glow);
}
.article-count-badge {
display: inline-block;
margin-top: 0.6rem;
padding: 0.3rem 1rem;
background: var(--accent-glow);
border: 1px solid var(--accent-border);
border-radius: 20px;
font-size: var(--small-size);
color: var(--accent-light);
font-weight: 500;
}
/* ═══════════════════════════════════════════════
STATS BAR
═══════════════════════════════════════════════ */
.stats-bar {
display: flex;
justify-content: center;
gap: clamp(1.5rem, 4vw, 3rem);
padding: clamp(1rem, 2vw, 1.5rem) clamp(1rem, 3vw, 2rem);
background: var(--bg-secondary);
border-radius: var(--radius);
border: 1px solid var(--border);
max-width: 700px;
margin: clamp(1.5rem, 3vw, 2rem) auto clamp(2rem, 4vw, 3rem);
flex-wrap: wrap;
}
.stat-item {
text-align: center;
min-width: 80px;
}
.stat-value {
font-family: var(--font-display);
font-size: var(--h2-size);
color: var(--accent);
line-height: 1.2;
}
.stat-label {
font-size: var(--small-size);
color: var(--text-secondary);
margin-top: 0.2rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* ═══════════════════════════════════════════════
CATEGORY CARDS GRID
═══════════════════════════════════════════════ */
.cards-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(100%, 380px), 1fr));
gap: clamp(1rem, 2vw, 1.5rem);
max-width: 1200px;
margin: 0 auto;
}
.category-card {
background: var(--bg-card);
border-radius: var(--radius);
border: 1px solid var(--border);
overflow: hidden;
transition: transform 0.3s var(--ease-out-expo), border-color 0.3s ease;
}
.category-card:hover {
transform: translateY(-2px);
border-color: var(--accent-border);
}
.category-card.hidden {
display: none;
}
.card-header {
display: flex;
align-items: center;
gap: 0.8rem;
padding: clamp(1rem, 2vw, 1.3rem) clamp(1rem, 2vw, 1.5rem);
background: linear-gradient(135deg, var(--accent) 0%, var(--accent-light) 100%);
}
.card-number {
font-family: var(--font-display);
font-size: var(--h2-size);
color: rgba(0, 0, 0, 0.3);
line-height: 1;
}
.card-category {
flex: 1;
font-family: var(--font-body);
font-size: var(--h3-size);
font-weight: 700;
color: #fff;
}
.card-count {
font-size: var(--small-size);
color: rgba(255, 255, 255, 0.8);
font-weight: 500;
background: rgba(0, 0, 0, 0.2);
padding: 0.2rem 0.6rem;
border-radius: 10px;
}
.card-body {
padding: clamp(0.8rem, 1.5vw, 1.2rem) clamp(1rem, 2vw, 1.5rem);
}
/* ═══════════════════════════════════════════════
ARTICLE ITEMS
═══════════════════════════════════════════════ */
.article-item {
padding: 0.7rem 0;
border-bottom: 1px solid var(--border);
display: flex;
gap: 0.8rem;
align-items: flex-start;
}
.article-item:last-child {
border-bottom: none;
padding-bottom: 0;
}
.article-cover {
width: 72px;
height: 72px;
border-radius: var(--radius-sm);
object-fit: cover;
flex-shrink: 0;
background: var(--bg-secondary);
}
.article-info {
flex: 1;
min-width: 0;
}
.article-title {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
color: var(--text-primary);
text-decoration: none;
font-size: var(--body-size);
font-weight: 500;
line-height: 1.5;
transition: color 0.2s ease;
}
.article-title:hover {
color: var(--accent);
}
.article-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 0.3rem;
font-size: var(--small-size);
color: var(--text-muted);
flex-wrap: wrap;
gap: 0.3rem;
}
.author {
color: var(--text-secondary);
}
.metrics {
display: flex;
gap: 0.8rem;
}
.metric {
white-space: nowrap;
}
/* ═══════════════════════════════════════════════
FOOTER
═══════════════════════════════════════════════ */
.report-footer {
text-align: center;
padding: clamp(2rem, 4vw, 3rem) 0 1rem;
color: var(--text-muted);
font-size: var(--small-size);
}
.report-footer .powered {
margin-top: 0.5rem;
color: var(--text-secondary);
}
/* ═══════════════════════════════════════════════
ANIMATIONS
═══════════════════════════════════════════════ */
.reveal {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.6s var(--ease-out-expo), transform 0.6s var(--ease-out-expo);
}
.reveal.visible {
opacity: 1;
transform: translateY(0);
}
.category-card:nth-child(1) { transition-delay: 0.05s; }
.category-card:nth-child(2) { transition-delay: 0.1s; }
.category-card:nth-child(3) { transition-delay: 0.15s; }
.category-card:nth-child(4) { transition-delay: 0.2s; }
.category-card:nth-child(5) { transition-delay: 0.25s; }
.category-card:nth-child(6) { transition-delay: 0.3s; }
.category-card:nth-child(7) { transition-delay: 0.35s; }
.category-card:nth-child(8) { transition-delay: 0.4s; }
/* ═══════════════════════════════════════════════
RESPONSIVE
═══════════════════════════════════════════════ */
@media (max-width: 600px) {
.stats-bar {
gap: 1rem;
}
.stat-item {
min-width: 60px;
}
.cards-grid {
grid-template-columns: 1fr;
}
.date-display .date-text {
font-size: 1.3rem;
}
.article-cover {
width: 56px;
height: 56px;
}
}
@media (prefers-reduced-motion: reduce) {
.reveal {
opacity: 1;
transform: none;
transition: none;
}
}
/* ═══════════════════════════════════════════════
INTELLIGENCE SECTION — AI情报调查报告
═══════════════════════════════════════════════ */
.intelligence-section {
max-width: 1200px;
margin: 3rem auto 0;
background: var(--bg-card);
border-radius: var(--radius);
border: 1px solid var(--accent-border);
overflow: hidden;
}
.intel-header {
padding: clamp(1.2rem, 2vw, 1.8rem) clamp(1.2rem, 2vw, 1.8rem);
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
border-bottom: 2px solid var(--accent-border);
}
.intel-title {
font-family: var(--font-display);
font-size: var(--h2-size);
color: var(--accent-light);
margin-bottom: 0.3rem;
}
.intel-subtitle-badge {
font-size: var(--small-size);
color: var(--text-secondary);
background: rgba(251, 114, 153, 0.1);
padding: 0.15rem 0.6rem;
border-radius: 8px;
border: 1px solid var(--accent-border);
}
.intel-body {
padding: clamp(1rem, 2vw, 1.5rem);
}
.intel-subsection {
margin-bottom: 1.5rem;
}
.intel-subsection:last-child {
margin-bottom: 0;
}
.intel-subtitle {
font-size: var(--body-size);
font-weight: 600;
color: var(--accent-light);
margin-bottom: 0.8rem;
padding-bottom: 0.4rem;
border-bottom: 1px solid var(--border);
}
/* 热度排行 */
.intel-rank-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.intel-rank-item {
display: flex;
align-items: center;
gap: 0.8rem;
padding: 0.6rem 0.8rem;
background: var(--bg-secondary);
border-radius: var(--radius-sm);
transition: background 0.2s ease;
}
.intel-rank-item:hover {
background: var(--bg-card-hover);
}
.intel-rank-num {
font-family: var(--font-display);
font-size: 1.3rem;
color: var(--accent);
min-width: 1.5rem;
text-align: center;
}
.intel-rank-info {
flex: 1;
min-width: 0;
}
.intel-rank-topic {
display: block;
font-weight: 600;
color: var(--text-primary);
font-size: var(--body-size);
}
.intel-rank-detail {
display: block;
font-size: var(--small-size);
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.intel-rank-metric {
font-size: var(--small-size);
color: var(--accent-light);
font-weight: 500;
white-space: nowrap;
}
/* 新兴起量 */
.intel-emerging-list {
display: flex;
flex-wrap: wrap;
gap: 0.6rem;
}
.intel-emerging-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.4rem 0.8rem;
background: rgba(251, 114, 153, 0.08);
border: 1px solid var(--accent-border);
border-radius: 20px;
}
.intel-emerging-badge {
font-size: 0.65rem;
font-weight: 700;
color: var(--accent);
background: var(--accent-glow);
padding: 0.1rem 0.4rem;
border-radius: 4px;
text-transform: uppercase;
}
.intel-emerging-topic {
font-weight: 600;
color: var(--text-primary);
font-size: var(--small-size);
}
.intel-emerging-detail {
font-size: var(--small-size);
color: var(--text-muted);
}
/* 核心达人 */
.intel-author-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.intel-author-item {
display: flex;
align-items: center;
gap: 0.6rem;
padding: 0.5rem 0.8rem;
background: var(--bg-secondary);
border-radius: var(--radius-sm);
}
.intel-author-name {
font-weight: 600;
color: var(--accent-light);
font-size: var(--small-size);
}
.intel-author-stats {
font-size: var(--small-size);
color: var(--text-muted);
}
/* ═══════════════════════════════════════════════
调查报告卡片 — 每个TOP话题一张
═══════════════════════════════════════════════ */
.intel-report-card {
background: var(--bg-secondary);
border-radius: var(--radius);
border: 1px solid var(--border);
overflow: hidden;
margin-bottom: 1.2rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
transition: box-shadow 0.2s ease;
}
.intel-report-card:hover {
box-shadow: 0 4px 16px rgba(0,0,0,0.25);
}
.intel-report-card:last-child {
margin-bottom: 0;
}
.intel-report-head {
display: flex;
align-items: center;
gap: 0.8rem;
padding: 0.9rem 1.2rem;
background: linear-gradient(135deg, rgba(251, 114, 153, 0.15) 0%, rgba(251, 114, 153, 0.04) 100%);
border-bottom: 1px solid var(--border);
}
.intel-report-topic {
font-family: var(--font-display);
font-size: 1.15rem;
color: var(--accent-light);
flex: 1;
font-weight: 700;
}
.intel-report-mode {
font-size: var(--small-size);
font-weight: 600;
color: var(--accent-light);
background: rgba(251, 114, 153, 0.18);
padding: 0.2rem 0.7rem;
border-radius: 6px;
border: 1px solid var(--accent-border);
}
.intel-report-engines {
font-size: var(--small-size);
color: var(--text-muted);
white-space: nowrap;
}
.intel-report-body {
padding: 1rem 1.2rem;
}
/* 调查场景标签 */
.intel-report-scenario {
display: inline-block;
font-size: 0.72rem;
color: var(--text-muted);
background: rgba(255,255,255,0.05);
padding: 0.15rem 0.5rem;
border-radius: 4px;
margin-bottom: 0.6rem;
border: 1px solid var(--border);
}
/* 调查发现表 */
.intel-findings-table {
width: 100%;
border-collapse: collapse;
font-size: var(--small-size);
margin-bottom: 1rem;
}
.intel-findings-table th {
text-align: left;
padding: 0.5rem 0.6rem;
border-bottom: 1px solid var(--accent-border);
color: var(--accent-light);
font-weight: 600;
white-space: nowrap;
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.intel-findings-table td {
padding: 0.5rem 0.6rem;
border-bottom: 1px solid var(--border);
color: var(--text-secondary);
line-height: 1.5;
}
.intel-findings-table td:first-child {
font-weight: 600;
color: var(--text-primary);
white-space: nowrap;
width: 72px;
}
.intel-findings-table td:nth-child(2) {
/* 发现列占最大空间 */
}
.intel-findings-table td:nth-child(3) {
white-space: nowrap;
color: var(--text-muted);
font-size: 0.72rem;
}
.intel-findings-table td:nth-child(4) {
text-align: center;
width: 56px;
}
.intel-cred-badge {
display: inline-block;
padding: 0.1rem 0.4rem;
border-radius: 4px;
font-weight: 600;
font-size: 0.7rem;
}
.intel-cred-a { background: rgba(72, 187, 120, 0.15); color: #48bb78; }
.intel-cred-b { background: rgba(66, 153, 225, 0.15); color: #4299e1; }
.intel-cred-c { background: rgba(237, 137, 54, 0.15); color: #ed8936; }
.intel-cred-d { background: rgba(245, 101, 101, 0.15); color: #f56565; }
/* 关键结论 */
.intel-conclusion-title {
font-size: var(--small-size);
font-weight: 700;
color: var(--accent-light);
margin-bottom: 0.5rem;
padding-top: 0.6rem;
border-top: 1px dashed var(--border);
}
.intel-conclusion-list {
list-style: none;
padding: 0;
margin: 0;
background: rgba(255,255,255,0.02);
border-radius: var(--radius-sm);
padding: 0.5rem 0.7rem;
}
.intel-conclusion-item {
padding: 0.3rem 0;
font-size: var(--small-size);
color: var(--text-secondary);
line-height: 1.6;
}
.intel-conclusion-confirmed { color: #48bb78; }
.intel-conclusion-pending { color: #ed8936; }
.intel-conclusion-denied { color: #f56565; }
.intel-conclusion-single { color: var(--text-muted); }
/* 跨平台对比 */
.intel-cross-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.intel-cross-tip {
padding: 0.5rem 0.8rem;
background: var(--bg-secondary);
border-radius: var(--radius-sm);
font-size: var(--small-size);
color: var(--text-secondary);
line-height: 1.5;
}
/* 可信度标注规范(紧凑版) */
.intel-cred-ref {
display: flex;
flex-wrap: wrap;
gap: 0.8rem;
padding: 0.6rem 0.8rem;
background: var(--bg-secondary);
border-radius: var(--radius-sm);
font-size: var(--small-size);
}
.intel-cred-ref-item {
display: flex;
align-items: center;
gap: 0.3rem;
color: var(--text-secondary);
}
.intel-cred-ref-item .intel-cred-badge {
margin-right: 0.2rem;
}
.intel-cred-ref-label {
color: var(--text-muted);
font-size: 0.7rem;
}
@media (max-width: 600px) {
.intel-report-head {
flex-wrap: wrap;
}
.intel-report-engines {
width: 100%;
}
.intel-findings-table {
font-size: 0.7rem;
}
.intel-findings-table td,
.intel-findings-table th {
padding: 0.3rem 0.3rem;
}
}
</style>
</head>
<body>
<!-- HEADER -->
<header class="report-header">
<h1>B站AI信息源</h1>
<p class="subtitle">每日热门内容聚类 · 点赞量靠前的 AI 爆款一网打尽</p>
<!-- 大日期 + 前后切换 -->
<div class="date-display">
<button class="date-nav-btn" id="prevDay" title="前一天">←</button>
<span class="date-text">{{DATE_CN}}</span>
<button class="date-nav-btn" id="nextDay" title="后一天">→</button>
</div>
<span class="article-count-badge">共 {{TOTAL_COUNT}} 条热门视频</span>
</header>
<!-- STATS BAR -->
<div class="stats-bar">
<div class="stat-item">
<div class="stat-value">{{TOPIC_COUNT}}</div>
<div class="stat-label">分类</div>
</div>
<div class="stat-item">
<div class="stat-value">{{TOTAL_COUNT}}</div>
<div class="stat-label">视频</div>
</div>
<div class="stat-item">
<div class="stat-value">{{AVG_LIKES}}</div>
<div class="stat-label">平均点赞</div>
</div>
<div class="stat-item">
<div class="stat-value">{{TOTAL_LIKES}}</div>
<div class="stat-label">总点赞</div>
</div>
</div>
<!-- CATEGORY CARDS -->
<div class="cards-grid" id="cardsGrid">
{{CATEGORY_CARDS}}
</div>
<!-- INTELLIGENCE SECTION -->
{{INTELLIGENCE_SECTION}}
<!-- FOOTER -->
<footer class="report-footer">
<p>Generated at {{TIMESTAMP}}</p>
<p class="powered">Powered by B站AI信息源 Skill</p>
</footer>
<!-- SCRIPTS -->
<script>
(function() {
// ── Scroll reveal animation ──
var observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
}
});
}, { threshold: 0.1, rootMargin: '0px 0px -50px 0px' });
document.querySelectorAll('.reveal').forEach(function(el) {
observer.observe(el);
});
// ── Date navigation ──
var currentDate = '{{DATE}}';
function navigateDate(offset) {
var parts = currentDate.split('-');
var d = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]));
d.setDate(d.getDate() + offset);
var y = d.getFullYear();
var m = String(d.getMonth() + 1).padStart(2, '0');
var day = String(d.getDate()).padStart(2, '0');
var newDate = y + '-' + m + '-' + day;
var newFile = 'AI-B站日报_' + newDate + '.html';
window.location.href = newFile;
}
document.getElementById('prevDay').addEventListener('click', function() {
navigateDate(-1);
});
document.getElementById('nextDay').addEventListener('click', function() {
navigateDate(1);
});
})();
</script>
</body>
</html>
B站AI信息源 (Bilibili AI Feed) / bili-ai-feed
---
Introduction
Automatically scans Bilibili AI-related videos every day, discovers trending content by likes, intelligently clusters topics into a visual HTML daily report, and simultaneously conducts multi-engine AI intelligence investigations on hot topics, delivering structured investigation reports.
Core Value
- Precise Trending Discovery: Filters high-engagement AI videos from Bilibili, ensuring no important daily AI updates are missed.
- Auto Topic Clustering: Intelligently identifies directions such as AI tutorials, large models, and AI art — categories are determined dynamically by the day's content, no manual sorting needed.
- In-depth Intelligence Investigation: Automatically performs multi-search-engine cross-validation on hot topics and delivers structured reports with credibility labels.
- One-click Visualization: Dark-themed HTML daily report with cover images, engagement data, and video links for a clear overview of daily AI trending content.
Target Audience
- 🤖 AI Content Researchers — Stay on top of Bilibili AI trends every day without manually browsing videos.
- 📊 Industry Intelligence Analysts — Quickly understand AI topic trends and competitor dynamics with multi-engine investigation reports.
- 🎬 Content Creators / Operators — Discover trending directions and get data-driven support for content planning.
---
Features
Core Capabilities
- Trending Video Discovery: Filters high-engagement videos from Bilibili AI-related accounts by likes to pinpoint the day's hot content.
- Smart Keyword Query: Defaults to querying "AI"; automatically expands keywords in batch when data is insufficient, saving API quota.
- Custom Keywords: Supports user-specified keyword combinations (e.g., "AI art, ComfyUI") for targeted queries in specific niches.
- Intelligent Topic Clustering: Automatically identifies and classifies daily trending topic directions; classification is dynamically generated from actual content.
- AI Intelligence Investigation: Based on clustered TOP topics, executes four investigation modes — competitor, public sentiment, background check, cross-validation — with multi-engine verification to produce structured reports.
- Visual HTML Daily Report: Dark-themed page with cover images, engagement data, and video links; auto-opens preview after generation.
- Daily Subscription: Enables automatic daily report generation and cumulative local saving.
---
API Key Acquisition & Security
- This skill requires the environment variable:
REDFOX_API_KEY. REDFOX_API_KEYis provided by RedFoxHub (https://redfox.hk).- Visit RedFoxHub to register an account and obtain your
REDFOX_API_KEY. - Configure the device environment variable
REDFOX_API_KEYbefore using this skill. - Before providing your key, confirm the key source, scope of use, validity period, and whether reset/revocation is supported.
- Never hard-code or expose the key in plain text in code, prompts, logs, or output files.
---
Usage Guide
Simply describe your needs in natural language — no commands to memorize.
Quick Reference
| Intent | Example | Result |
|---|---|---|
| Get latest AI daily report | "Generate today's Bilibili AI daily report" | Automatically retrieves the latest available date and generates the trending report with intelligence investigation |
| View historical report | "Check the Bilibili AI trending content for 2026-06-10" | Fetches the trending video report and topic investigation for the specified date |
| Targeted niche query | "Search for popular Bilibili videos about AI art and ComfyUI" | Queries by custom keywords, clusters results, and generates a focused report |
| Enable daily subscription | "Subscribe to the Bilibili AI daily report, auto-generate every day" | Enables subscription; reports are automatically saved to a local folder |
| Cancel subscription | "Cancel the Bilibili AI daily report subscription" | Disables daily automatic subscription |
Output Example
After the report is generated, you will receive a structured conversation report, roughly as follows (illustrative):
Bilibili AI Feed · 2026-06-10 Daily Report
Scanned 200 trending videos, clustered into 5 categories
Category Overview
| Category | Count | Share | Highlight |
|---|---|---|---|
| #Large Models | 68 | 34% | A video with 10k+ likes discussing new model capabilities |
| #AI Tutorials | 52 | 26% | Beginner series with steadily climbing views |
Also includes AI intelligence investigation reports for each TOP topic, with multi-engine verification conclusions and credibility labels.
---
Use Cases
| Scenario | Role | Example Query | Benefit |
|---|---|---|---|
| Daily AI trending tracking | AI researcher / operator | "Generate today's Bilibili AI daily report" | Quickly grasp the day's Bilibili AI trending content and dynamics |
| Competitor & topic intelligence | Industry analyst | "Check recent Bilibili trending content about large models and do an intelligence analysis" | Get structured competitor/topic reports with multi-engine cross-validation |
| Niche content discovery | Content creator | "Find popular Bilibili videos in the AI art direction recently" | Discover high-engagement content by keyword to support content planning |
| Historical data review | Content operator | "Check the Bilibili AI video trends from last Wednesday" | Review historical reports to analyze content trend changes |
---
B站AI信息源 / bili-ai-feed
---
简介
每日自动扫描B站AI相关视频,按点赞量发现爆款内容,智能聚类话题后生成可视化HTML日报,并同步对热门话题执行多引擎AI情报调查,输出结构化报告。
核心价值
- 爆款精准发现:从海量B站AI视频中筛选高热度内容,不遗漏每日重要AI动态。
- 话题自动聚类:智能识别AI教程、大模型、AI绘画等方向,分类由当日内容动态决定,无需手动整理。
- 情报深度调查:对热门话题自动执行多搜索引擎交叉验证,输出带可信度标注的结构化调查报告。
- 可视化一键呈现:深色主题HTML日报,含封面图、互动数据与视频直链,直观浏览每日AI热点。
适用对象
- 🤖 AI 内容研究者 — 每日掌握B站AI热点,省去手动刷视频的时间。
- 📊 行业情报分析师 — 结合多引擎调查报告,快速了解AI话题走向与竞品动态。
- 🎬 内容创作者 / 运营 — 发现爆款方向,为选题与内容策略提供数据支撑。
---
功能特性
核心功能
- 爆款视频发现:按点赞量从B站AI相关账号中筛选高热度视频,精准定位每日热门内容。
- 智能关键词查询:默认查询"AI",数据不足时自动扩展关键词批量查询,节省接口额度。
- 自定义关键词:支持用户指定任意关键词组合(如"AI绘画,ComfyUI"),定向查询细分方向。
- 话题智能聚类:自动识别并分类当日热门话题方向,分类结果由实际内容动态生成。
- AI情报调查:基于聚类TOP话题,执行竞品/舆情/人物/交叉验证四种调查模式,多引擎交叉验证输出结构化报告。
- 可视化HTML日报:深色主题页面,展示封面图、互动数据与视频直链,生成后自动打开预览。
- 每日订阅:开启订阅后自动产出日报,累积保存到本地文件夹。
---
密钥获取与安全说明
- 本技能需要使用环境变量:
REDFOX_API_KEY。 REDFOX_API_KEY由 红狐 hub (https://redfox.hk)提供。- 请前往 红狐 hub 注册账号,获取
REDFOX_API_KEY。 - 配置设备环境变量
REDFOX_API_KEY后使用本技能。 - 在提供密钥前,请先确认密钥来源、可用范围、有效期及是否支持重置/撤销。
- 禁止在代码、提示词、日志或输出文件中硬编码/明文暴露密钥。
---
使用指南
直接用自然语言描述需求即可,无需记忆命令。
常用说法速查
| 意图 | 示例话术 | 效果 |
|---|---|---|
| 查看最新AI日报 | 「帮我生成今天的B站AI日报」 | 自动获取最新有数据日期,生成爆款日报与情报报告 |
| 查看历史日报 | 「查一下2026-06-10的B站AI热点」 | 获取指定日期的爆款视频日报与话题调查 |
| 定向查某个方向 | 「查B站AI绘画、ComfyUI相关的热门视频」 | 按自定义关键词定向查询,聚类并生成专项日报 |
| 开启每日订阅 | 「帮我订阅B站AI日报,每天自动生成」 | 开启订阅,日报自动保存到本地文件夹 |
| 取消订阅 | 「取消B站AI日报订阅」 | 关闭每日自动订阅 |
输出示例
日报生成后,你将收到结构化的对话报告,大致如下(示意):
B站AI信息源 · 2026-06-10 日报
扫描 200 条热门视频,聚类 5 个分类
分类概览
| 分类 | 数量 | 占比 | 亮点 |
|---|---|---|---|
| #大模型 | 68条 | 34% | 某视频点赞破万,讨论新模型能力 |
| #AI教程 | 52条 | 26% | 入门系列播放量持续攀升 |
同时附带各TOP话题的AI情报调查报告,含多引擎验证结论与可信度标注。
---
使用场景
| 场景 | 角色 | 示例问法 | 收益 |
|---|---|---|---|
| 每日AI热点追踪 | AI研究者 / 运营 | 「生成今天的B站AI日报」 | 快速了解当日B站AI爆款,掌握热点动态 |
| 竞品与话题情报 | 行业分析师 | 「查一下最近大模型相关的B站热门,做个情报分析」 | 获取含多引擎交叉验证的结构化竞品/话题报告 |
| 细分方向内容挖掘 | 内容创作者 | 「帮我查B站AI绘画方向最近的爆款视频」 | 按关键词定向发现高热度内容,辅助选题决策 |
| 历史数据回溯 | 内容运营 | 「查一下上周三的B站AI视频热点」 | 回溯历史日报,分析内容趋势变化 |
---
引擎选择策略详解
一、决策树:如何选择引擎组合
用户输入调查需求
│
├── 包含中文关键词?
│ ├── 是 → 必选: Baidu + WeChat + Toutiao
│ └── 否 → 跳过国内引擎
│
├── 需要国际视角?
│ ├── 是 → 必选: Google + DuckDuckGo/Brave
│ └── 否 → 聚焦国内引擎
│
├── 信息敏感/需隐私?
│ ├── 是 → 优先: DuckDuckGo + Startpage + Qwant
│ └── 否 → 无特殊要求
│
├── 需要时间线/实时性?
│ ├── 小时级 → Google(tbs=qdr:h) + Brave
│ ├── 天级 → Google(tbs=qdr:d) + Baidu
│ └── 周级 → 全引擎均可
│
├── 需要数据验证?
│ ├── 是 → WolframAlpha + Google Scholar
│ └── 否 → 通用引擎
│
└── 需要技术深度?
├── 是 → DuckDuckGo(!gh !so !npm)
└── 否 → 通用引擎---
二、四类调查模式的引擎编排
竞品情报调查
| 轮次 | 目的 | 引擎 | 搜索策略 |
|---|---|---|---|
| 第1轮 | 广域扫描 | Baidu, Google, Bing INT | 广泛关键词,建立全景 |
| 第2轮 | 深度挖掘 | WeChat, Toutiao, DuckDuckGo | 细分关键词,挖掘真实反馈 |
| 第3轮 | 交叉验证 | Baidu, Google, Brave | 关键数据多源验证 |
关键词构建模板:
- 第1轮:
{竞品名} 产品 功能 定价 - 第2轮:
{竞品名} 使用体验 测评 评价/site:reddit.com {竞品名} review - 第3轮:
{竞品名} 融资 营收 市场份额
舆情事件调查
| 轮次 | 目的 | 引擎 | 搜索策略 |
|---|---|---|---|
| 第1轮 | 事件还原 | Baidu, Google(tbs=qdr:d), Toutiao | 时间过滤+热点词 |
| 第2轮 | 多视角 | WeChat, Sogou, DuckDuckGo | 评论区+论坛+自媒体 |
| 第3轮 | 时间线 | Google(tbs=qdr:w), Bing INT | 追溯事件发展脉络 |
关键词构建模板:
- 第1轮:
{事件关键词}(加时间过滤) - 第2轮:
{事件关键词} 评论 分析 观点/site:reddit.com {事件} - 第3轮:
{事件关键词} 时间线 经过 回顾
人物背景调查
| 轮次 | 目的 | 引擎 | 搜索策略 |
|---|---|---|---|
| 第1轮 | 基本信息 | Baidu, Google, Bing INT | 姓名+职务+公司 |
| 第2轮 | 专业验证 | DuckDuckGo(!gh), Google Scholar | 学术/技术成果 |
| 第3轮 | 信誉排查 | Baidu, Google, WeChat | 争议+诉讼+负面 |
关键词构建模板:
- 第1轮:
{人物名} 简介 背景 职务/{人物名} biography - 第2轮:
!gh {人物名}/author:"{人物名}" - 第3轮:
{人物名} 争议 诉讼 负面/{人物名} controversy
信息交叉验证
| 轮次 | 目的 | 引擎 | 搜索策略 |
|---|---|---|---|
| 第1轮 | 溯源 | Google(精确匹配), Baidu | 引号包裹+精确搜索 |
| 第2轮 | 比对 | DuckDuckGo, Brave, Startpage | 同一关键词不同引擎 |
| 第3轮 | 权威 | WolframAlpha, Google(site:权威站) | 官方信源确认 |
关键词构建模板:
- 第1轮:
"{待验证信息}" - 第2轮:
{待验证信息核心关键词}(不同引擎) - 第3轮:
site:gov.cn {关键词}/ WolframAlpha计算
---
三、引擎独有能力与场景匹配
| 引擎 | 独有能力 | 最佳调查场景 |
|---|---|---|
| 最全索引+高级操作符+时间过滤+语言筛选 | 所有调查的基础引擎 | |
| Baidu | 中文内容最全+知道/贴吧/百科 | 国内舆情+竞品口碑 |
| DuckDuckGo | Bangs直达(!gh !so !w !a)+无追踪 | 技术调查+隐私调查 |
| WeChat搜狗 | 微信公众号文章搜索 | 深度分析文章+行业观察 |
| Toutiao | 自媒体+热点追踪+实时性 | 热点事件+舆论走向 |
| Brave | 独立索引+无偏见+Discussions | 无过滤信息+论坛观点 |
| Startpage | Google结果+隐私保护 | 需Google结果但保护隐私 |
| WolframAlpha | 结构化数据+知识计算 | 数据验证+数值型信息 |
| Bing INT | 中文界面+国际搜索结果 | 跨国调查+国际对比 |
| Sogou | 微信+知乎内容 | 中文社区深度内容 |
---
四、高级搜索策略
4.1 反向搜索法
目的:通过已知信息反推更多细节
已知:公司名 → 反向搜索
├── Google: "site:linkedin.com {公司名}"
├── DuckDuckGo: "!gh {公司名}"
├── Baidu: "{公司名} 团队 创始人"
└── Google: "{公司名} filetype:pdf" (查找公开文档)4.2 时间轴搜索法
目的:追踪事件/信息随时间的变化
├── Google: "{关键词}&tbs=qdr:h" (1小时内)
├── Google: "{关键词}&tbs=qdr:d" (24小时内)
├── Google: "{关键词}&tbs=qdr:w" (1周内)
├── Google: "{关键词}&tbs=qdr:m" (1月内)
└── 对比不同时间段结果变化4.3 地域对比法
目的:对比不同地区的信息差异
├── Baidu: "{关键词}" (中国视角)
├── Google: "{关键词}&gl=us" (美国视角)
├── Google HK: "{关键词}" (香港视角)
├── Ecosia: "{关键词}" (欧洲视角)
└── 对比结果差异,识别信息偏差4.4 垂直深耕法
目的:在特定平台深入挖掘
├── Google: "site:reddit.com {关键词}" (Reddit社区)
├── Google: "site:zhihu.com {关键词}" (知乎)
├── Google: "site:github.com {关键词}" (开源项目)
├── Google: "site:crunchbase.com {关键词}" (融资数据)
├── WeChat: "{关键词}" (公众号深度文章)
└── Google: "site:bloomberg.com {关键词}" (财经数据)4.5 证据链构建法
目的:构建完整证据链确认信息
信息A(待验证)
├── 寻找首发源 → 源头是官方还是转载?
├── 确认传播路径 → 哪些媒体引用了?
├── 检查是否有反驳 → 搜索"辟谣"+"信息关键词"
├── 权威信源验证 → site:gov.cn / site:reuters.com
└── 数据验证 → WolframAlpha(如适用)---
五、常见调查场景引擎组合速查
| 调查场景 | 推荐引擎组合 | 时间过滤 | 关键操作符 |
|---|---|---|---|
| 产品竞品分析 | Baidu+Google+WeChat+DuckDuckGo | 近1月 | site: "" |
| 公司背景调查 | Baidu+Google+Bing INT+WeChat | 无限制 | site:linkedin.com |
| 热点事件追踪 | Baidu+Toutiao+Google+WeChat | 近1天/1周 | tbs=qdr:d |
| 人物背景验证 | Baidu+Google+DuckDuckGo(!gh) | 无限制 | "" site: |
| 融资数据验证 | Google+Baidu+WolframAlpha+Jisilu | 近1年 | site:crunchbase.com |
| 用户口碑收集 | WeChat+Toutiao+DuckDuckGo+Brave | 近1月 | site:reddit.com |
| 技术栈调查 | DuckDuckGo(!gh !so)+Google | 无限制 | !gh !so site: |
| 价格/销量调查 | Baidu+Google+DuckDuckGo(!a) | 近1月 | filetype:pdf |
| 学术论文验证 | Google Scholar+Google+DuckDuckGo | 近2年 | site:arxiv.org |
| 法律诉讼排查 | Baidu+Google+Bing INT | 无限制 | site:court.gov.cn |
调查模式详解
竞品情报调查
分析竞争对手产品、市场策略、用户口碑。
搜索策略编排
第1轮:广域扫描
| 引擎 | 搜索词 | 目的 |
|---|---|---|
| Baidu | {竞品名} 产品 功能 | 建立全景认知 |
| {竞品名} product review | 国际化视角 | |
| Bing INT | {竞品名} vs alternatives | 竞品对比 |
第2轮:深度挖掘
| 引擎 | 搜索词 | 目的 |
|---|---|---|
| {竞品名} 分析 测评 | 深度分析文章 | |
| Toutiao | {竞品名} 用户 评价 | 自媒体评价 |
| DuckDuckGo | {竞品名} site:reddit.com OR site:producthunt.com | 社区真实反馈 |
第3轮:交叉验证
| 引擎 | 搜索词 | 目的 |
|---|---|---|
| Baidu | {竞品名} 融资 营收 | 国内商业数据 |
| {竞品名} funding revenue | 国际商业数据 | |
| Brave | {竞品名} market share | 市场份额验证 |
输出模板
| 维度 | 发现 | 来源 | 可信度 |
|---|---|---|---|
| 产品定位 | ... | 多源交叉 | 高/中/低 |
| 核心功能 | ... | 官方+评测 | 高 |
| 用户口碑 | ... | 社交+论坛 | 中 |
| 市场表现 | ... | 公开数据 | 高 |
| 竞争优势 | ... | 对比分析 | 中 |
| 潜在风险 | ... | 舆情监测 | 低 |
---
舆情事件调查
热点事件追踪、舆论走向分析、危机监测。
搜索策略编排
第1轮:事件还原
| 引擎 | 搜索词 | 目的 |
|---|---|---|
| Baidu | {事件关键词} (tbs=qdr:d) | 国内最新报道 |
| {事件关键词} (tbs=qdr:d) | 国际最新报道 | |
| Toutiao | {事件关键词} | 热点实时追踪 |
第2轮:多视角收集
| 引擎 | 搜索词 | 目的 |
|---|---|---|
| {事件关键词} 评论 分析 | 公众号深度解读 | |
| Sogou | {事件关键词} 观点 | 知乎等中文社区 |
| DuckDuckGo | {事件关键词} site:reddit.com | 国际社区讨论 |
第3轮:时间线重建
| 引擎 | 搜索词 | 目的 |
|---|---|---|
| {事件关键词} (tbs=qdr:w) | 一周内演变 | |
| Bing INT | {事件关键词} timeline | 国际时间线 |
| Baidu | {事件关键词} 时间线 | 国内时间线 |
输出模板
📋 舆情调查报告
🕐 时间线
├── [时间1] 事件起因 — 来源: xxx
├── [时间2] 关键转折 — 来源: xxx
└── [时间3] 当前状态 — 来源: xxx
📊 舆论分布
├── 支持方观点: ... (来源数: N)
├── 反对方观点: ... (来源数: N)
└── 中立/观望: ... (来源数: N)
⚠️ 待验证信息
├── [信息A] — 验证状态: ⚠️ 单一来源
└── [信息B] — 验证状态: ✅ 多源确认
🔑 关键结论
├── 已确认: ...
├── 高概率: ...
└── 待观察: ...---
人物背景调查
商务合作前的背景调查、行业人物了解。
搜索策略编排
第1轮:基本信息
| 引擎 | 搜索词 | 目的 |
|---|---|---|
| Baidu | {人物名} 简介 背景 | 建立人物画像 |
| {人物名} biography profile | 国际化视角 | |
| Bing INT | {人物名} company role | 职业信息 |
第2轮:专业验证
| 引擎 | 搜索词 | 目的 |
|---|---|---|
| Google Scholar | {人物名} | 学术成果验证 |
| DuckDuckGo | !gh {人物名} | 开源项目/技术能力 |
| DuckDuckGo | !so {人物名} | 技术社区活跃度 |
第3轮:信誉排查
| 引擎 | 搜索词 | 目的 |
|---|---|---|
| Baidu | {人物名} 争议 诉讼 | 国内风险信号 |
| {人物名} controversy lawsuit | 国际风险信号 | |
| {人物名} 评价 口碑 | 行业口碑 |
输出模板
| 维度 | 信息 | 来源 | 可信度 |
|---|---|---|---|
| 身份核实 | ... | 官方/社交 | 高 |
| 职业履历 | ... | LinkedIn/官网 | 高 |
| 专业成就 | ... | 学术/社区 | 中 |
| 行业口碑 | ... | 社交媒体 | 中 |
| 风险信号 | ... | 新闻/法律 | 需验证 |
---
信息交叉验证
验证某条信息的真实性、对比不同来源的说法。
搜索策略编排
第1轮:信息溯源
| 引擎 | 搜索词 | 目的 |
|---|---|---|
| "{待验证信息}" (精确匹配) | 找到信息首发源 | |
| Baidu | "{待验证信息}" | 国内信息溯源 |
第2轮:多源比对
| 引擎 | 搜索词 | 目的 |
|---|---|---|
| DuckDuckGo | {待验证信息} | 无追踪比对 |
| Brave | {待验证信息} | 独立索引比对 |
| Startpage | {待验证信息} | Google结果+隐私比对 |
第3轮:权威验证
| 引擎 | 搜索词 | 目的 |
|---|---|---|
| site:gov.cn {相关关键词} | 政府权威信源 | |
| Bing INT | site:reuters.com OR site:apnews.com {关键词} | 国际权威信源 |
| WolframAlpha | {可计算的数据} | 结构化数据验证 |
输出模板
🔍 信息验证报告
📝 待验证信息:"{原始信息}"
📊 验证结果
├── 支持来源数: N个
│ ├── 来源1: xxx (可信度: 高)
│ └── 来源2: xxx (可信度: 中)
├── 反对来源数: N个
│ └── 来源3: xxx
└── 无法确认来源数: N个
⚖️ 综合判定
├── ✅ 已确认: ... (多源一致)
├── ⚠️ 待确认: ... (来源矛盾)
└── ❌ 已否定: ... (权威信源反驳)调查报告模板集
一、竞品情报调查报告
# 🔍 竞品情报调查报告
## 基本信息
- **调查目标**:{竞品名称}
- **调查日期**:{日期}
- **调查模式**:竞品情报调查
---
## 1. 产品定位与概述
| 维度 | 内容 | 来源 |
|------|------|------|
| 公司名称 | ... | ... |
| 产品定位 | ... | ... |
| 目标用户 | ... | ... |
| 核心价值主张 | ... | ... |
| 定价策略 | ... | ... |
## 2. 功能与特性
| 功能 | 描述 | 我们是否有 | 差异分析 |
|------|------|-----------|---------|
| 功能A | ... | ✅/❌ | ... |
| 功能B | ... | ✅/❌ | ... |
| 功能C | ... | ✅/❌ | ... |
## 3. 用户口碑分析
### 正面评价
- 评价1: "..." — 来源: {平台}
- 评价2: "..." — 来源: {平台}
### 负面评价
- 评价1: "..." — 来源: {平台}
- 评价2: "..." — 来源: {平台}
### 口碑总结
- 正面关键词: {关键词1, 关键词2, 关键词3}
- 负面关键词: {关键词1, 关键词2, 关键词3}
## 4. 市场表现
| 指标 | 数据 | 来源 | 可信度 |
|------|------|------|--------|
| 融资轮次/金额 | ... | ... | ... |
| 用户规模 | ... | ... | ... |
| 市场份额 | ... | ... | ... |
| 增长趋势 | ... | ... | ... |
## 5. SWOT分析
| | 正面 | 负面 |
|---|------|------|
| **内部** | **优势(S)**: ... | **劣势(W)**: ... |
| **外部** | **机会(O)**: ... | **威胁(T)**: ... |
## 6. 关键发现与建议
### 🔑 关键发现
1. ...
2. ...
3. ...
### 💡 行动建议
1. ...
2. ...
3. ...
---
*数据来源:多搜索引擎交叉调查 | 可信度标注遵循ABCD分级*---
二、舆情事件调查报告
# 📋 舆情事件调查报告
## 基本信息
- **事件主题**:{事件名称}
- **调查日期**:{日期}
- **调查模式**:舆情事件调查
---
## 1. 事件概况
| 维度 | 内容 |
|------|------|
| 事件概述 | ... |
| 涉及方 | ... |
| 当前状态 | ... |
| 舆论热度 | 🔥🔥🔥 (1-5级) |
## 2. 事件时间线
| 时间 | 事件 | 来源 | 可信度 |
|------|------|------|--------|
| {时间1} | {事件1} | {来源1} | {A/B/C/D} |
| {时间2} | {事件2} | {来源2} | {A/B/C/D} |
| {时间3} | {事件3} | {来源3} | {A/B/C/D} |
## 3. 舆论分布
### 支持方观点
- 观点1: "..." (来源数: N, 代表平台: ...)
- 观点2: "..." (来源数: N, 代表平台: ...)
### 反对方观点
- 观点1: "..." (来源数: N, 代表平台: ...)
- 观点2: "..." (来源数: N, 代表平台: ...)
### 中立/观望
- 观点1: "..." (来源数: N)
## 4. 关键信息验证
| 待验证信息 | 验证结果 | 支持来源 | 反对来源 |
|-----------|---------|---------|---------|
| {信息1} | ✅已确认/⚠️待确认/❌已否定 | ... | ... |
| {信息2} | ✅已确认/⚠️待确认/❌已否定 | ... | ... |
## 5. 舆论走向预判
| 方向 | 概率 | 依据 |
|------|------|------|
| 走向A | 高/中/低 | ... |
| 走向B | 高/中/低 | ... |
## 6. 风险评估
| 风险项 | 影响级别 | 说明 |
|--------|---------|------|
| {风险1} | 🔴高/🟡中/🟢低 | ... |
| {风险2} | 🔴高/🟡中/🟢低 | ... |
---
*数据来源:多搜索引擎交叉调查 | 可信度标注遵循ABCD分级*---
三、人物背景调查报告
# 👤 人物背景调查报告
## 基本信息
- **调查对象**:{人物姓名}
- **调查日期**:{日期}
- **调查模式**:人物背景调查
---
## 1. 身份信息
| 维度 | 信息 | 来源 | 可信度 |
|------|------|------|--------|
| 姓名 | ... | ... | ... |
| 当前职务 | ... | ... | ... |
| 所在公司 | ... | ... | ... |
| 所在地 | ... | ... | ... |
| 教育背景 | ... | ... | ... |
## 2. 职业履历
| 时间 | 职位/经历 | 机构 | 来源 |
|------|---------|------|------|
| ... | ... | ... | ... |
| ... | ... | ... | ... |
## 3. 专业成就
| 成就 | 详情 | 来源 | 可信度 |
|------|------|------|--------|
| ... | ... | ... | ... |
| ... | ... | ... | ... |
## 4. 行业口碑
| 维度 | 评价 | 来源 |
|------|------|------|
| 技术能力 | ... | ... |
| 管理风格 | ... | ... |
| 行业影响力 | ... | ... |
## 5. 风险信号
| 风险项 | 详情 | 来源 | 可信度 |
|--------|------|------|--------|
| 争议事件 | ... | ... | ... |
| 法律诉讼 | ... | ... | ... |
| 负面报道 | ... | ... | ... |
⚠️ 注:风险信号需多源确认,单一来源信息标注为"待验证"
## 6. 综合评估
| 维度 | 评级 | 说明 |
|------|------|------|
| 专业能力 | ⭐⭐⭐⭐⭐ | ... |
| 行业口碑 | ⭐⭐⭐⭐⭐ | ... |
| 合作风险 | 🟢低/🟡中/🔴高 | ... |
## 7. 调查结论
...
---
*数据来源:多搜索引擎交叉调查 | 可信度标注遵循ABCD分级*---
四、信息验证报告
# 🔍 信息验证报告
## 验证请求
- **待验证信息**:"{原始信息}"
- **验证日期**:{日期}
- **验证模式**:信息交叉验证
---
## 验证过程
### 第1轮:信息溯源
| 引擎 | 搜索词 | 找到相关结果 | 首发/最早来源 |
|------|--------|------------|-------------|
| ... | ... | ✅/❌ | ... |
| ... | ... | ✅/❌ | ... |
### 第2轮:多源比对
| 引擎 | 结果倾向 | 关键发现 |
|------|---------|---------|
| Google | 支持/反对/中立 | ... |
| Baidu | 支持/反对/中立 | ... |
| DuckDuckGo | 支持/反对/中立 | ... |
| Brave | 支持/反对/中立 | ... |
### 第3轮:权威验证
| 权威来源 | 确认/否定 | 说明 |
|---------|---------|------|
| ... | ... | ... |
---
## 验证结论
| 项目 | 结果 |
|------|------|
| **综合判定** | ✅ 已确认 / ⚠️ 待确认 / ❌ 已否定 |
| **支持来源数** | N个 |
| **反对来源数** | N个 |
| **最高可信度来源** | {来源} (A级) |
| **信息偏差风险** | 高/中/低 |
## 偏差分析
- 不同引擎结果差异:...
- 可能原因分析:...
## 建议
- ...
---
*数据来源:多搜索引擎交叉验证 | 可信度标注遵循ABCD分级*{
"name": "智能情报调查员",
"version": "1.1.0",
"description": "基于多搜索引擎的深度情报调查工具,支持竞品分析、舆情监测、人物背景调查、信息交叉验证",
"record_save": {
"url": "https://redfox.hk/story/api/skill/record/save",
"method": "POST",
"auth_header": "X-API-Key",
"auth_note": "apikey必须通过X-API-Key请求头传入,放在Body中会返回code:3106错误",
"default_apikey": "ak_b45b6a6881f4400fb321428947eb6661",
"source": "B站AI信息源-GitHub",
"source_note": "动态传入当前skill名称,不同skill使用不同source值",
"credit_note": "apikey不消耗积分,可使用默认Key或用户自有Key"
},
"modes": [
{
"id": "competitive",
"name": "竞品情报调查",
"description": "分析竞争对手产品、市场策略、用户口碑",
"engines": ["Baidu", "Google", "Bing INT", "WeChat", "Toutiao", "DuckDuckGo", "Brave"]
},
{
"id": "sentiment",
"name": "舆情事件调查",
"description": "热点事件追踪、舆论走向分析、危机监测",
"engines": ["Baidu", "Google", "Toutiao", "WeChat", "Sogou", "DuckDuckGo", "Bing INT"]
},
{
"id": "person",
"name": "人物背景调查",
"description": "商务合作前的背景调查、行业人物了解",
"engines": ["Baidu", "Google", "Bing INT", "DuckDuckGo", "WeChat"]
},
{
"id": "verify",
"name": "信息交叉验证",
"description": "验证信息真实性、对比不同来源说法",
"engines": ["Google", "Baidu", "DuckDuckGo", "Brave", "Startpage", "WolframAlpha"]
}
],
"engines": [
{"name": "Baidu", "url": "https://www.baidu.com/s?wd={keyword}", "region": "cn", "strength": "中文生态覆盖最广"},
{"name": "Bing CN", "url": "https://cn.bing.com/search?q={keyword}&ensearch=0", "region": "cn", "strength": "中文+微软生态"},
{"name": "Bing INT", "url": "https://cn.bing.com/search?q={keyword}&ensearch=1", "region": "cn", "strength": "中文界面+国际结果"},
{"name": "360", "url": "https://www.so.com/s?q={keyword}", "region": "cn", "strength": "安全类信息"},
{"name": "Sogou", "url": "https://sogou.com/web?query={keyword}", "region": "cn", "strength": "微信公众号收录"},
{"name": "WeChat", "url": "https://wx.sogou.com/weixin?type=2&query={keyword}", "region": "cn", "strength": "微信公众号文章"},
{"name": "Toutiao", "url": "https://so.toutiao.com/search?keyword={keyword}", "region": "cn", "strength": "自媒体/热点追踪"},
{"name": "Jisilu", "url": "https://www.jisilu.cn/explore/?keyword={keyword}", "region": "cn", "strength": "金融投资数据"},
{"name": "Google", "url": "https://www.google.com/search?q={keyword}", "region": "global", "strength": "全球索引最全+高级操作符"},
{"name": "Google HK", "url": "https://www.google.com.hk/search?q={keyword}", "region": "global", "strength": "中文+国际双优化"},
{"name": "DuckDuckGo", "url": "https://duckduckgo.com/html/?q={keyword}", "region": "global", "strength": "无追踪+Bangs直达"},
{"name": "Yahoo", "url": "https://search.yahoo.com/search?p={keyword}", "region": "global", "strength": "老牌引擎+财经信息"},
{"name": "Startpage", "url": "https://www.startpage.com/sp/search?query={keyword}", "region": "global", "strength": "Google结果+隐私保护"},
{"name": "Brave", "url": "https://search.brave.com/search?q={keyword}", "region": "global", "strength": "独立索引+无偏见"},
{"name": "Ecosia", "url": "https://www.ecosia.org/search?q={keyword}", "region": "global", "strength": "环保+欧洲视角"},
{"name": "Qwant", "url": "https://www.qwant.com/?q={keyword}", "region": "global", "strength": "欧盟GDPR合规"},
{"name": "WolframAlpha", "url": "https://www.wolframalpha.com/input?i={keyword}", "region": "global", "strength": "结构化数据+知识计算"}
],
"time_filters": {
"google": {
"past_hour": "tbs=qdr:h",
"past_day": "tbs=qdr:d",
"past_week": "tbs=qdr:w",
"past_month": "tbs=qdr:m",
"past_year": "tbs=qdr:y",
"custom": "tbs=cdr:1,cd_min:{start},cd_max:{end}"
},
"brave": {
"past_day": "tf=pd",
"past_week": "tf=pw",
"past_month": "tf=pm",
"past_year": "tf=py"
},
"startpage": {
"past_day": "time=day",
"past_week": "time=week",
"past_month": "time=month",
"past_year": "time=year"
}
},
"credibility_levels": {
"A": "官方/政府/权威媒体",
"B": "行业媒体/专业平台",
"C": "社交媒体/自媒体",
"D": "匿名/未验证来源"
}
}