课程视频抓取与下载技能。通用型方案,适配任意课程平台。流程:用户提供课程链接→浏览器打开→检测登录墙(需要登录则弹窗让用户手动操作)→登录完成后分析页面→提取视频直链(video
课程视频抓取与下载技能是一项面向实际任务的技能,主要用于通用型课程视频下载方案,适配任意课程平台(腾讯课堂、B站、超星学习通、网易云课堂、学堂在线、小鹅通等);无法录屏 :浏览器自动化没有录屏能力,但通过抓取视频源文件直链下载,画质更好、速度更快;
从功能定位来看,该技能强调把分散的操作要求整理成清晰、可复用的处理流程,使用户能够围绕既定目标快速准备输入、选择执行方式并获得结构化结果。实际使用前应先确认任务范围、数据来源、运行环境、必要权限和关键参数,再依据技能说明逐步执行;
若输入条件不完整,应先补齐信息或采用保守配置,避免因错误假设导致结果偏离需求。执行过程中需要关注工具调用是否成功、接口或依赖是否可用、输出格式是否符合预期,并对异常提示、缺失字段和边界情况进行处理;涉及批量任务时,还应保存进度,避免中断后重复操作。
通用型课程视频下载方案,适配任意课程平台(腾讯课堂、B站、超星学习通、网易云课堂、学堂在线、小鹅通等)。
用户提供建议课程链接后,用 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)
分析页面快照中的文本和交互元素,判断是否需要登录:
判断标准(命中任一即触发):
如果需要登录:
ask_user_question,提供选项:「弹出浏览器我自己操作」/「放弃」browser.request_manual() 弹窗ask_user_question 询问用户是否完成操作browser.get_interactive_elements() 读取当前页面快照,继续后续流程登录成功后,分析页面结构:
标签或 iframe 嵌入使用 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 标签
标签有直接的 src(mp4/webm/ogg),直接获取策略 B: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 拦截
# 注入 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 内部提取
获取到视频直链后,使用 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}")
如果课程有多个章节/课时:
# 批量处理示例伪代码
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/ 目录{序号}_{章节标题}.mp4download_log.txt,记录每个文件的下载状态| 平台 | 特点 | 注意事项 |
|---|---|---|
| 腾讯课堂 | 视频嵌在 iframe,m3u8 流 | 需要 cookie 鉴权,先登录再抓 |
| B站 | BV号直链,有 CDN | 可用 bili-api 或直接抓取 |
| 超星学习通 | iframe + object 嵌入 | 需要进入 iframe 上下文提取 |
| 网易云课堂 | m3u8 加密流 | 可能有防盗链,需要正确 Referer |
| 小鹅通 | 微信生态,m3u8 | 部分需要微信授权 |
| 学堂在线 | HLS 流 | 需要 cookie |
用户只需说:
Agent 自动执行完整流程。
相关专题
热门下载
相关下载
精品课程
共6课时 | 54.7万人学习
共89课时 | 134.1万人学习
共49课时 | 82.6万人学习
最新文章