返回 Skill 列表
extension
分类: 数据与分析无需 API Key

Course Video Downloader

课程视频抓取与下载技能。通用型方案,适配任意课程平台。流程:用户提供课程链接→浏览器打开→检测登录墙(需要登录则弹窗让用户手动操作)→登录完成后分析页面→提取视频直链(video

person作者: user_b22a52bahubcommunity

课程视频抓取与下载技能

通用型课程视频下载方案,适配任意课程平台(腾讯课堂、B站、超星学习通、网易云课堂、学堂在线、小鹅通等)。

核心限制

  • 无法录屏:浏览器自动化没有录屏能力,但通过抓取视频源文件直链下载,画质更好、速度更快
  • DRM 加密视频无法下载:如果平台使用 blob URL、Widevine/DRM 加密流,只能建议用户用 OBS 手动录屏
  • 不同平台适配程度不同:需要根据页面结构动态分析

工作流程

第一步:接收链接 & 打开页面

用户提供建议课程链接后,用 browser 导航打开:

import sys, os
sys.path.insert(0, os.path.join(os.getenv("SKILL_PATH"), "browser", "scripts"))
import browser

result = browser.navigate(url="用户提供的链接")
print(result)

第二步:检测登录墙

分析页面快照中的文本和交互元素,判断是否需要登录:

判断标准(命中任一即触发)

  • 页面文本包含"登录""注册""请先登录""登录后观看"等字样
  • 存在登录按钮、登录表单(用户名/密码输入框)
  • 视频区域显示"登录后即可观看"或类似提示
  • 页面跳转到了登录页

如果需要登录

  1. 停止浏览器操作,告诉用户遇到了登录要求
  2. 调用 ask_user_question,提供选项:「弹出浏览器我自己操作」/「放弃」
  3. 用户选择接管后,调用 browser.request_manual() 弹窗
  4. 立刻再次调用 ask_user_question 询问用户是否完成操作
  5. 用户确认完成后,调用 browser.get_interactive_elements() 读取当前页面快照,继续后续流程

第三步:分析课程页面结构

登录成功后,分析页面结构:

  1. 课程目录/章节列表:找到课程章节、课时列表的元素索引
  2. 视频播放区域:定位 <video> 标签或 iframe 嵌入
  3. 章节展开:如果章节默认折叠,需要逐个点击展开

使用 browser.execute_script() 提取页面关键信息:

# 提取所有 video 标签的 src
result = browser.execute_script("""
    const videos = document.querySelectorAll('video');
    return Array.from(videos).map(v => ({
        src: v.src || v.currentSrc,
        sources: Array.from(v.querySelectorAll('source')).map(s => s.src)
    }));
""")
print(result)
# 提取所有 iframe(有些平台视频嵌在 iframe 中)
result = browser.execute_script("""
    const iframes = document.querySelectorAll('iframe');
    return Array.from(iframes).map(f => ({ src: f.src, id: f.id }));
""")
print(result)

第四步:提取视频直链

根据页面情况,采用不同策略提取视频地址:

策略 A:直接 video 标签

  • 如果 <video> 标签有直接的 src(mp4/webm/ogg),直接获取

策略 B:m3u8 流

  • 通过 Network 请求拦截或页面 JS 变量中查找 m3u8 地址
result = browser.execute_script("""
    // 尝试从全局变量和常见的播放器对象中提取
    const results = [];
    // 检查 window 上的视频相关变量
    for (const key of Object.keys(window)) {
        try {
            const val = String(window[key]);
            if (val.includes('.m3u8') || val.includes('.mp4')) {
                const match = val.match(/https?:\\/\\/[^"'\\s]+\\.(m3u8|mp4)[^"'\\s]*/);
                if (match) results.push(match[0]);
            }
        } catch(e) {}
    }
    // 检查所有 script 标签中的视频地址
    document.querySelectorAll('script').forEach(s => {
        const matches = s.textContent.match(/https?:\\/\\/[^"'\\s]+\\.(m3u8|mp4)[^"'\\s]*/g);
        if (matches) results.push(...matches);
    });
    return [...new Set(results)];
""")
print(result)

策略 C:XHR/Fetch 拦截

  • 注入网络请求拦截器,等待视频数据加载后捕获请求 URL
# 注入 fetch 拦截器
browser.execute_script("""
    window.__videoUrls = [];
    const origFetch = window.fetch;
    window.fetch = function(...args) {
        const url = typeof args[0] === 'string' ? args[0] : args[0]?.url || '';
        if (url.includes('.m3u8') || url.includes('.mp4') || url.includes('video')) {
            window.__videoUrls.push(url);
        }
        return origFetch.apply(this, args);
    };
    const origOpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function(method, url) {
        if (url.includes('.m3u8') || url.includes('.mp4') || url.includes('video')) {
            window.__videoUrls.push(url);
        }
        return origOpen.apply(this, arguments);
    };
""")

策略 D:进入 iframe 内部提取

  • 如果视频嵌在 iframe 中,需要进入 iframe 的文档上下文提取

第五步:下载视频

获取到视频直链后,使用 Python 下载:

import requests
import os

def download_video(url, save_path, filename):
    """下载视频文件"""
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Referer': '当前课程页面URL',  # 部分平台需要正确的 Referer
    }
    
    # m3u8 需要 ffmpeg 下载
    if url.endswith('.m3u8'):
        download_m3u8(url, save_path, filename)
        return
    
    # mp4 直接下载
    resp = requests.get(url, headers=headers, stream=True)
    total = int(resp.headers.get('content-length', 0))
    downloaded = 0
    filepath = os.path.join(save_path, filename)
    
    with open(filepath, 'wb') as f:
        for chunk in resp.iter_content(chunk_size=8192):
            if chunk:
                f.write(chunk)
                downloaded += len(chunk)
                if total > 0:
                    progress = downloaded / total * 100
                    print(f"\r下载进度: {progress:.1f}% ({downloaded}/{total})", end="")
    print(f"\n下载完成: {filepath}")

