Python · AI Workflow · Feishu Integration

YouTube Learning Skill

YouTube 自动学习工作流:RSS 监控 → 飞书通知 → Gemini 生成学习笔记和知识图卡 → 同步飞书文档

Python 3.10+ Gemini API Feishu SDK YouTube Data API v3 lark-cli yt-dlp WebSocket ThreadPoolExecutor

Background

从被动刷视频到主动学习

YouTube 上有大量优质技术内容,但看视频学习效率低:没有笔记、难以回顾、知识无法沉淀。手动做笔记耗时且容易遗漏关键信息,长视频尤其痛苦。这个项目用 AI 自动分析视频内容,生成结构化中文学习笔记和可视化知识图卡,让学习从被动观看变成主动获取。

✕ Before
  • 手动刷 YouTube 订阅,容易错过更新
  • 看完视频就忘,没有笔记沉淀
  • 长视频做笔记耗时巨大,容易遗漏
  • 知识碎片化,无法系统回顾
  • 技术概念难以可视化理解
✓ After
  • 自动监控订阅频道,飞书推送新视频通知
  • 一键生成结构化中文学习笔记
  • AI 深度分析视频,不遗漏任何知识点
  • 笔记自动同步飞书文档,随时回顾
  • 知识图卡可视化核心概念和关系
全栈开发 · 从架构设计到部署运维
Challenge 01
两步 Gemini 调用解决长视频超时
! Problem
  • 长视频超时 — 1 小时以上的视频,单次 Gemini API 调用极易触发 HTTP 超时,Streaming 模式也难以在一次调用中同时输出笔记和图卡 Prompt
  • 输出截断 — 一次性要求生成笔记 + 图卡 Prompt,输出 token 过长导致内容被截断,图卡部分丢失
  • 重复传视频 — 如果图卡生成失败需要重试,重新传视频给 Gemini 既慢又浪费 Token
Solution
  • 拆分为两步调用 — Step 1 用 generate_content_stream() Streaming 模式分析视频生成笔记;Step 2 用笔记文本生成图卡 Prompt,无需重新传视频
  • 10 分钟超时配置 — 通过 HttpOptions(timeout=600000) 给 Gemini 客户端配置 10 分钟超时,覆盖长视频分析场景
  • JSON 结构化输出 — 图卡 Prompt 以 JSON 格式输出,支持 1-5 张图卡弹性数量,AI 自主判断内容复杂度决定图卡数量
scripts/gemini_notes.py Python
def generate_notes_and_card_prompts(
    youtube_url: str, max_cards: int = 5
) -> tuple[str, dict | None]:
    """两步生成:先笔记(需要视频),再图卡 prompt(纯文本)"""
    client = init_client()

    # Step 1: Streaming 模式分析视频生成笔记
    # 传入 YouTube URL,Gemini 原生支持视频输入
    notes_markdown = _generate_notes(client, youtube_url)

    # Step 2: 根据笔记生成图卡 prompt(不需要视频)
    # 纯文本输入,速度快且稳定
    card_prompts = _generate_card_prompts(client, notes_markdown, max_cards)

    return notes_markdown, card_prompts

# Step 1: Streaming 模式避免长视频超时
for chunk in client.models.generate_content_stream(
    model=MODEL,
    contents=[types.Content(role="user", parts=[
        types.Part(file_data=types.FileData(
            file_uri=youtube_url, mime_type="video/mp4"  # Gemini 原生 YouTube 支持
        )),
        types.Part(text=prompt)
    ])],
    config=types.GenerateContentConfig(temperature=0.7),
):
    chunks.append(chunk.candidates[0].content.parts[0].text)
长视频(1h+)笔记生成成功率从约 60% 提升到 95%+
单次调用超时截断 → 两步 Streaming 稳定输出,图卡生成无需重传视频
Challenge 02
并行知识图卡生成与自动重试
! Problem
  • 串行生成太慢 — 5 张图卡串行请求 Gemini,每张耗时 15-30 秒,总耗时 75-150 秒
  • 偶发超时失败 — 图片生成 API 不稳定,单张图卡可能因超时或 429 错误失败,导致整批图卡不完整
  • 质量一致性 — 多张图卡需要统一的设计风格(配色、排版、字体),随机生成容易风格割裂
