把视频下载封装成AI Agent Skill:从设计到落地的完整实践
2026/9/7 4:14:50 网站建设 项目流程

如果你在任何一个技术交流群待过三天以上,一定见过有人问“这个视频怎么下载?”。常规答案就那么几种:甩一个在线解析站、让装某个浏览器插件、或者来一句“你 F12 自己看”。说实话,这些方案都有点碰运气,在线站今天能用明天挂,插件也经常被平台策略牵着走。我这次想换个思路:把“视频下载”做成一整个 Skill,直接交给 AI Agent 来调度。所谓 Skill,通俗说就是给 Agent 预装的一整套“技能包”,里面包含操作说明、脚本和参数定义,Agent 遇到对应任务时能自己读说明、调工具、跑完整个流程,不需要我每次手敲命令。这篇文章就把我搭这个 Skill 的过程完整拆开,包括方案选型、目录结构、脚本怎么写、以及一堆只有实际踩坑才会知道的问题。适合正在玩 Agent 工作流开发、或者想把自己的重复性任务封装成 Skill 的朋友。

1. 项目整体设计与思路拆解

1.1 为什么要把“下载视频”做成 Skill 而不是普通脚本

这个项目的最初需求其实特别朴素:我希望在 Claude Code、Codex 这类 Agent 环境里,直接说一句“把 B 站这个视频下下来,要高清无音轨的那种”,它就能自己完成解析、下载、后台整理。一个普通 shell 脚本其实也能做到“输入链接输出文件”,但问题在于脚本不会思考。你给它一个小红书分享口令,它不知道要先把“复制这段内容”里的乱码链接提取出来;你告诉它“要 4K 但不要弹幕”,它也不知道对应 yt-dlp 的哪个参数。把这些“判断逻辑”写死在脚本里会很僵硬,可一旦交给 Agent 去理解意图、再调用底层工具,体验就完全不同。

做成 Skill 的另一个好处是可复用、可分享。以前我写工具,散落在各个文件夹里,换个电脑就要重新搭环境。Skill 把使用说明、脚本、配置全部收在一个目录下,我甚至可以让 Agent 自己根据任务描述去搜索该调用哪个 Skill。根据我实际使用下来的感受,Skill 更像一个“可对话的命令行工具”,你不需要记参数,用自然语言提出需求,Agent 负责翻译成精确操作。这也是为什么现在 Skill 相关的话题越来越火,“skill 是什么”这类问题下面,很大一部分人其实是在问同一个问题:怎么让自己的 AI 助手变得更“能干”。

1.2 总体架构与工具选型分析

在定方案之前,我把市面上的视频下载方式梳理了一遍,按可靠程度排了个序。首选肯定是yt-dlp,这个开源项目是我目前见过兼容面最广、还在持续维护的下载引擎,B 站、网易云音乐、甚至很多泛媒体站点它都能解析。它对 Python 生态的支持也顺手,一个人维护的 Skill 全家桶里放一个 yt-dlp 就能覆盖大部分场景。备选是you-get,它的代码更简单,在一些 B 站番剧、公开课上反而比 yt-dlp 表现稳定,我把它作为解析失败时的兜底工具。

然后是平台差异问题。在真实环境里,“B 站视频下载”“抖音原视频下载”“小红书视频下载”这几个热词背后,是完全不同的技术路径。B 站公开视频的接口还算友好,只要拿到 BV 号或直接复制网页地址,yt-dlp 就能解析出视频流;小红书则经常需要处理分享口令里的冗余文本;抖音的风控链路更复杂,很多时候需要借助浏览器登录后的 Cookie 才能拿到完整清晰度;微信视频号和公众号视频既没有明显的下载按钮,网页端接口又藏得很深,我最后采用的是“浏览器开发者工具抓取 m3u8 地址 + ffmpeg 合并下载”的通用方案。所以这个 Skill 的架构不能是单引擎,而是一套“主引擎加辅助策略”的组合:先让 yt-dlp 试一遍,失败就切 you-get,再不行就引导用户走手动嗅探。

1.3 平台支持范围与边界划定