m3u8 下载(需要 ffmpeg)

def download_m3u8(m3u8_url, save_path, filename):
    """使用 ffmpeg 下载 m3u8 视频流"""
    import subprocess
    filepath = os.path.join(save_path, filename)
    
    # 先检查 ffmpeg 是否可用
    try:
        subprocess.run(['ffmpeg', '-version'], capture_output=True, check=True)
    except FileNotFoundError:
        print("未安装 ffmpeg,正在尝试用 requests 直接合并 ts 片段...")
        download_m3u8_fallback(m3u8_url, save_path, filename)
        return
    
    cmd = [
        'ffmpeg', '-i', m3u8_url,
        '-c', 'copy', '-bsf:a', 'aac_adtstoasc',
        '-y', filepath
    ]
    subprocess.run(cmd, check=True)
    print(f"下载完成: {filepath}")

第六步:批量处理课程章节

如果课程有多个章节/课时:

  1. 先提取课程目录结构和所有章节链接
  2. 按顺序逐个进入章节页面
  3. 对每个页面执行视频提取和下载
  4. 下载完成后回到目录页,进入下一章节
  5. 记录已下载列表,支持断点续传
# 批量处理示例伪代码
chapters = extract_chapter_list()  # 从页面提取章节列表
downloaded = []  # 已下载记录

for i, chapter in enumerate(chapters):
    if chapter['title'] in downloaded:
        continue  # 跳过已下载的
    
    print(f"[{i+1}/{len(chapters)}] 正在处理: {chapter['title']}")
    
    # 进入章节页面
    browser.click(element_index=chapter['element_index'])
    
    # 等待视频加载
    import time
    time.sleep(3)
    
    # 提取视频链接
    video_url = extract_video_url()
    
    if video_url:
        download_video(video_url, save_dir, f"{chapter['title']}.mp4")
        downloaded.append(chapter['title'])
    else:
        print(f"  未找到视频链接,可能需要特殊处理")
    
    # 返回课程目录
    browser.navigate(back_to_course_url)

输出规范

  • 所有视频保存到工作区下的 output/course_videos/ 目录
  • 文件命名格式:{序号}_{章节标题}.mp4
  • 生成一份下载记录文件 download_log.txt,记录每个文件的下载状态
  • 如果某些视频因 DRM 或其他原因无法下载,在记录中标注原因

常见平台适配要点

| 平台 | 特点 | 注意事项 | |------|------|----------| | 腾讯课堂 | 视频嵌在 iframe,m3u8 流 | 需要 cookie 鉴权,先登录再抓 | | B站 | BV号直链,有 CDN | 可用 bili-api 或直接抓取 | | 超星学习通 | iframe + object 嵌入 | 需要进入 iframe 上下文提取 | | 网易云课堂 | m3u8 加密流 | 可能有防盗链,需要正确 Referer | | 小鹅通 | 微信生态,m3u8 | 部分需要微信授权 | | 学堂在线 | HLS 流 | 需要 cookie |

错误处理

  • blob URL:视频使用 blob URL 时无法直接下载,提示用户使用 OBS 录屏
  • DRM 加密:检测到 Widevine/PlayReady 等加密时,提示用户无法绕过
  • 防盗链:下载失败时尝试添加正确的 Referer 和 Cookie
  • 网络中断:支持断点续传(记录已下载进度)
  • 超时:单个视频下载超时 30 分钟则跳过,记录到日志

使用示例

用户只需说:

  • "帮我下载这个课程的视频:[链接]"
  • "抓取这个网课的视频"
  • "录制这个链接里的课程视频"

Agent 自动执行完整流程。