目录

Podcast Hunter

📋 代码审查报告已生成,详见 docs/code_review_final_2026-09-06.md(架构重构 P0–P5 经 Codex 独立复核,无 CRITICAL,文档与守卫缺口已闭环),请在下次开发前优先查阅。 历史各轮与归档去向见审查入口 docs/CODE_REVIEW.md(报告按日期归档,一轮一个文件)。

中文 AI 情报台

自动追踪全球 AI 内容源,把博客、播客、视频和关键人物观点变成中文摘要与可问答知识库,并沉淀到 Obsidian / Markdown

Python 3.10+ License: MIT


功能特性

功能 说明
多源采集 RSS 博客/播客、YouTube 频道、关键人物观点(精选历史窗口,不保证最新)
字幕优先 YouTube 有字幕直接下载,无字幕才提取音频转录
双语 ASR 中文 → FunASR + 3D-Speaker(说话人分离),英文 → WhisperX large-v3
长音频 自动识别超长音频(>2h),WhisperX 按块处理,FunASR 内置 VAD 无限制
AI 摘要 转录后自动生成中文摘要,导出 Obsidian 知识库(Anthropic 协议)
主题标签 摘要后按受控词表(config/taxonomy.yaml)自动打 3-5 个主题标签,用于内容筛选、/api/v1/tags 与 Obsidian frontmatter
内容问答 Web UI 侧边聊天:对单篇内容问答,或跨内容综合问答(”这周有什么进展”),流式回答并附可点击的内容引用
智能翻译 全文中文翻译(OpenAI 兼容协议:硅基流动/DeepSeek 等),手动触发
远程 ASR GPU 转录运行在远程服务器,本地只需 SSH 隧道
定时调度 APScheduler 三条周期 lane(fetch 采集 / process 转录 / summarize 摘要)+ 每日清理孤儿音频
Web UI serve 启动后访问 http://localhost:8000/,一级导航为 今日 / 内容库 / 简报 / 来源 / 仪表盘(仅 admin)
REST API FastAPI,/docs 自带 Swagger UI

它为谁而做

用户 用它解决什么
AI 工程师 / 研究员 快速知道 OpenAI、Anthropic、DeepMind、Karpathy、论文讲解频道有什么技术变化,省下刷信息源的时间
AI 产品经理 / 创业者 跟踪模型发布、API 变化、竞品动态,用于路线判断与团队同步
内容创作者 / 博主 从英文长播客摘要里找选题、观点与嘉宾访谈素材
投资人 / 行研 / 咨询顾问 按公司、创始人、研究机构主题化跟踪产品发布,用于 memo 与行业简报
企业内部 AI 学习小组 私有部署的 AI 情报库,成员只读访问、共享同一份中文摘要

情报包

内容源按「服务谁、解决什么问题」分成 7 个情报包(config/source_packs.yaml,纯展示层分组,不入库),/sources/contents 页面均可按包筛选:

情报包 服务谁
AI 研究前沿 AI 工程师 / 研究员
AI 产品与开发者 AI 工程师 / 产品经理
AI 播客深度访谈 内容创作者 / 研究员 / 产品经理
科技商业与创投播客 创业者 / 投资人 / 行研咨询
中文科技与商业 中文 AI 从业者 / 企业学习小组
关键人物观点 投资人 / 产品经理 / 从业者(精选历史窗口,不保证最新)
其他来源 通用科技读者

架构概览

本地 Mac(podcast_hunter)
  │
  ├─ 采集  →  RSS / YouTube / Twitter
  ├─ 存储  →  SQLite + 本地文件
  │
  ├─ 转录  →  SSH 隧道 → 远程 ASR 服务(GPU 服务器)
  │                         ├─ 中文  →  FunASR + 3D-Speaker
  │                         └─ 英文  →  WhisperX large-v3
  │
  ├─ 摘要  →  LLM(Anthropic 协议)→ Obsidian 知识库(自动,stage→summarized)
  └─ 翻译  →  LLM(OpenAI 兼容协议)→ 成品 md 回填(手动触发,stage→completed)

ASR 服务地址http://62.234.59.148:8001(需 SSH 隧道,见下方配置)


快速开始

1. 安装依赖

git clone <repo-url>
cd podcast_hunter

# 核心环境(RSS 采集、API、数据库)
uv sync

# 开发/测试
uv sync --extra dev --extra test

# YouTube 下载(本地需要 yt-dlp + ffmpeg)
uv sync --extra media
brew install ffmpeg yt-dlp   # macOS