做工具最忌讳的就是“什么都想支持”,结果每个平台都做不深。我一开始列了十多个目标站点,后来删到只剩五类核心:B 站、小红书、抖音、微信视频号、微信公众号。理由很简单,这些平台在我日常内容搜集里出现频率最高,而且它们的下载逻辑互不相同,正好可以把 Skill 的适配能力展示得比较全面。至于海外视频平台,说实话这类平台的下载需求也很真实,但在国内网络环境里涉及的问题比较多,我最终选择不纳入默认支持列表。这个 Skill 的定位就是解决咱们手边最常见的视频保存需求,不去碰那些需要额外折腾才能访问的资源。

还有一个重要的边界,就是版权问题。我只把这个 Skill 用于个人学习、资料整理和内容备份,下载范围限定在公开可播放的视频。如果你要拿下载的视频做二次创作、商用或者传播,一定要先获得授权,不要在版权问题上打擦边球。技术本身是中性的,但使用场景一定要干净。

2. 核心细节解析与实操要点

2.1 Skill 的标准目录结构与配置格式

如果你了解过 Claude Code 的 Skill 体系,大概知道它的本质是一种“镜头式技能注入”:一个 Skill 就是一组文件,最上面是SKILL.md,里面写清楚这个技能是什么、什么时候用、怎么用,用户提问后 Agent 会读取这份说明来决定是否调用。我自己用的目录结构非常简单:

video-download-skill/ ├── SKILL.md ├── scripts/ │ ├── download.py │ ├── extract_url.py │ └── merge_video.py ├── config/ │ └── yt-dlp.conf └── requirements.txt

SKILL.md是 Agent 的行动手册,它的开头 YAML 部分会声明技能的 name、description 和适用场景,正文部分则是给 Agent 看的操作指引。这里有个细节很容易被忽略:Agent 很善于读文档,但它不善于从一份长文档里自行提炼“什么时候该调用”。所以你必须在 description 里写得非常具体,比如“当用户想要保存某个平台的视频文件、提取音频、批量下载合集时使用”,而不是笼统的“视频下载工具”。这个字段直接决定了 Agent 在意图匹配阶段的准确率,我前期用“视频工具”这种模糊描述测试时,经常出现 Agent 明明需要下载视频却跑去搜索网页的情况。

config/yt-dlp.conf是把通用策略抽出来的地方,比如默认格式选择、保存目录、文件命名模板,这样脚本只负责传参和异常处理,策略调整不用改代码。

2.2 各主流平台的解析差异与应对策略

我在设计脚本时,意识到一件事:与其把所有解析逻辑都堆在 Python 里,不如先定义清晰的分支策略。Skill 的主入口是一个 Python 脚本download.py,它接收 Agent 传递的参数,比如 URL、清晰度、是否只下音频、保存目录,然后根据域名走不同的处理路径。

B 站下载,我的做法是直接传原始网页链接给 yt-dlp。它能自动识别出 BV 号、作品标题、分 P 列表,甚至能把封面、弹幕和字幕都捎带下载。唯一要注意的是有些视频有“仅登录可见”或“大会员专享”的限制,这种情况需要在脚本里额外传入 cookies 文件,把浏览器里已登录的 cookie 导出来给 yt-dlp 用。

小红书的问题更像“纸币没拆封”:用户在手机上分享时得到的是一个带文案的短口令,里面夹杂着“复制此消息,打开小红书 App 查看我的笔记”这类字眼。我的extract_url.py就是干这个的,用正则把真正的那条短链接从整段文本里挖出来,再让 yt-dlp 去解析。这里有个容易踩的坑是,小红书的视频流地址有比较强的时效性,解析出来后要抓紧下载,放久了签名会过期。

抖音的兼容性相对差一些,网页版的接口风控频繁。常规做法还是 yt-dlp 加 cookies,但在脚本里我额外加了一个判断:如果传入了文本格式的分享口令,先尝试从口令中提取短链,再用短链去解析。实测下来,配合登录 Cookie 的成功率能到七成以上,剩下那三成大多发生在深夜高峰期或者账号被临时限流的时候。

微信视频号和公众号视频就没有那么标准的接口了。视频号网页端基本不暴露直链,公众号文章里的视频虽然能播,但常规工具也经常抓不准。我给 Skill 预设的是“手动辅助模式”:引导用户打开浏览器开发者工具,在 Network 面板里筛选m3u8mp4后缀,把视频流真实地址复制出来,然后交给download.py里的 ffmpeg 分支去处理。这个流程看似手动,但其实已经比其他方法省力不少,而且对那些加了防盗链的地址也能通过“伪造 Referer 头”的方式绕过。