Solution
  • ThreadPoolExecutor 并行max_workers=len(cards) 所有图卡同时请求,总耗时等于最慢的一张
  • 自动重试机制 — 每张图卡最多重试 2 次,重试间隔 3 秒,应对偶发超时和 429 错误
  • 统一设计风格 Prompt — 内置「温暖学术人文主义」风格前缀(森林绿主色 + 橙黄强调 + 暖米白背景),确保所有图卡视觉一致
scripts/gemini_cards.py Python
def generate_cards_from_prompts(card_prompts: dict, output_dir: str) -> list[str]:
    """从图卡 prompt 并行生成图片"""
    from concurrent.futures import ThreadPoolExecutor, as_completed

    cards = card_prompts.get("cards", [])
    results: dict[int, str | None] = {}

    # 所有图卡并行生成,max_workers = 图卡数量
    with ThreadPoolExecutor(max_workers=len(cards)) as executor:
        futures = {
            executor.submit(_gen_one, i, card): i
            for i, card in enumerate(cards, 1)
        }
        for future in as_completed(futures):
            idx, path = future.result()
            results[idx] = path

    # 按顺序返回成功的图片路径
    return [results[i] for i in sorted(results) if results[i]]

def generate_image_from_prompt(client, prompt, output_path, filename, max_retries=2):
    """单张图卡生成,支持自动重试"""
    for attempt in range(max_retries + 1):
        try:
            if attempt > 0:
                time.sleep(3)  # 重试间隔
            response = client.models.generate_content(
                model="gemini-3-pro-image-preview",
                contents=[IMAGE_GEN_PREFIX + prompt],  # 统一风格前缀
                config=types.GenerateContentConfig(
                    response_modalities=["image", "text"],
                    image_config=types.ImageConfig(aspect_ratio="16:9", image_size="2K")
                )
            )
5 张图卡生成耗时从 75-150 秒降至 15-30 秒
串行逐张生成 → 全部并行,总耗时等于最慢一张
Challenge 03
飞书交互卡片回调与流水线编排
! Problem
  • 回调阻塞 — 学习流水线耗时 5+ 分钟(视频分析 + 图卡生成 + 文档同步),飞书回调要求快速响应,不能在回调中同步执行
  • 状态追踪 — 用户点击「开始学习」后需要实时反馈:处理中 → 已完成 / 失败,三种状态需要平滑切换
  • 重复点击 — 用户可能重复点击「开始学习」按钮,导致同一视频被多次处理
Solution
  • 后台线程执行 — 回调中用 threading.Thread(daemon=True) 启动后台线程执行流水线,回调立即返回「处理中」卡片
  • 三态卡片更新 — 点击 → 返回橙色「正在学习中」卡片;完成 → 通过 interactive/v1/card/update API 更新为绿色「已完成」;失败 → 更新为红色「处理失败」
  • 防重入集合processing_tasks: set[str] 跟踪正在处理的视频 URL,重复点击直接返回「正在处理中」提示
scripts/callback_server.py Python
# 防重复处理
processing_tasks: set[str] = set()

def handle_card_action(data: P2CardActionTrigger) -> P2CardActionTriggerResponse:
    action_value = action.get("value", {})

    if action_value.get("action") == "start_learning":
        video_url = action_value.get("video_url")

        # 防重入:同一视频不重复处理
        if video_url in processing_tasks:
            resp.toast = CallBackToast(type="warning", content="该视频正在处理中...")
            return resp

        processing_tasks.add(video_url)

        # 后台线程执行流水线,回调立即返回
        thread = threading.Thread(
            target=process_video,
            args=(video_url, video_title, callback_token, chat_id),
            daemon=True
        )
        thread.start()

        # 立即返回「处理中」卡片
        card = CallBackCard(type="raw", data=generate_processing_card(video_title))
        resp.card = card
        return resp