2. 环境变量

cp .env.example .env   # 没有就手动创建

.env 最少配置:

OPENAI_API_KEY=sk-...                 # 翻译必填(OpenAI 兼容协议)
OPENAI_BASE_URL=https://api.siliconflow.cn/v1
OPENAI_MODEL=deepseek-ai/DeepSeek-V4-Flash
PH_SUMMARY_TOKEN=...                  # 摘要必填(Anthropic 协议)
PH_OBSIDIAN_VAULT=/path/to/vault      # 摘要导出 Obsidian 时必填
# PH_WEB_TOKEN=...                    # 可选,设置后 Web/API 需带 x-api-token
# PH_TWITTER_SYNDICATION_BASE=...     # 可选,Twitter 走自建转发器时指向隧道地址(如 http://127.0.0.1:8443)
# PH_SYNDICATION_TOKEN=...            # 可选,转发器 Bearer token(见 docs/TWITTER_SYNDICATION.md)

3. 开启 ASR SSH 隧道

转录功能依赖远程 GPU 服务器,需要在本地保持隧道运行

ssh -p 6002 -N -f -L 8001:localhost:8001 user@62.234.59.148

验证是否通:

curl http://127.0.0.1:8001/health
# {"status":"ok","workers":1,...}

说明:隧道关闭后转录会跳过(报警告但不崩溃)。采集和字幕下载不依赖隧道。

4. 初始化并运行

# 建数据库表(首次必须,源配置在 fetch 时自动同步)
python main.py init

# 查看所有配置的源
python main.py list

# 抓取所有内容(采集 + 下载字幕/音频 + 转录,首次会同步 source 到 DB)
python main.py fetch

# 只抓取某一个源
python main.py fetch --source "硅谷101"

# 查看状态
python main.py status

# 启动 Web UI + REST API
#   Web UI:     http://localhost:8000/
#   API docs:   http://localhost:8000/docs
python main.py serve

# 清理孤儿音频(转录失败/中断遗留的 MP3)
python main.py cleanup --dry-run    # 预览
python main.py cleanup              # 真清

内容源

当前配置的源分四类,配置文件在 config/sources/

播客(podcasts.yaml)

播客 语言 说明
硅谷101 🇨🇳 中文 硅谷科技与 AI,FunASR 转录
十字路口 Crossing 🇨🇳 中文 AI 与科技深度访谈,FunASR 转录
Training Data 🇺🇸 英文 Sequoia Capital AI 播客
Dwarkesh Podcast 🇺🇸 英文 科技/历史深度访谈
Lex Fridman Podcast 🇺🇸 英文 AI/科技领袖访谈
Acquired 🇺🇸 英文 科技公司商业史
Google DeepMind: The Podcast 🇺🇸 英文 DeepMind 研究
No Priors 🇺🇸 英文 AI 创业与 VC
The TWIML AI Podcast 🇺🇸 英文 ML 从业者访谈

YouTube(youtube.yaml)

3Blue1Brown · Yannic Kilcher · Two Minute Papers · Andrej Karpathy · Lex Fridman

字幕策略:有字幕 → 直接下载(不转录);无字幕 → 提取音频 → 远程 ASR 转录。

博客(blogs.yaml)

OpenAI · Anthropic · Google AI · DeepMind · Hugging Face · AI Alignment Forum · Distill · Lil’Log

Twitter(twitter.yaml)

免费 syndication 通道(无需 API token):抓取 X 网页 widget 用的精选历史推文窗口,约 100 条/源。 生产经 VPS 转发器 + SSH 隧道访问(配置 PH_TWITTER_SYNDICATION_BASE + PH_SYNDICATION_TOKEN,见 docs/TWITTER_SYNDICATION.md)。⚠️ 窗口由 X 服务器端精选,非时间倒序、 不保证最新(活跃账号窗口滞后可达 1-2 年),定位是「AI 大佬高互动推文精选库」而非实时时间线。 已配置:Sam Altman · Dario Amodei · Yann LeCun · Andrew Ng · Andrej Karpathy · Elon Musk。


项目结构

