十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

给AI装上视频下载Skill:架构设计、平台解析与实战排坑

给AI装上视频下载Skill:架构设计、平台解析与实战排坑 最近在折腾 AI 编程助手发现Skill这个词被反复提起Codex、Claude Code 这些工具都在推类似的概念但真正讲清楚它是什么、能拿来干什么的文章少之又少。正好我这边有个真实需求经常要把 B站、抖音、小红书、视频号上的视频下载到本地做素材整理手动开网页、找解析站、复制粘贴这些老办法效率低有些站点还三天两头失效于是索性写了一个视频下载 Skill丢给 AI 助手让它拿到链接就能按固定流程干活。这个 Skill 跑起来之后效果比我预想的好不少。它本质上是一个可复用的能力包把如何识别平台、如何解析真实地址、如何下载合并这整套流程固化下来AI 只要看到视频链接就知道该调用哪套逻辑。这篇文章会把整个项目从设计到落地的过程拆开讲清楚包括 Skill 的目录结构、核心代码、多平台适配的思路以及几个把我折磨得不轻的坑。想给 AI 装技能包的人或者对视频下载原理好奇的朋友都可以参考。1. 为什么我会想给 AI 装一个视频下载 Skill1.1 这个需求的真实来源我的日常工作和视频素材整理强相关。B站的教学视频想存下来离线看抖音上的营销案例要收集归档小红书里一些商品演示要做对比微信视频号里朋友分享的行业内容也想备份。这些操作单独看都不难但放在一起就很折磨人。以前用过几类方案。第一类是浏览器插件Chrome 商店里一搜一大把但基本都是一两个平台专用装五六个插件互相打架是常态。第二类是在线解析网站把链接粘进去等它吐出下载地址广告弹窗满天飞还担心链接里的信息被第三方服务器记走。第三类是命令行工具功能确实强大但每次都要记一堆参数遇到失效站点还得去翻 issue 找新规则。这些方案最大的问题不是不能用而是太碎。我想要的是一个统一入口丢一个链接进去不用管是哪个平台的它能自动识别、自动解析、自动下载最后告诉我文件保存在哪。这恰好就是 Skill 擅长干的事。1.2 Skill 和普通脚本的本质区别很多人第一次接触 Skill 会把它理解成一个 Python 脚本或者一个插件我不太同意这个看法。普通脚本是把流程写死输入输出都是确定的而 Skill 是给 AI 用的一套能力包它包含三个层次的东西描述层告诉 AI 这个技能是干什么的、什么情况下该调用工具层真正干活的代码比如解析器、下载器流程层给出执行步骤和注意点让 AI 按正确的顺序操作打个比方普通脚本是一台内置了程序的自动售货机你投币它出货Skill 更像是一本带工具箱的操作手册AI 是那个读手册的人遇到情况还能临场变通。比如用户说把这个视频下了顺便把字幕也提出来如果只有下载脚本这需求就得再写一套但如果是一个设计良好的 SkillAI 看到 SKILL.md 里写了下载完成后可用 ffmpeg 提取字幕它自己就能组合出新的操作流程。这也是我选择用 Python 来实现而不是直接写一个 CLI 工具的原因。CLI 工具解决的是当前已知问题Skill 解决的是未来可能出现的相关问题后者的复用价值高得多。2. 视频下载的底层套路先问地址再搬运数据2.1 平台解析的原理一件事三种做法不管哪个视频平台界面做得再花哨最后都要让浏览器拿到一段视频流。所谓解析本质就是想办法问出这段流的真实地址。根据平台的防护强度我总结出三种从易到难的做法。第一种是直接翻页面 HTML。这是最老实的办法适合那些没有做任何防护的平台。打开网页源码找video标签src属性里就是视频地址。当然现在这么做能成功的平台已经很少了大多数页面里的视频都是用 JS 动态加载的源码里根本看不到。第二种是抓接口请求。打开浏览器的开发者工具切到 Network 面板刷新页面盯着那些 XHR 请求看。视频地址通常藏在某个返回 JSON 的接口里可能是playurl、video_url、play_addr之类的字段名。这是目前最主流的解析思路也是我要重点讲的。第三种是逆向 JS 加密逻辑。有些平台把接口参数做了签名直接请求会报参数错误。这时候就得去读前端代码找出签名算法在 Python 里重新实现一遍。工作量最大但也是最稳的方案。2.2 下载与合并不是拿到地址就完事拿到视频地址只是第一步后面还有两个常见的坑。第一个坑是视音频分离。现在的视频平台为了节省带宽、方便用户拖动进度条普遍采用 DASH 技术视频轨道和音频轨道分开传视频流里只有画面没有声音音频流单独一个文件最后在播放器里实时合成。如果你直接下载视频地址大概率得到一个无声视频。解决办法是用 ffmpeg 把两个流合并ffmpeg -i video.mp4 -i audio.m4a -c copy output.mp4-c copy表示不重新编码直接复制流速度飞快也不会损失画质。第二个坑是分片。有些平台把完整视频切成几秒一个的 ts 小文件下载的时候要按顺序把所有分片拉下来再拼接。这个操作也可以用 ffmpeg 完成先把分片列表写进一个 txt 文件然后一条命令搞定ffmpeg -f concat -safe 0 -i list.txt -c copy merged.mp42.3 为什么不干脆做成一个全平台一键下载的大工具聊到这肯定有人问这些逻辑都清楚了为什么不做成一个大而全的工具覆盖所有平台答案是没必要而且维护成本极高。平台接口说变就变今天还能用的解析方式明天可能就返回 403。与其维护一个巨大的解析规则库不如让 Skill 保持轻盈——只覆盖自己最常用的几个平台遇到失效就用 AI 协助快速修。3. 动手实现Skill 结构、核心代码与配置细节3.1 目录设计一眼能看懂的职责划分一个 Skill 能不能被 AI 用好目录结构很关键。我最终定下来的结构长这样video-downloader/ ├── SKILL.md ├── requirements.txt ├── config.yaml ├── downloader/ │ ├── __init__.py │ ├── cli.py # 入口接收 URL 和参数 │ ├── platform.py # 平台识别 │ ├── core.py # 下载调度、重试、合并 │ ├── utils.py # UA 管理、请求头构造 │ └── parsers/ │ ├── __init__.py │ ├── bilibili.py │ ├── douyin.py │ ├── xiaohongshu.py │ └── wechat.pyparsers目录里每个文件对应一个平台职责单一。以后要加新平台只需要在这个目录里新增一个文件然后在platform.py里加一条识别规则完全不用动其他代码。这样设计的好处是AI 在协助我修改某个平台逻辑时它能很清楚地定位到对应文件不会改错地方。3.2 SKILL.mdAI 能不能正确调用就靠它SKILL.md 是整个 Skill 的灵魂。写得太简单AI 不知道怎么用写得太啰嗦AI 抓不住重点。我踩了几次坑之后总结出一个比较稳的结构--- name: video-downloader description: 用户提供视频链接时自动识别平台、解析真实地址并下载到本地。 triggers: - 视频下载 - 保存视频 - 下载B站视频 - 下载抖音视频 - 下载小红书视频 - 下载视频号视频 --- # Video Downloader Skill ## 使用条件 - 用户提供可访问的视频页面 URL - 仅用于个人学习、资料备份、已获授权内容的下载 ## 执行步骤 1. 从用户输入中提取视频链接 URL 2. 调用 platform.py 的 detect_platform 识别平台 3. 根据平台名调用对应 parser解析真实视频地址 4. 调用 core.py 的 download_video 下载并返回保存路径 5. 如果视频包含分离的音轨调用 ffmpeg 合并 ## 常见问题 - 403 错误检查 Referer 和 User-Agent 是否与目标平台匹配 - 下载超时调用重试机制最多重试 3 次间隔 2 秒关键点是triggers部分一定要写清楚触发条件。AI 判断用户这句话是否该调用这个技能靠的就是这个字段。之前我把 triggers 写得太泛结果用户问今天天气怎么样它也去调解析器闹了不少笑话。3.3 核心代码平台识别与下载调度平台识别非常简单就是域名匹配PLATFORM_RULES [ (bilibili.com, bilibili), (douyin.com, douyin), (xiaohongshu.com, xiaohongshu), (weixin.qq.com, wechat), ] def detect_platform(url: str) - str: for keyword, platform in PLATFORM_RULES: if keyword in url: return platform raise UnsupportedPlatformError(f无法识别的平台: {url})下载调度部分我封装了一个带重试的流式下载函数def download_video(url: str, save_path: str, headers: dict, max_retries: int 3) - str: for attempt in range(max_retries): try: resp requests.get(url, headersheaders, streamTrue, timeout30) resp.raise_for_status() with open(save_path, wb) as f: for chunk in resp.iter_content(chunk_size64 * 1024): f.write(chunk) return save_path except (requests.RequestException, IOError) as e: if attempt max_retries - 1: raise DownloadError(f下载失败: {e}) time.sleep(2)注意iter_content的chunk_size我设成了 64KB。这个值太小会导致请求次数过多太大则占用内存64KB 是我实测下来比较平衡的选择。4. 主流平台逐个聊B站、抖音、小红书、视频号的适配思路4.1 B站视音频分离是必修课B站是所有平台里解析逻辑最标准的一个。它有一个公开的 playurl 接口传入视频的 BV 号和分 P 的 cid就能拿到播放地址。老版本接口不登录也能拿到 480P1080P 以上需要登录 Cookie。拿到返回的 JSON 后重点关注dash字段。里面是一个video数组和一个audio数组video 里是无声的视频流audio 里是音频流。下载的时候要分别取一条然后用 ffmpeg 合并。还有一个细节是 Referer。B站的 CDN 会校验请求头如果 Referer 不是https://www.bilibili.com会直接 403。这个问题我在后面专门花了一下午排查大家可以直接把 Referer 写进请求头模板里省得踩坑。4.2 抖音短链跳转与无水印地址抖音的分享链接通常是v.douyin.com/xxxx这种短链。第一步要用requests.get跟随重定向拿到真实页面 URL再从这个页面里提取数据。页面的初始化数据里通常包含视频的地址但那个地址是带水印的。社区里一个公开的技巧是把地址中的playwm替换成play就能得到无水印版本。这个方法我实测有效但不敢保证一直有效——抖音改接口是家常便饭。抖音的请求头里 User-Agent 尤其重要最好伪装成手机端浏览器的 UA否则页面返回的数据结构可能不一样。4.3 小红书与视频号Cookie 门槛与页面数据提取小红书的视频地址藏在页面初始化数据里字段名经常变我每次都要去开发者工具里现翻。它有一个门槛是必须带登录后的 Cookie否则接口返回的数据里根本没有视频地址。这个逻辑没法绕过只能在 Skill 里预留 Cookie 配置项。视频号的方式比较特殊。直接在微信里打开的视频号链接页面是复杂的 SPA 应用直接解析 HTML 拿不到什么。但是视频号助手的页面里视频地址是直链形式后缀是.mp4等于是平台主动暴露给内部工具用的。我目前的方案是在 Skill 里配置一个视频号助手的 Cookie用它去请求对应页面然后从返回 JSON 里提取url字段。4.4 平台失效之后用浏览器自动化兜底解析逻辑写得再好也架不住平台改版。我的应对策略是给 Skill 留一条兜底路线如果某个平台的 parser 失效了就让 AI 切换到 Playwright 方案——用浏览器真实加载页面监听网络请求把那些media类型的请求拦下来找到最大的那个就是视频源。这个方案慢而且重但胜在通用。它不是一个常规解析路径而是应对突发失效的保险丝。我在 Skill 里把这套逻辑也封装好了遇到 parser 报错时自动降级。5. 一次持续一下午的 403 排查完整定位链路5.1 现象所有请求都返回 403有一次我在调试 B站解析逻辑突然发现下载功能全挂了。无论下载哪个视频都是 403 Forbidden。一开始我以为是 IP 被平台封了换了个网络测试还是 403。这时候我意识到问题大概率出在请求头或者解析逻辑上。5.2 定位过程三步缩小范围我的排查思路分三步走。第一步排除代码 bug。直接用 curl 手动请求解析出来的视频地址同样 403。这说明问题不在下载函数而在请求本身。第二步区分是 CDN 问题还是接口问题。我去请求 playurl 接口发现接口返回正常视频地址也能拿到只有在请求视频 CDN 地址时才 403。说明问题出在 CDN 的校验上。第三步对比浏览器和代码的差异。我在浏览器里打开同一个视频播放正常。打开开发者工具看视频请求的请求头发现浏览器带了一个Referer: https://www.bilibili.com。而我的代码里没有设置 Referer。补上之后再试直接 200。5.3 根治方案把 Referer 变成动态值这个问题的根源在于B站 CDN 的 Referer 校验是必须匹配视频所在页面域名。虽然直接写死https://www.bilibili.com也能用但不够优雅——如果将来支持其他平台每个平台的 Referer 都不一样。我的最终方案是在解析阶段就把 Referer 作为上下文传给下载器parse_result parser.parse(url) # parse_result 里包含 video_url, audio_url, referer download_video( parse_result.video_url, parse_result.referer, headersbuild_headers(parse_result.referer), )这样每个 parser 负责提供自己的 Referer下载器只负责按规则组装请求头职责清晰以后加新平台也不会再犯同样的错。6. 跑通之后的体验实测数据与还能玩出的花样6.1 实际使用效果整个 Skill 跑起来之后我记录了每个平台的解析耗时和下载速度。平台解析耗时下载速度是否需登录结果B站1s8MB/s高清需Cookie成功需合并音视频抖音1-2s5MB/s否成功无水印小红书2-3s3MB/s需要Cookie成功视频号1s6MB/s需要Cookie成功实测下来B站的体验最稳因为它的接口格式最规范、文档化程度也最高。抖音和小红书时不时的字段变动我一般几分钟就能修好因为 Skill 结构清晰定位到对应 parser 文件改就行。6.2 在此基础上还能做什么视频下载只是这个 Skill 的一个应用场景。实际上把解析能力和下载能力拆开之后可以组合出很多有意思的玩法。比如做一个视频内容总结的 Skill用户丢一个 B站链接过来AI 先下载视频然后抽取音频转文字再调用大模型生成摘要一条龙完成。这个流程里视频下载 Skill 只是作为一个底层模块被调用但它解决了最关键的素材获取环节。再比如批量下载场景。抖音的合集、B站的分 P 视频都可以在解析层识别出这是一个合集然后循环下载所有分集最后按序合并。我给 Skill 加了--batch参数后整理课程视频方便了很多。还有一个思路是把下载完的视频自动转成音频用 ffmpeg 提取音轨这样通勤路上可以直接当播客听。这个需求本质上只是给现有 Skill 加一个后处理步骤完全不需要另起炉灶。最后分享一个我在实际操作中的体会Skill 的价值不在于它一开始多完美而在于它能不能让你在遇到新需求时快速扩展。我的这个视频下载 Skill 从第一版到现在核心的core.py几乎没改过大部分迭代都发生在parsers目录里——每遇到一个新平台就新增一个解析文件每遇到一个接口变更就修一个解析函数。这种框架稳定、细节易改的结构才是它能持续为我干活的关键。如果你也想给自己的 AI 助手加类似的能力建议先从最常用的平台开始跑通一条完整流程再逐步扩展。一口吃不成胖子但先啃下最硬的那块骨头后面就顺了。
返回列表