def process_video(video_url, video_title, callback_token, chat_id):
    try:
        # Step 1: Gemini 生成笔记
        notes_markdown, card_prompts = generate_notes_and_card_prompts(video_url)
        # Step 2: 并行生成图卡
        image_paths = generate_cards_from_prompts(card_prompts, str(output_dir))
        # Step 3: 同步飞书文档
        doc_url = sync_to_feishu(notes_path, video_title, video_url, image_paths)
        # 更新卡片为完成状态
        update_card_via_api(callback_token, generate_completed_card(...))
    except Exception as e:
        update_card_via_api(callback_token, generate_error_card(video_title, str(e)))
    finally:
        processing_tasks.discard(video_url)  # 清理防重入标记
用户点击后 200ms 内收到反馈,5 分钟后自动收到文档链接
同步阻塞等待 → 异步线程 + 三态卡片实时更新
Challenge 04
用户画像驱动的个性化 AI 笔记
! Problem
  • 千人一面的笔记 — 同一个视频,产品经理和资深工程师需要的笔记深度和角度完全不同,通用 Prompt 生成的内容无法满足个性化需求
  • Insight 空泛 — 没有用户背景信息时,AI 生成的「启发」和「收获」往往是大话套话,如「这对行业有深远影响」
  • 术语门槛 — 技术视频中的专业术语,不同背景的人需要不同程度的解释
Solution
  • 双模板系统user_profile.md 定义用户背景(职业、技术水平、学习目标),output_format.md 定义笔记结构(总结 → Insight → 术语表 → 详细内容)
  • Prompt 注入 — 两个模板文件内容直接注入 Gemini Prompt,AI 根据用户背景调整 Insight 角度和术语解释深度
  • 结构化输出约束 — Insight 部分强制要求「结合用户背景」,术语表用表格形式,一句话解释清楚,避免过度简化或过度学术化
scripts/gemini_notes.py Python
def load_prompts() -> tuple[str, str]:
    """读取用户背景和输出格式配置"""
    user_profile_path = PROJECT_DIR / "references" / "user_profile.md"
    output_format_path = PROJECT_DIR / "references" / "output_format.md"

    user_profile = user_profile_path.read_text(encoding='utf-8') if user_profile_path.exists() else ""
    output_format = output_format_path.read_text(encoding='utf-8') if output_format_path.exists() else ""
    return user_profile, output_format

def _generate_notes(client, youtube_url) -> str:
    user_profile, output_format = load_prompts()

    prompt = f"""你是一个专业的视频学习助手。请仔细观看这个 YouTube 视频,生成学习笔记。

## 视频信息
链接: {youtube_url}

## 用户背景
{user_profile}

## 输出格式要求
{output_format}

请严格按照输出格式要求生成学习笔记。注意:
3. Insight 部分必须结合用户背景来写
4. 详细内容部分要保留所有有价值的信息
6. 链接字段必须使用上面提供的实际 YouTube 链接
"""
笔记 Insight 从通用套话变为针对用户背景的具体可操作建议
千人一面 → 用户画像驱动,产品经理和工程师看到不同的 Insight 角度
Challenge 05
YouTube OAuth + 订阅同步自动化
! Problem
  • OAuth 授权流程复杂 — Google OAuth 2.0 需要打开浏览器授权、本地服务器接收回调、用授权码换取 Token,流程繁琐易出错
  • Token 过期 — Access Token 有效期短,需要自动刷新;Refresh Token 丢失后需要重新授权
  • 频道管理灵活度 — 既要支持 OAuth 自动同步 YouTube 订阅列表,又要支持手动添加非订阅频道
Solution
  • 本地 HTTP 服务器接收回调HTTPServer(('localhost', 8888)) 启动临时服务器,自动打开浏览器完成 OAuth 授权,用户无需手动复制授权码
  • 自动 Token 刷新rss_monitor.py 每次检查时自动用 refresh_token 刷新 access_token,Token 持久化到 data/youtube_tokens.json
  • 双模式频道管理cmd_sync() 从 YouTube 订阅自动同步,cmd_add() 手动添加频道,统一存储在 data/channels.jsonsource 字段标记来源