podcast_hunter/
├── main.py                      # CLI 入口(argparse 分发到 src/cli/)
├── config/
│   ├── settings.yaml            # 系统配置(ASR 引擎、LLM 模型注册表、调度间隔等)
│   └── sources/                 # 内容源配置,按类型分文件
│       ├── blogs.yaml
│       ├── podcasts.yaml        # language: zh/en 决定 ASR 引擎路由
│       ├── youtube.yaml
│       └── twitter.yaml
├── src/
│   ├── cli/                     # CLI 子命令实现(fetch / status / transcribe / translate)
│   ├── collectors/              # 采集器(RSS、youtube/ 子包、Twitter)
│   ├── processors/              # 内容处理器(音频下载、HTML 清洗)
│   ├── transcription/           # 转录:remote_asr.py(推荐)+ local_whisperx.py / whisper_engine.py(备用)
│   ├── llm/                     # LLM 基础设施层:命名模型注册表 + provider 客户端(唯一管 SDK/key/重试/超时)
│   ├── translation/             # 全文翻译业务层(LLMTranslator,OpenAI 兼容协议,消费 src/llm)
│   ├── summarization/           # AI 摘要业务层(LLMSummarizer,Anthropic 协议,消费 src/llm)
│   ├── workflow/                # 工作流编排(功能归属见下表)
│   ├── scheduler/               # APScheduler 封装(scheduler.py)+ 周期作业定义(jobs.py)
│   ├── maintenance/             # 孤儿音频清理(cleanup.py)
│   ├── storage/                 # SQLAlchemy ORM(models_pkg/ 按表拆分)+ Repository(repositories/)+ 文件存储
│   ├── api/                     # FastAPI REST + Web UI
│   │   ├── routes/              # REST 路由 + web.py(Jinja2 模板路由)
│   │   └── templates/           # base + 7 个页面 + partials/
│   └── utils/                   # 工具函数
├── scripts/                     # 运维/验证脚本;一次性迁移归档在 scripts/migrations/
├── data/
│   ├── database/                # SQLite(WAL 模式)
│   └── output/
│       ├── audio/               # 下载的音频(转录成功后删除,遗留由 cleanup 清理)
│       ├── subtitles/           # YouTube 字幕
│       ├── transcripts/         # 转录文本
│       └── markdown/<源>/       # 成品文档(两层目录,单文件随阶段回填成长)
└── tests/                       # 923 个测试用例

输出目录设计

两套产物目录有意不同构,互链靠文件名 stem 同名,不靠目录对应:

  • 成品 md(两层)data/output/markdown/<源>/<date>_<title>_<id10>.mdid10 为内容稳定身份的前 10 位十六进制短码,防止同名不同内容互相覆盖;历史文件仍是旧的无短码命名,两者并存)。单文件随处理阶段”成长”——fetched 只有元信息+简介,transcribed 回填 ## 原文转录,手动翻译后回填 ## 中文翻译。渲染统一在 src/documents/document_writer.py,幂等全量重渲染,按源平铺方便程序回填。
  • Obsidian 摘要笔记(三层)<vault>/<subdir>/<类型组>/<源>/<同名 stem>.md,由 src/documents/obsidian_exporter.py 导出,按「类型组/源」分层方便人在 vault 里浏览。

功能归属(功能 ↔ 代码位置)

功能 代码归属
采集编排(多源调度) src/workflow/orchestrator.py,具体采集器在 src/collectors/
单条内容处理流程 src/workflow/pipeline.py(ContentPipeline,含语言路由)
周期消费:转录 lane / 摘要 lane src/workflow/pending_processor.py,作业注册在 src/scheduler/jobs.py
手动翻译 lane src/workflow/translation_runner.py(单 session 事务闭环)
成品 md 渲染回填 src/documents/document_writer.py
Obsidian 摘要导出 src/documents/obsidian_exporter.py
YouTube 字幕下载 / RSS↔YouTube 集数匹配 src/workflow/subtitle_downloader.py / src/workflow/youtube_episode_resolver.py
转录策略链(字幕优先 → ASR → 下载兜底) src/workflow/transcript_strategies.py
LLM 调用(模型注册表 / key / 重试 / 超时) src/llm/(registry + providers/)
翻译 / 摘要业务逻辑 src/translation/ / src/summarization/
处理阶段状态机(单一事实来源) src/storage/models_pkg/content.py(STAGE_ORDER)
处理时间线(细粒度过程记录) content_processing_attempts 表,Web 内容详情页可视化
孤儿音频清理 src/maintenance/cleanup.py
Web UI / REST API src/api/routes/web.py / src/api/routes/

ASR 引擎配置

config/settings.yaml 中选择引擎:

transcription:
  asr:
    engine: "remote-asr"          # 推荐:远程统一服务
    remote-asr:
      api_base: "http://127.0.0.1:8001"   # 通过 SSH 隧道访问
      language: null              # null=由播客源的 language 字段决定
      poll_interval: 15           # 轮询间隔(秒),长音频适当调大
      timeout_upload: 600         # 上传超时(秒)

播客源中的 language: zh/en 字段决定路由:

language: zh  →  FunASR + 3D-Speaker(中文转录 + 说话人分离)
language: en  →  WhisperX large-v3(英文转录 + 时间戳对齐)
language: null →  WhisperX 自动检测语言

Web UI

python main.py serve 启动后浏览器打开 http://localhost:8000/

当前运行时只支持一个 API 进程、一个应用副本、一个进程内 APScheduler。不要使用 uvicorn --workers >1WEB_CONCURRENCY>1、多个容器副本,或重叠滚动发布;在 P2 lease-based execution 完成前,这些部署会导致周期任务重复执行,且启动回收逻辑可能把另一个仍在运行进程的任务标记为失败。main.py serve 固定以 workers=1 启动,--workers >1 会在参数解析阶段直接报错拒绝;更底层的强制手段是应用启动(lifespan)时对数据库目录下的 .podcast_hunter.runtime.lock 抢占一把非阻塞的 OS 级 flock——无论是绕过 main.py serve 直接跑 uvicorn --workers N,还是先后启动两个独立的 serve 进程,抢不到锁的那个进程都会在启动时硬失败退出,而不会触碰数据库;副本数量本身仍无法在进程内可靠检测,部署时必须保证不重叠。

页面 路径 主要交互
今日 / 当前用户订阅源的「今日 AI 情报」:概览指标 + 今日值得看 + 来源更新侧栏 + 完整订阅信息流分页
内容库 /contents 按源 / 情报包 / 主题标签筛选 + 快速筛选 chip + 分页 + 详情
来源 /sources 按情报包浏览 + HTMX 一键启停;点进去能改 URL / 抓取间隔 / 优先级
仪表盘 /dashboard 仅 admin:统计概览 + 最近任务 + 最近内容
任务 /tasks 仅 admin:二级运维入口,”立即触发抓取” 按钮;有 active 时 5s 轮询;取消/重试
维护 /maintenance 仅 admin:二级运维入口,一键 dry-run 预览或真清孤儿音频

技术栈:FastAPI + Jinja2 + Tailwind CDN + HTMX,无独立前端构建,无 Node.js。

主题标签

摘要生成之后,系统会再调一次模型,按受控词表给每篇内容打 3-5 个主题标签,落在 Content.tags。 用受控词表(而不是让模型自由发挥)是为了避免「大模型 / LLM / 大语言模型」各算一个标签,把标签云打成上千个长尾。

看哪里/contents 页面的主题标签筛选(?topics= 可多选、AND 组合;与「源标签」?tags= 是两回事,后者筛的是源配置上的标签);GET /api/v1/tags 返回用过的标签及次数;Obsidian 笔记的 frontmatter 里也会带上。

怎么开config/settings.yamltagging: 段(默认已开启,词表随仓库提供)。

配置键 作用
tagging.enabled 总开关。开着但词表加载不了时,main.py 启动阶段就直接报错退出,不会静默跳过
tagging.taxonomy_path 受控词表路径,默认 config/taxonomy.yaml
tagging.model_ref 用哪个模型,默认 tag-default(见 models: 段,走自建网关,复用 OPENAI_*;可用 TAG_API_KEY/TAG_BASE_URL/TAG_MODEL 单独指定)
tagging.max_tags / min_tags 单篇标签数上限 / 期望下限
tagging.max_new_terms 单篇最多允许几个词表外新词(进候选区等人工归并)
tagging.batch_size / concurrency / daily_max_calls / daily_max_tokens 每轮自愈扫描条数、并发、每日调用/token 闸门(0 = 不限)

什么时候跑summarize-cycle 内联抽取;同一轮末尾还会扫一遍「有摘要没标签」的漏网补上,无需人工介入。

改词表config/taxonomy.yaml 直接编辑即可(groups 分组给人看,aliases 是别名 → 规范词)。想从库里的存量摘要重新归纳一版草稿,跑 uv run python scripts/build_taxonomy.py——它只写 config/taxonomy.draft.yaml,人工过目后再另存为正式词表(详见 用户手册 §9)。

内容问答(聊天)

