Cyxj youtube topics
Skill chenyuxiaojin/xiaochen-skills/plugins/cyxj-youtube-topics/skills/cyxj-youtube-topics
17 Claude Code plugins / 19 skills for video, publishing, Obsidian, GEO and real content workflows.
npx -y skills add chenyuxiaojin/xiaochen-skills --skill cyxj-youtube-topicsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 5 stars5 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
YouTube 选题发现 + 判断。搜索 "Claude Code" 相关最近 48 小时新视频, 去重、按话题聚类、做硬信号 + 字幕内容分析,输出带 verdict(值得做/观望/跟风/跳过)+ 理由 + 差异化切口建议的选题报告。写入 Obsidian 选题库。 触发方式:「YouTube 选题」「找 YouTube 选题」「YouTube 最近有什么」「帮我找找最近的新选题」「跑一下选题发现」「有什么新视频」 边界:只做 YouTube 新视频的选题发现与判断;泛选题、短视频 IP 人群选题、文案式出选题不归本 skill。
SKILL.md
15.1 KB, as published. Nobody here has run it
youtube-topic-discovery:YouTube 选题发现 + 判断
⚠️ 执行约束(严格遵守)
本 skill 在一次调用内只允许执行一遍 7 步流程。具体禁止:
- 不要在 verdict 输出后又调用
youtube_search.py拉新视频 - 不要二次调用
write_topics.py - 输出
CYXJ_RESULT_FILE=<路径>后立即终止,不要"再补全一下"或"再确认一下" - 不要模仿 launcher 的
===== 启动/结束/失败 =====日志格式 echo 任何文字
如果你在执行过程中产生"是不是该再扫一次"的念头,停下——这是错的。流水线的完整性由 launcher 保证,不由你保证。
🔁 断点续传机制(每步执行前必读)
上一次跑可能在中间某步失败(budget 烧光 / 网络挂 / Ctrl+C),失败时 /tmp/yt_*.json 中间产物可能还在。每步开始前先检查对应输出文件,存在且新鲜则跳过该步:
# 全流程都会用到 $SKILL_DIR,先定义(CLAUDE_PLUGIN_ROOT 由 Claude Code 注入)
SKILL_DIR="${CLAUDE_PLUGIN_ROOT}/skills/cyxj-youtube-topics"
# 模板:每一步开头先这样判断(stat -f 是 BSD/macOS 写法,Linux 兜底用 stat -c)
if [ -f /tmp/yt_videos.json ] && [ $(($(date +%s) - $(stat -f %m /tmp/yt_videos.json 2>/dev/null || stat -c %Y /tmp/yt_videos.json))) -lt 21600 ]; then
echo "复用现有 /tmp/yt_videos.json(6h 内)"
else
python3 "$SKILL_DIR/youtube_search.py" > /tmp/yt_videos.json
fi
6h 阈值:超过 6h 的中间产物视为过期(48h lookback 窗口已显著移位),重新跑。
LLM 步骤(第 3 步聚类 / 第 5 步 verdict):执行前先 ls -la /tmp/yt_clusters.json /tmp/yt_final.json,存在且新鲜则跳过判断动作,直接读文件进入下一步。
第 6 步 write_topics.py 不做断点续传——一旦写盘就更新了 .seen_video_ids.json、话题索引、判断日志,没有"重复写入"概念。如果第 6 步已经跑过(看 ${CYXJ_TOPIC_DIR} 下有今天的 YYYY-MM-DD HH-MM YouTube选题总览.md),直接终止流程并报告"今天已经跑过了"。
角色
你是一个选题判断助手。任务不是把视频摆给用户看(那只是过滤器),而是带理由地告诉用户哪些话题值得做、哪些是跟风、哪些该跳过。理由比结论重要——好的理由能让用户反驳,反驳就是用户在思考选题。
前置准备
首次使用前配置以下环境变量(一次配置永久生效):
-
YouTube Data API v3 Key(必需,可配多个轮询)
- 在 https://console.cloud.google.com/apis/credentials 创建 key 并启用 YouTube Data API v3
- 按优先级配置任选其一:
export YOUTUBE_API_KEY=你的key- 在
${SKILL_DIR}/.env写入YOUTUBE_API_KEY=你的key - 在
~/.config/cyxj/.env写入YOUTUBE_API_KEY=你的key
- 多 key 轮询:单日 quota 10000 单位经常用爆,可加备用 key —— 在同处再写
YOUTUBE_API_KEY_2=...、YOUTUBE_API_KEY_3=...。脚本 403 quotaExceeded 时自动切下一个 key 重试。⚠️ 备用 key 必须来自不同的 Google Cloud 项目才有独立配额,同项目里加几个 key 也是同一份 quota。变量名大小写不敏感、_2和2都认。
-
Obsidian 选题库目录(必需)
export CYXJ_TOPIC_DIR="$HOME/obsidian/灵感库/选题库"
-
用户个人档案(可选,但强烈建议)
export CYXJ_USER_PROFILE="$HOME/obsidian/.../个人档案.md"- 内容应包含:身份定位、内容聚焦方向、目标受众、不做什么、代表作品
- 有这个文件,判断层能给"差异化切口"建议;没有时降级为客观判断
-
Apify API Token(必需,字幕抓取主路径)
- 注册 apify.com,Settings → API & Integrations → Personal API Token
- 主路径 Actor:
scrape-creators/best-youtube-transcripts-scraper(脚本直接按 Actor ID 调用,无需 bookmark) - 按优先级配置任选其一:
export APIFY_API_TOKEN=你的token- 在
${SKILL_DIR}/.env写入APIFY_API_TOKEN=你的token - 在
~/.config/cyxj/.env写入APIFY_API_TOKEN=你的token
- Free plan 每月 $5 credit,scrape-creators 约 $0.001/条,每月 600 视频约 $0.6,远在 Free 额度内
-
Supadata API Key(可选,fallback 兜底)
- 注册 supadata.ai,dashboard 拷贝 API key
- 配置:同 Apify,变量名
SUPADATA_API_KEY - Free tier 每月 100 credits,应急 fallback 够用
- 不配置也能跑,只是主路径挂时没兜底(Supadata 是独立服务商、独立 IP 池,与 Apify 不共享额度)
-
Python 依赖:
pip install -r requirements.txt- 必需:
requests - 不再需要
youtube-transcript-api(主路径已换 Apify 代理,不走 YouTube 内部接口)
- 必需:
高级环境变量(可选,从脚本实际行为归纳)
CYXJ_STATE_DIR:覆盖状态目录(话题索引 / 创作者索引 / 判断日志 /.seen_video_ids.json的存放处;默认~/Library/Application Support/cyxj-youtube-topics/state,为避 iCloud 文件锁不放同步目录)CYXJ_TRUSTED_BACKEND:信任频道直查后端,youtube_api(默认)或apify(测试用,不烧 YouTube quota,且会跳过关键词召回)CYXJ_LOOKBACK_HOURS:召回回看窗口小时数,默认 48,取值范围 1–168CYXJ_PHASE:两段式 cron 专用;=1时write_topics.py硬锁拒绝写盘(防廉价模型越界),=2或不设则放行
流程
流程开头先定义 skill 目录(后续所有命令依赖它;CLAUDE_PLUGIN_ROOT 由 Claude Code 注入):
SKILL_DIR="${CLAUDE_PLUGIN_ROOT}/skills/cyxj-youtube-topics"
第一步:运行搜索脚本
python3 "$SKILL_DIR/youtube_search.py" > /tmp/yt_videos.json
输出 JSON 数组,每元素含 video_id / title / url / channel / description / relative_time / view_count_formatted / duration_formatted。
空数组([])→ 告诉用户"最近 48 小时没有新内容",结束。
第 1.5 步:跑中文区参考(默认开启)
python3 "$SKILL_DIR/chinese_reference.py" > /tmp/yt_zh_reference.json
输出最近 48 小时中文区相关视频列表(top 15,按播放量降序)。每元素含 video_id / title / url / channel / description / view_count / view_count_formatted / relative_time。
用途:选题前查参考——看中文 up 主有没有做过同款,决定做时点进去参考人家怎么做。
零结果可能出现,不是 bug——表示中文区没人发,整个章节会跳过不渲染。
关闭:export CYXJ_ENABLE_ZH_REFERENCE=0
第二步:读取话题索引与个人档案
话题索引不在 $CYXJ_TOPIC_DIR——它是机器状态,已迁到本地状态目录 STATE_DIR(迁移原因:iCloud 同步目录的文件锁会导致写盘死锁)。STATE_DIR 规则:CYXJ_STATE_DIR 覆盖 > 平台默认 ~/Library/Application Support/cyxj-youtube-topics/state。路径统一通过 paths.py 获取,不要手拼:
STATE_DIR=$(cd "$SKILL_DIR" && python3 -c "from paths import get_state_dir; print(get_state_dir())")
cat "$STATE_DIR/话题索引.json"
话题索引每条 topic 现在包含:
id、name、aliases、status(新话题/升温中/饱和/已沉寂)first_seen、first_video、total_videos、appearances、last_updatedtop_3_videos:历史头部视频(title/channel/view_count/video_id)——做聚类匹配时最可靠的"话题指纹"signals:上次跑的硬信号快照(saturation/age_days/momentum/head_concentration)last_judgment:上次 verdict(label/reason/angle/signals_used/timestamp)
匹配时优先比对 top_3_videos 的标题,而不是只看 name 和 aliases。
第三步:LLM 聚类 + 话题匹配(同时归类中文区视频)
两件事同一次 LLM 调用搞定:
A. 英文区聚类:对 /tmp/yt_videos.json 里的每个视频,按标题和描述的语义分组。每组起一个简洁中文话题名。对每个聚类组,跟话题索引逐条比对(看 name、aliases、top_3_videos 标题):
- 语义匹配 →
is_new: false+existing_topic_id - 未匹配 →
is_new: true
B. 中文区话题归类:读 /tmp/yt_zh_reference.json(中文区视频列表),按标题和描述做轻量话题归类。每组起一个简洁中文话题名(如"Claude Code 教程"、"Cursor 对比"、"MCP 实战")。归不进任何话题的零散视频归到 "其他" 组。
中文区归类的边界:
- 不要和英文话题做匹配——它们是两套独立的归类
- 不要判断 verdict(值得做/跟风/跳过)、不要判断饱和度
- 不要写话题索引——中文话题只活在当前报告里
把两边结果合并写入 /tmp/yt_clusters.json:
{
"clusters": [
{
"topic": "中文话题名",
"is_new": true,
"videos": [{video_id, title, url, channel, channel_id, source, ...}]
},
{
"topic": "中文话题名",
"is_new": false,
"existing_topic_id": "...",
"videos": [...]
}
],
"zh_topics": [
{
"topic": "Claude Code 教程",
"videos": [{video_id, title, url, channel, view_count_formatted, relative_time}, ...]
},
{
"topic": "其他",
"videos": [...]
}
]
}
重要:clusters.videos 必须原样透传 video 对象的所有字段,包括 channel_id、source、view_count_formatted 等。这些字段被下游的创作者索引(auto-promotion 机制)依赖,丢失会导致博主信任名单失效。
如果 /tmp/yt_zh_reference.json 是空数组(中文区零结果),zh_topics 也输出空数组 []。
第四步:跑 topic_judge 做硬信号 + 粗筛 + 字幕
python3 "$SKILL_DIR/topic_judge.py" /tmp/yt_clusters.json > /tmp/yt_enriched.json
脚本读取新结构 {clusters, zh_topics},只对 clusters 做 enrichment,zh_topics 原样透传到输出。每个 cluster 加:
signals:saturation / age_days / momentum / this_run_count / total_videos / head_concentration / top_view_counttriage:{status: "pass" | "skip", reason}- skip:话题 ≥14 天前首发且本期 ≤1 新增,或饱和(≥10 视频)且本期头部 <300 播放
subtitles:{video_id: 前180秒纯文本 or null}- 抓字幕的条件(两层筛选):
- triage=pass(未被饱和/沉寂粗筛砍)
- 精筛命中其中之一:
- 全新话题(is_new=True)→ 抓本期全部视频
- 已知 + 升温中 + 头部播放 ≥1 万 → 抓 top 3
- 已知 + 饱和但头部 ≥1 千 → 抓 top 3(救援边界话题)
- 其他(饱和+头部低 < 1 千)→ 不抓,LLM 降级用标题+描述判断
- 理由:字幕对"跟风/跳过"的判断带不来增量,只对"可能值得做"的话题有价值
- 主路径 Apify
scrape-creators/best-youtube-transcripts-scraper(Apify IP 池,不污染本地 IP,约 $0.001/条) - 失败 fallback Supadata(独立服务商、独立 IP 池,每月 100 credits 免费)
- 抓字幕的条件(两层筛选):
第五步:LLM 生成 verdict
/tmp/yt_enriched.json 是 {clusters, zh_topics} 结构。verdict 判断只针对 clusters 里 triage=pass 的话题——zh_topics 不参与判断(中文区只是参考清单,由 write_topics.py 直接渲染)。
对每个 triage=pass 的话题,结合以下输入做综合判断:
- 话题名、硬信号、本期视频标题和描述
- top 3 视频的字幕(可能为 null——降级用标题+描述)
- 话题索引里的
last_judgment(上次怎么判断的) - 个人档案(如果可用)——用来给"差异化切口"建议
输出 JSON 对象(写入话题的 last_judgment 字段):
{
"label": "值得做|观望|跟风|跳过",
"reason": "<= 50 字 — 为什么这个 label",
"angle": "<= 80 字差异化切口(仅 label=值得做 时填)",
"signals_used": ["饱和", "角度同质化", "中文空位", ...]
}
对 triage=skip 的话题,不要调 LLM——直接在输出里保留 triage 字段,write_topics 会自动把这些归入"跳过"区。
把每个话题加上 last_judgment 后写入 /tmp/yt_final.json,保持 {clusters, zh_topics} 结构——zh_topics 原样透传,clusters 每条多了 last_judgment。
判断原则:
- 判断是建议不是决定。理由要具体(带具体的信号名、数字、空位描述),不要模糊。
- "值得做"要有 angle。没想到好切口的话题,宁可标"观望"。
- "跟风"用在"饱和 + 角度同质化"的话题——硬规则粗筛不会砍这些,但 LLM 判断会。
- "跳过"用在"没空位也没差异化"的话题。
第六步:写入 Obsidian
python3 "$SKILL_DIR/write_topics.py" /tmp/yt_final.json
write_topics.py 会:
- 生成每日总览
YYYY-MM-DD HH-MM YouTube选题总览.md,按 verdict 四分区(💎值得做 / 👀观望 / 🔁跟风 / 📋跳过) - 用 Obsidian 原生 Callouts 渲染:值得做=
[!success]+绿色 / 观望=[!info]+蓝色 / 跟风=[!warning]-橙色折叠 / 跳过=[!failure]-红色折叠 - 每个话题的 callout 内嵌一个
> > [!example]-折叠视频列表(点开就能看链接) - 已知话题额外嵌一个
[!quote]首发追溯块 - frontmatter 用 4 个独立 Number 字段(verdict_worth_doing / verdict_watching / verdict_follow / verdict_skip),便于 Bases 视图筛选
- 更新话题索引的
top_3_videos、signals、last_judgment - 更新创作者索引
- 更新
判断日志.jsonl(每行一条判断快照,按 (timestamp, topic_id) 去重合并后重写,两周后回看准不准) - 最后才更新
.seen_video_ids.json——总览写入成功后才标"已见",中途失败下次仍能捞回
最后清理临时文件:
rm /tmp/yt_videos.json /tmp/yt_zh_reference.json /tmp/yt_clusters.json /tmp/yt_enriched.json /tmp/yt_final.json
第七步:回复用户
分区呈现结果:
找到 N 个视频,X 个新话题、Y 个已知话题有更新。
## 💎 值得做(N 个)
1. **话题名** — 理由 + 切口
## 👀 观望(N 个)
2. **话题名** — 理由
## 🔁 跟风 / 📋 跳过(合并 N 个)
文件:YYYY-MM-DD HH-MM YouTube选题总览.md
重点只突出"值得做"。观望简短列出,跟风/跳过合并一行带过。
回复末尾带一行交接(按用户下一步意图):深研某个选题 → cyxj-notebook-research;看外部博主怎么讲、找差异化切口 → cyxj-yt-creator;选题已定、想诊断这条怎么做 → cyxj-content。