scripts/youtube_oauth.py Python
def get_oauth_tokens(client_id: str, client_secret: str) -> dict:
    """执行 OAuth 流程:本地服务器接收回调"""
    auth_url = f"{GOOGLE_AUTH_URL}?" + urlencode({
        "client_id": client_id, "redirect_uri": REDIRECT_URI,
        "response_type": "code", "scope": " ".join(SCOPES),
        "access_type": "offline", "prompt": "consent"
    })

    # 启动本地服务器接收回调
    server = HTTPServer(('localhost', 8888), OAuthCallbackHandler)
    server_thread = threading.Thread(target=server.handle_request)
    server_thread.start()
    webbrowser.open(auth_url)  # 自动打开浏览器

    server_thread.join(timeout=120)
    server.server_close()

    # 用授权码换取 Token(含 refresh_token)
    resp = httpx.post(GOOGLE_TOKEN_URL, data={
        "client_id": client_id, "client_secret": client_secret,
        "code": OAuthCallbackHandler.auth_code,
        "grant_type": "authorization_code", "redirect_uri": REDIRECT_URI
    })
    return resp.json()

# RSS Monitor 自动刷新 Token
def _get_youtube_access_token() -> str | None:
    tokens = json.loads(tokens_file.read_text())
    resp = httpx.post("https://oauth2.googleapis.com/token", data={
        "client_id": client_id, "client_secret": client_secret,
        "refresh_token": tokens["refresh_token"],
        "grant_type": "refresh_token"
    })
    return resp.json().get("access_token")
OAuth 授权全流程自动化,用户只需点击浏览器授权按钮
手动复制授权码 → 自动本地回调 + Token 持久化 + 自动刷新

Architecture

How it all fits together

graph TD
subgraph S1["定时任务"]
A["YouTube Data API v3"] -->|"playlistItems"| B["rss_monitor.py"]
B -->|"lark-cli"| C["飞书通知卡片"]
end
subgraph S2["用户交互"]
C -->|"点击开始学习"| D["Callback Server"]
end
subgraph S3["学习流水线"]
D -->|"Step 1"| E["gemini_notes.py"]
E -->|"Streaming"| F["Gemini 3.1 Pro"]
F -->|"notes.md"| G["笔记 + 图卡 Prompt"]
G -->|"Step 2"| H["gemini_cards.py"]
H -->|"ThreadPoolExecutor"| I["Gemini 3 Pro Image"]
I -->|"2K 图片"| J["知识图卡"]
G -->|"Step 3"| K["feishu_sync.py"]
J --> K
K -->|"lark-cli"| L["飞书云文档"]
end
subgraph S4["配置与数据"]
M["user_profile.md"] -->|"用户画像"| E
N["output_format.md"] -->|"输出格式"| E
O["channels.json"] -->|"频道列表"| B
P["youtube_tokens.json"] -->|"OAuth Token"| B
end
style A fill:#ff6b6b,color:#fff
style C fill:#3b82f6,color:#fff
style F fill:#6c63ff,color:#fff
style I fill:#6c63ff,color:#fff
style L fill:#10b981,color:#fff
        
youtube-learning-skill/ ├── SKILL.md # Claude Skills 规范入口 ├── .env.example # 环境变量模板 ├── requirements.txt # Python 依赖 ├── references/ │ ├── user_profile.md # 用户背景(个性化笔记) │ └── output_format.md # 笔记格式模板 ├── scripts/ │ ├── youtube_oauth.py # OAuth 授权 + 订阅管理 │ ├── rss_monitor.py # 视频监控 + 飞书通知 │ ├── callback_server.py # 飞书回调 + 流水线编排 │ ├── gemini_notes.py # 两步笔记生成 │ ├── gemini_cards.py # 并行图卡生成 │ └── feishu_sync.py # 飞书文档同步 └── data/ # 运行时数据(自动生成) ├── channels.json # 频道列表 + 去重 ├── youtube_tokens.json # OAuth Token └── outputs/ # 笔记 + 图卡输出

Results

What we shipped

3
运行组件
RSS Monitor + Callback Server + OAuth
2-Step
Gemini 调用策略
单次超时截断 → 两步 Streaming
5x
图卡生成加速
串行 150s → 并行 30s
2K
图卡分辨率
16:9 横版知识图卡
3-State
卡片状态反馈
无反馈 → 处理中/已完成/失败
Auto
OAuth Token 刷新
手动复制授权码 → 全自动