Web UI 每个页面右下角有一个聊天入口,随 serve 一起提供,无需额外配置开关;它复用摘要用的那个模型(models: 段的 summarize-default,即 OPENAI_API_KEY / OPENAI_BASE_URL / OPENAI_MODEL 那套凭证),没配好这套凭证时聊天会返回错误提示。

两种问法:

  • 单篇问答:在某条内容的详情页发问,只用这一篇的原文回答(播客/视频用转录全文,文章/推文用正文,都没有时退回摘要)。
  • 全局问答:不指定内容时,先检索候选摘要再综合作答。候选来自两条互补通路——问句里的时间词(”今天”/“这周”/“本月”等,默认 7 天窗)和主题标签召回(问句里认出词表里的词,跨时间窗捞历史内容)。所以「历史上讲 Agent 的都有哪些」这类问题依赖上面的主题标签功能;词表缺失时聊天不会挂,只是退化成纯时间窗检索。

回答是流式输出(SSE)。答案里的 [#7231] 是内容 ID 引用,可点击跳转;追问时带 [#7231] 可以让模型深入讲这一条。

调度器

serve 启动时同时拉起 APScheduler(scheduler.enabled: false 可整体关闭,间隔都在 config/settings.yamlscheduler: 段):

调度器是进程内组件,不是分布式队列。同一数据库和输出目录同一时间只能有一个 serve 实例负责周期作业;如果需要发布新版本,请先停止旧进程,再启动新进程。

作业 触发 行为
periodic-fetch-cycle 默认每 3600s 全量启用源采集落库
process-cycle 默认每 600s 消费「已抓取未转录」:字幕 / 远程 ASR / 音频下载(唯一周期消费者
summarize-cycle 默认每 900s,需 summarization.enabled: true 转录后生成 AI 摘要 → 导出 Obsidian → stage→summarized
daily-cleanup 每天 03:00 清理 mtime 超期的孤儿音频

处理阶段状态机:initial → fetched → audio_downloaded → transcribed → summarized →(手动翻译)translated → completedsummarized 是当前产品的常态终点;翻译是成本敏感的可选增值,由 Web「翻译」按钮或 task_type='translate' 手动触发。

里程碑

里程碑 时间 内容
M1 — 核心链路(Phase 1-6) 2026-03 下旬 采集 → 存储 → 转录 → 翻译 → 成品 md 全链路 + CLI
M2 — 重构与批次 1-4 2026-04 ~ 05 代码重构(M1-M6)、调度器、孤儿音频清理、Web UI(Jinja2 + HTMX)
M3 — 全链路一致性整改 2026-06-02 统一状态机 + 成品文档回填 + 翻译闭环
M4 — 架构重构 + 摘要自动化 2026-06-03 LLM 基础设施收口(模型注册表)、TranscriptStrategy 链、AI 摘要 → Obsidian 导出
M5 — 项目体检修复 2026-06-10 四视角审查落地:安全收紧、SQLite 加固、假成功修复、summarized 阶段、文档对齐

待办与留置项统一维护在 docs/ROADMAP.md,历史细节见 docs/archive/。 内容阅读页近期修复记录 + 待排查事项见 docs/archive/CONTENT_READING_HANDOFF.md

开发

# 运行测试
uv run pytest tests/ --ignore=tests/e2e -q

# 真实链路测试(需网络)
PODCAST_HUNTER_RUN_REAL_DOWNLOAD=1 pytest tests/integration/ -v

# 代码检查(main.py / src / tests / scripts 全部纳入,门禁基线为零告警)
# 唯一 lint 工具是 flake8,配置在 .flake8(line-length 120),不是 ruff
uv run flake8 main.py src tests scripts

# 安装 pre-commit 钩子(black + isort + flake8,line-length 统一 120)
uv run pre-commit install
uv run pre-commit run --all-files   # 手动对全仓库跑一遍

# CI 三件套(平台无关脚本,详见 docs/CI.md)
bash scripts/ci/lint.sh   # lint
bash scripts/ci/test.sh   # 单元 + 集成,带覆盖率门禁(--cov-fail-under=75)
bash scripts/ci/e2e.sh    # e2e(跑在 nightly,非每次 PR)

工程门禁与配置单一来源的说明见 docs/CI.md

License

MIT License

关于
40.0 MB
邀请码
    Gitlink(确实开源)
  • 加入我们
  • 官网邮箱:gitlink@ccf.org.cn
  • QQ群
  • QQ群
  • 公众号
  • 公众号

版权所有:中国计算机学会技术支持:开源发展技术委员会
京ICP备13000930号-9 京公网安备 11010802047560号