2.3 清晰度选择、命名规则与存储路径

视频下载最容易翻车的不是下载本身,而是“下下来的不是我要的”。yt-dlp 的格式选择参数写得长一点,比如-f "bv*+ba/b",意思是优先要分离的视频流和音频流然后合并,拿不到就退回最佳混合文件。在 Skill 里,我通过quality参数把用户意图翻译成三档:best则加-S "res:1080"audio则加-x --audio-format mp3default就直接-f "bv*+ba/b"

命名这块我吃过不少亏。早期直接拿视频标题当文件名,结果各种奇奇怪怪的字符(比如 B 站标题里的/和引号)直接把路径搞崩。现在我在yt-dlp.conf里固定了一套模板:

outtmpl: /data/videos/%(extractor_key)s/%(upload_date)s_%(title).50s_%(resolution)s.%(ext)s

按平台分目录、按日期加前缀、标题截断到 50 字符,这样不仅好看,还能避免重名覆盖。要在 Skill 里实现这个逻辑,就要把它固化到脚本参数里,保证 Agent 调用时不用临时想命名规则。

3. 实操过程与核心环节实现

3.1 环境准备与依赖安装

开始动手之前,先把运行环境准备好。我是在一台 Ubuntu 服务器上做的,Python 版本 3.10+,然后安装了核心依赖:

pip install yt-dlp you-get ffmpeg-python apt install ffmpeg

这里有个细节,ffmpeg是必须要装的,因为现在主流视频平台普遍把视频流和音频流分开传输,没有 ffmpeg 就没有办法合成最终文件。很多新手下载出来只有画面没有声音,十有八九就是没装 ffmpeg 或者没把它加到 PATH 里。装完后跑一句ffmpeg -version确认一下,免得后面出莫名奇妙的错误。

如果你打算让 Skill 去解析 B 站高清视频,建议把浏览器登录后的 cookies 导出成cookies.txt放到config/目录。这个操作在 Chrome 里可以通过一些导出扩展做,注意导出的格式要是 Netscape 格式,yt-dlp 才能直接识别。

3.2 SKILL.md 与下载脚本的完整实现

SKILL.md是整套 Skill 的灵魂。我的写法是给 Agent 一个清晰的判断清单,让它知道自己什么时候该出场、出场后按什么顺序执行。完整内容如下:

--- name: video-downloader description: 下载主流平台视频。当用户想要保存 B站、小红书、抖音、微信视频号、公众号里的视频文件、提取音频、批量下载视频集合时使用。 ---

然后是给 Agent 看的主体说明:

# 视频下载助手 这个 Skill 用于解析和下载主流视频平台的内容。它支持 B站、小红书、抖音、微信视频号、微信公众号。 ## 什么时候使用 - 用户给出视频分享链接或分享口令,要求“下载视频”“保存视频”“提取音频”。 - 用户需要把在线视频转换成 mp4/mp3 文件。 ## 调用步骤 1. 先从用户输入中提取 URL。如果输入是纯文本分享口令,先运行 scripts/extract_url.py。 2. 调用 scripts/download.py,传入 URL、期望清晰度、保存目录等参数。 3. 下载完成后反馈文件保存路径和时长。 ## 注意事项 - 不要臆造不存在的下载链接。 - 如果脚本返回风控或解析失败,不要重复尝试超过两次,直接向用户说明情况,并提示可以使用浏览器开发者工具手动获取 m3u8 地址。 - 所有内容仅用于个人学习和备份,禁止用于商业用途。

这份文档看起来不长,但对 Agent 的行为约束却非常关键。尤其是“不要臆造下载链接”和“失败时不要反复重试”这两条,都是我在实际使用中总结出来的。Agent 有时候会自作主张地拼一个看似合理的 URL,或者因为条件判断不严谨而对同一个链接发起无效请求,提前在 SKILL.md 里写清楚能省去很多麻烦。

下载脚本download.py的核心逻辑如下:

#!/usr/bin/env python3 import argparse import re import subprocess import sys from pathlib import Path def extract_short_url(text: str) -> str: pattern = r'https?://[^\s]+' urls = re.findall(pattern, text) return urls[0] if urls else "" def run_ytdlp(url: str, quality: str, output: str, cookies: str = ""): cmd = ["yt-dlp", "--config-location", "config/yt-dlp.conf"] if cookies: cmd += ["--cookies", cookies] if quality == "best": cmd += ["-f", "bv*+ba/b", "-S", "res:1080"] elif quality == "audio": cmd += ["-x", "--audio-format", "mp3"] else: cmd += ["-f", "bv*+ba/b"] cmd += ["-o", f"{output}/%(extractor_key)s/%(upload_date)s_%(title).50s_%(resolution)s.%(ext)s", url] return subprocess.call(cmd) def main(): parser = argparse.ArgumentParser() parser.add_argument("url") parser.add_argument("--quality", default="default", choices=["default", "best", "audio"]) parser.add_argument("--output", default="/data/videos") parser.add_argument("--cookies", default="config/cookies.txt") args = parser.parse_args() url = extract_short_url(args.url) if "复制" in args.url or "http" not in args.url else args.url if not url: print("无法从输入中提取有效链接") sys.exit(1) # 优先使用 yt-dlp code = run_ytdlp(url, args.quality, args.output, args.cookies) if code != 0: # 兜底使用 you-get print("yt-dlp 解析失败,尝试 you-get") subprocess.call(["you-get", "-o", args.output, url]) if __name__ == "__main__": main()

这个脚本的设计思路很直接:提取 URL、调主引擎、失败再兜底。不过你要注意,在不同的 Agent 环境里,脚本参数传递的方式可能不太一样。有些 Agent 框架允许在 Skill 描述里定义“输入输出 schema”,有了 schema 之后,Agent 调用脚本时传参会更精准,不会出现漏传参数导致脚本报错的情况。我后来给这个 Skill 补了一个简单的 schema 描述,声明了urlqualityoutput三个字段,Agent 的调用准确率高了很多。

3.3 Agent 端到端调试与测试流程

搭好 Skill 后,最重要的一步是端到端测试。不要一上来就拿真实链接测,先用一个已知稳定的 B 站公开视频试。测试时我在名为workbench的 Agent 环境里启动,然后输入指令:“把 https://www.bilibili.com/video/BV1xx411c7mD 这个视频下载到默认目录,要最高清。”

第一次测试我期待它能直接跑通,但实际反馈不太理想:Agent 虽然识别了下载意图,却把download.py的路径写错了,因为当前工作目录跟 Skill 目录不在同一层。后来我在脚本里加了基于__file__的绝对路径寻址,并把这个逻辑写进SKILL.md,让 Agent 明白“脚本路径相对于 Skill 根目录”。第二次测试就顺利多了,yt-dlp 解析出标题和视频流,ffmpeg 完成合并,文件落在了/data/videos/bilibili/20250115_xxx.mp4

之后我又对不同的输入格式做了一组测试,覆盖裸链接、带文字的口令、只给“B 站搜‘XX’然后下载第一个视频”这类模糊指令。前两种很容易过,第三种才暴露问题:Agent 并不知道怎么“搜索 B 站”,它需要额外信息。我于是在 Skill 里新增了一个参数search_keyword,当用户没有提供具体链接时,脚本会先调 B 站的搜索接口拿到第一个结果的 BV 号,再走正常下载流程。这个功能补上以后,整个 Skill 才真正称得上“好用”。

3.4 手动嗅探技巧:没有接口时的通用方案

不管你用什么手段,总会遇到几个无法通过常规接口解析的视频。这未必是平台风控多强,有时候只是它们没有给网页版暴露完整的媒体地址。这时候我一般切换“浏览器嗅探”方案,过程很短:

打开视频所在页面,按下 F12 进入开发者工具,切到 Network(网络)面板,刷新页面,再点播放。在类型筛选栏里输入media或者m3u8,就能看到视频流请求。找到请求后,右键复制它的 URL,这就是视频直链。接下来把这个 URL 交给 Skill,脚本会识别到它是 m3u8 地址,自动走 ffmpeg 分支:

ffmpeg -i "http://example.com/video.m3u8" -c copy output.mp4

如果发现视频只有画面没有声音,说明这个 m3u8 只包含了视频轨,通常在同一次刷新记录里还会有对应的 audio m3u8,需要两个地址一起交给合并脚本。这个技巧特别适合微信视频号、网页内嵌播放器等场景。我的建议是把这个流程固化在SKILL.md的“疑难杂症”一节里,遇到 Agent 解析失败时,它就知道该怎么引导用户配合操作。

4. 常见问题与排查技巧实录

4.1 解析失败与“签名过期”怎么处理

用这个 Skill 一段时间后,我积累了不少问题排查经验,其中最典型的就是解析失败。有时候你明明把 B 站链接发给 Agent,它却返回一个错误:HTTP Error 403: Forbidden。原因通常是两类,一类是视频有地区限制或者需要登录,输入的 Cookie 没有通过校验;另一类是平台的防盗链机制校验了请求头里的Referer字段。解决方法是把浏览器里的完整请求头信息导出,在下载脚本里给 yt-dlp 加上--add-header "Referer:https://www.bilibili.com/"。如果是签名过期导致的“视频流地址已经失效”,这个基本没有自动修复的办法,重新跑一次解析,让 Skill 拿最新地址再下载就行。

4.2 合集、多P视频与限速问题

B 站的视频经常是整个合集动辄几十条,如果用户直接丢一个合集页链接过来,默认行为是只下载第一个 P 还是全下载?我在 Skill 里做了一个显式约定:默认下载全部 P,但会在下载前告诉用户数量。“全部下载”这个策略在对付 B 站合集时很好用,但有时候用户只想要其中一集。这时候你需要在参数里加-I 3这样的索引选择。yt-dlp 的--playlist-items参数就是干这个的,Skill 里通过--begin--end暴露给用户。限速问题则是另一个高频问题,尤其是高峰期下载大文件,经常被平台限流。我的办法是在配置里限制下载速度上限为 5M/s,配合--sleep-requests 1让每个请求之间留一个间隔,从体验上看下载成功率有明显提升。

4.3 下载后文件损坏、没有声音

很多人的第一反应是“下载工具坏了”,其实“没声音”的原因往往是格式选择不对。B 站和一些视频站现在都是视频轨和音频轨分离,如果下载时只抓了bv*而没合并ba,就会拿到一个静音的视频。验证是否是这个原因,可以先在命令行手动跑一次 yt-dlp 并开启 verbose 日志,看看最终命令里有没有包含ba音频轨的选择。另一个常见问题是文件下载到一半空间不足,Skill 默认输出目录在/data/videos,如果磁盘满了,ffmpeg 合并的时候会直接崩溃。我在脚本里加了一个df检查,磁盘空间低于 1GB 就主动报错,提醒用户先腾空间。

4.4 快速排查清单

把我踩过的坑整理成一份速查表,基本能覆盖 90% 的问题:

现象可能原因处理办法
403 Forbidden缺少 Referer 或 Cookie加请求头、传 cookies.txt
只有画面没有声音没装 ffmpeg 或格式少选了音频轨安装 ffmpeg,确保格式bv*+ba/b
解析出来秒数很短抓到了测试流或广告流重新解析,带上平台域名做参数
某平台一直失败账号风控或被临时限速等待 5 分钟后重试,或换浏览器登录态
文件名乱码标题里特殊字符未转义使用--restrict-filenames
m3u8 手动下载报错请求头或 Referer 被检查ffmpeg 命令里加-headers

我一直觉得,做工具的最高优先级不是“功能多”,而是“异常情况说得清”。Skill 里能让 Agent 理解这些异常并给出候选解决方案,比把代码写得再长都管用。

结尾

这个 Skill 从最初一个简单的 shell 脚本到现在,已经在我自己的内容归档工作流里稳定跑了好几个月。我个人的最大体会是:真正的效率提升不在于有没有一个“万能下载器”,而在于当用户能直接用自然语言提出“把这个视频存下来”时,Agent 是否已经准备好接住这句话。Skill 的价值就在这里——把工具从“需要学习的使用说明”变成“随时待命的同事”。下一步我打算把搜索能力再接进来,让 Agent 收到“帮我下几个关于机器学习的公开课”这种模糊需求时,也能自动完成检索、筛选、下载的全流程。如果你也在玩 Agent 工作流,强烈建议从这类小技能开始做,投入产出比非常高。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询