资讯详情

用Claude Code定位隐藏M3U8地址:网页视频下载实战

📅 2026/10/14 2:37:30 | 华诺云谱 👁 阅读
用Claude Code定位隐藏M3U8地址:网页视频下载实战
Claude Code这个命令行编程助手最近在我手里干了一件挺省心的活定位一个藏在播放器背后的真实视频地址再把整段视频拉回本地。起因是我调一个在线教学页面的样式时发现视频资源走的是分段流页面上根本不存在一个能直接下载的完整MP4直链。F12一开请求列表密密麻麻看着像视频的接口不少但真正能用来下载的地址一个都找不到。以前碰到这种问题要么手动翻源码翻到眼瞎要么把几百个JS文件一个个保存下来慢慢读。现在不用了我可以直接把整份HTML和相关的JS片段交给Claude Code让它替我分析播放逻辑告诉我真实地址从哪来再让它把下载脚本生成好。这篇文章我就把完整的思路和代码记录下来包括我踩过的坑和判断链接有效性的经验。适合看这篇文章的人手头有公开页面需要离线保存视频但发现页面上没有直链或者单纯想搞清楚网页播放器的工作原理又或者你已经在用Claude Code这类编程助手想看看它在这种网络任务里到底能帮上多少忙。先说清楚这里讲的技术只面向你明确有使用权的内容——比如自己的课程、公开演示素材、无版权争议的开放资源。付费墙、DRM这类东西不在讨论范围内。1. 先搞明白你在页面上看到的“视频地址”为什么是假的1.1 四种常见的“假地址”形态很多人以为在浏览器里看到视频能播就一定能找到某个以.mp4结尾的直链。但现代网页播放器的实现方式早就不是这样了。我随手列一下最常见的几种“假地址”你看到的东西实际状态能不能直接下载video srcblob:https://...浏览器内存里的数据流引用不能这只是个指向内存的占位符Network 面板里一堆video/MP2T请求一个个 TS 分片单个分片没有意义播放器拿到的是顺序以.m3u8结尾的清单播放预案可以但要看清单里写的是直链还是子清单接口返回的一串加密参数播放器运行时再解析不能得模拟完整解析过程我调试的页面属于第二种加第三种播放器用的是 HLS 协议视频被切成几十个甚至几百个小分片播放器拿到一张.m3u8清单之后才按顺序挨个请求分片。所以你即使看到一个.ts分片的地址单独把它下载下来也没有意义——它只是整段视频里两秒钟的画面。注意如果你在元素面板里选中视频标签看到srcblob:https://...这种地址不用怀疑自己找错地方这是正常的。播放器把所有分片拼成了流再喂给浏览器的video标签这个 blob 地址只在当前标签页存活换个页面就失效。1.2 不理解HLS后面全白搭HLS 全称是 HTTP Live Streaming苹果公司提出的流媒体协议。它的核心思路可以用一个生活化类比解释你不是直接拿到一个完整的汉堡而是拿到一份“食材分装包”——m3u8 是打包菜单上面写了分几步、每步用哪块食材分片ts 文件是食材包本身可能是两秒的一段画面。播放器拿到菜单后按顺序把食材一个一个拆开、组装、递给观众。这个协议的好处是方便做多码率切换和网络自适应。比如带宽差的设备只请求低码率分片带宽好的设备请求高码率分片。这就解释了为什么有些 m3u8 清单里不会直接写.ts地址而是继续写了几个子清单——每个子清单对应一种清晰度。#EXTM3U #EXT-X-STREAM-INF:BANDWIDTH800000,RESOLUTION640x360 360p.m3u8 #EXT-X-STREAM-INF:BANDWIDTH2000000,RESOLUTION1280x720 720p.m3u8这种“主清单再指向子清单”的结构是最容易让人懵圈的地方。我第一次处理这种链接时脚本只做了第一层解析结果拉到一堆文字列表而不是视频分片白跑了一趟。后面我把判断分支补齐如果清单里出现#EXT-X-STREAM-INF说明拿到的是主清单还得再请求一次子清单才能看到真正的分片地址。1.3 定位真实地址的关键思路明白了协议结构“隐藏链接”其实就不神秘了。所谓隐藏只是播放器把地址从静态 HTML 转移到了运行时的 JavaScript 逻辑里。常见的存放点有三种页面的某个 JS 文件里有写死的接口路径比如/api/video/config?id123接口返回的 JSON 里带有一个带签名参数的 m3u8 地址播放器库如 hls.js的初始化参数里直接传入了视频清单地址我的思路很简单与其猜不如直接看这个页面运行时到底发了什么请求。Chrome 的 Network 面板里勾选“保留日志”刷新页面然后输入m3u8过滤通常几秒钟就能看到真正的清单地址。这个方法不需要什么高深技巧但它能帮我们绕过所有页面源码的干扰直奔结果。2. 为什么这类活适合让Claude Code来干2.1 我的人工分析流程 vs AI辅助流程在说 Claude Code 之前先说清楚我手工做这件事要花多少步这样才能理解为什么后来我会依赖它节省时间。传统流程大概是打开页面用开发者工具翻半天请求看到一个疑似接口复制到终端拼curl命令遇到 403、token 过期再回页面找生成逻辑写下载脚本时还要处理分片列表解析、并发下载、合并转封装。全部走完顺利的话一两个小时不顺利的话一下午。用 Claude Code 辅助之后流程变成了这样任务环节手工做法Claude Code 辅助分析页面播放逻辑手动读几百上千行 JS寻找接口线索喂页面源码直接问“播放地址从哪来”几秒出分析请求接口测试手拼 curl反复改 header 试错让 AI 生成带默认请求头的命令一条条验证编写下载脚本从零处理分片、并发、异常重试描述需求后 AI 生成完整可运行脚本我只需要检查改动排查报错搜社区、读文档、自己猜多轮追问“为什么报错”AI 能结合上下文给方向这里面最值钱的部分不是“让 AI 替你写代码”而是“让 AI 替你读代码”。页面里的 JS 可能压缩过也可能互相引用人眼去追线索非常累。Claude Code 作为命令行工具能直接读取项目目录里的文件我只需要把 HTML 和 JS 源码保存到当前目录然后像聊天一样问它。2.2 动手前的环境准备和能力边界要用 Claude Code条件其实很简单一个能跑命令行的终端按照工具官方说明完成登录配置然后进入一个目录敲claude就能开始对话。它会自动读取目录里能访问的文件也允许我指定某个文件让它分析。我自己还会提前装好三样东西Python 3 和requests库用来写下载脚本FFmpeg用来合并分片和转封装一个文本编辑器用来临时保存抓下来的源码片段装 FFmpeg 的一句话命令是brew install ffmpegmacOS或者去官网下载对应平台版本Windows 用户也可以用winget install ffmpeg。只要不涉及重新编码合并分片时 FFmpeg 速度极快。但要注意边界Claude Code 不会替我们突破访问权限它既不能读取付费接口背后的加密逻辑也不应该被要求去生成破解 DRM 的代码。如果某个请求返回 401那说明用户身份验证不过关正确的做法是回到页面确认自己有没有权限而不是让 AI 绕过鉴权。这是整个流程的安全底线。3. 实战从页面源码到本地MP4的完整链路3.1 先让Claude Code替我读页面找线索我把页面保存下来后目录结构大致是这样的project/ ├── page.html ├── app.js └── player.js然后在终端里进入这个目录启动 Claude Codeclaude它的工作方式是读取当前项目的上下文。我给它下的第一个指令是请阅读 page.html 和 player.js找出页面里视频播放相关的所有信息。 重点说明播放器初始化时传入了什么地址是否有请求额外接口的逻辑Claude Code 的返回通常不是直接给我一个视频地址而是一份“侦察结论”初步分析 1. player.js 中引入了 hls.js属于 HLS 播放方案。 2. 页面使用了 #videoContainer 作为挂载节点。 3. 播放器初始化时没有直接给 m3u8 地址而是先请求了 /api/lesson/12/config 这个接口。 4. config 接口返回的 JSON 中应该包含 videoUrl 字段 猜测是最终的 m3u8 地址。看到第 3 条我就确定了真实地址藏在那个 config 接口里。这一步如果让我自己读player.js虽然也能找到但至少要花几分钟去追踪几个变量之间的赋值链。AI 帮我跳过了最无聊的部分。3.2 用手工请求验证候选地址是否有效拿到线索后我先让 Claude Code 生成一条curl命令来测试接口curl -s https://example.com/api/lesson/12/config \ -H User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) \ -H Referer: https://example.com/lesson/12接口返回的 JSON 大概长这样{ code: 0, data: { duration: 1832, videoUrl: https://cdn.example.com/videos/lesson12/index.m3u8?tokenabc123expires1700000000 } }我把这个 videoUrl 复制到浏览器里直接打开看到的内容不是二进制而是这样的清单文本#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:10 #EXT-X-MEDIA-SEQUENCE:0 #EXTINF:10.000, segments/00001.ts #EXTINF:10.000, segments/00002.ts ...这说明链接有效真实地址就是它。如果打开是乱码或者 403 页面那就要回到 config 接口的生成逻辑里排查看是不是需要额外的请求头。注意请求接口时务必带上Referer头。很多视频服务端会校验这个字段不带 Referer 的请求很容易被直接拒绝。这个细节我在第一次实战中踩过后面专门整理成了排查表。3.3 完整下载脚本分片拉取、并发、重试一次搞定拿到了 m3u8 地址接下来就是下载。我有两个选择直接用 FFmpeg 一条命令拉流或者写 Python 脚本手动分片下载。大多数情况下我会选后者因为脚本可控性更强能处理断点续传、并发限制也能清楚看到每个分片的下载情况。先看最省事的方案一句话就能完成ffmpeg -i https://cdn.example.com/videos/lesson12/index.m3u8?tokenabc123expires1700000000 -c copy lesson12.mp4FFmpeg 会自己读清单、按顺序请求所有分片并写入 mp4非常方便。但缺点也很明显如果中间某个分片因为网络问题下载失败整个任务就失败了重来一遍成本高。所以我会写一个 Python 脚本把分片一个个拉下来配合并发和重试稳很多。下面是基于这次实战整理的脚本你替换成自己的 m3u8 地址和目录就能用import os import time import requests from concurrent.futures import ThreadPoolExecutor, as_completed SEGMENT_DIR lesson_segments os.makedirs(SEGMENT_DIR, exist_okTrue) M3U8_URL https://cdn.example.com/videos/lesson12/index.m3u8?tokenabc123expires1700000000 BASE https://cdn.example.com/videos/lesson12/ HEADERS { User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7), Referer: https://example.com/lesson/12, Origin: https://example.com, } def fetch_m3u8(url: str) - str: resp requests.get(url, headersHEADERS, timeout15) resp.raise_for_status() return resp.text def resolve_segments(text: str) - list[str]: urls [] for line in text.splitlines(): line line.strip() if not line or line.startswith(#): continue if line.startswith(http): urls.append(line) else: urls.append(BASE line) return urls def download_one(seg_url: str, index: int) - bool: for attempt in range(3): try: resp requests.get(seg_url, headersHEADERS, timeout20) resp.raise_for_status() filepath os.path.join(SEGMENT_DIR, f{index:05d}.ts) with open(filepath, wb) as f: f.write(resp.content) return True except Exception as e: print(f[retry {attempt 1}] segment {index}: {e}) time.sleep(1) return False def main(): text fetch_m3u8(M3U8_URL) segments resolve_segments(text) print(f共发现 {len(segments)} 个分片) with ThreadPoolExecutor(max_workers8) as pool: future_map { pool.submit(download_one, seg, i): i for i, seg in enumerate(segments) } for future in as_completed(future_map): index future_map[future] if not future.result(): print(f分片 {index} 最终失败) print(下载结束可以进入合并步骤) if __name__ __main__: main()脚本的逻辑很直接先请求 m3u8 清单解析出所有分片地址再用 8 个线程并发下载。每个分片如果失败会重试 3 次避免偶发网络抖动导致整个任务失败。文件名用了%05d格式也就是从00000.ts到00120.ts这样排序合并时不会乱序。可能你会问为什么用 8 个线程这个数字是我试过几个并发档位之后折中的结果。太高比如 32容易被服务器识别成异常流量导致部分请求被拒太低比如 2下载几百个分片要等很久。8 是一个在多数场景下稳定又够快的值。3.4 多码率清单的处理分支我这次实战遇到的 m3u8 是直接指向分片的所以比较简单。但前面提到过有些清单是“主清单”里面写的不是分片地址而是多个子清单。这种清单需要在下载前先判断一下if #EXT-X-STREAM-INF in text: lines text.splitlines() for i, line in enumerate(lines): if line.startswith(#EXT-X-STREAM-INF): sub_url lines[i 1].strip() if not sub_url.startswith(http): sub_url BASE sub_url text fetch_m3u8(sub_url) break segments resolve_segments(text)这个分支通常放在fetch_m3u8得到文本之后、resolve_segments之前。加了之后脚本面对多码率清单也能自动挑到第一种子清晰度去下载。如果你想下载固定清晰度可以改成匹配关键词比如选择分辨率里包含720的那个子清单。3.5 合并分片与转封装收尾分片下载完成后目录里是一堆.ts文件得把它们合并成一个完整视频。Linux 和 macOS 下可以直接拼接cat lesson_segments/*.ts combine.ts ffmpeg -i combine.ts -c copy lesson12.mp4Windows 下的命令稍有不同copy /b lesson_segments\*.ts combine.ts ffmpeg -i combine.ts -c copy lesson12.mp4-c copy的含义是不重新编码直接复制流所以速度极快也不会损失画质。如果你打算把这个视频放到网页上做流式播放建议最后再加一步优化ffmpeg -i lesson12.mp4 -c copy -movflags faststart lesson12_fast.mp4faststart会把视频文件的元数据从文件尾挪到文件头播放器开始播放时不用等整个文件的索引读完体验会好很多。这一步不是必须的但如果你保存视频的目的是后续复用值得多做一下。4. 实战血泪6个高频坑和对应排查清单4.1 常见错误速查表这个表是我在这类任务里反复遇到、也反复帮其他开发者解决过的问题汇总这次一并整理出来症状根本原因解决办法请求 m3u8 返回 403服务端校验 Referer 或 User-Agent在请求头里补上页面地址和完整浏览器 UA请求返回 401接口鉴权失败token 过期脚本里先向页面登录接口刷新 token再拼视频地址分片下载了一部分后开始失败签名参数带时间限制过期了检查 m3u8 里的expires确认是否要重新获取清单合并后的视频黑屏或花屏分片下载不完整或顺序错乱检查文件名是否按零填充编号重试失败分片合并后没有声音视频流和音频流是分开的两条清单看主清单里是否还有二级音频清单需要一并下载脚本解析出来一堆子清单而不是分片没处理多码率分支在代码里增加#EXT-X-STREAM-INF判断取子清单位置其中“合并后没有声音”这个坑最隐蔽。我第一次遇到时下载、合并都很顺利播放画面也正常就是没有声音。后来重新翻主清单发现里面除了视频子清单还有一个单独的音频子清单。这种设计通常用于多语言音轨切换播放器会同时请求一条视频流和一条音频流再把它们合成到一起呈现给用户。如果下载脚本只处理了视频分片自然就没有声音。解决思路是在解析主清单时把所有#EXT-X-MEDIA类型的音轨地址也找出来分别下载视频分片和音频分片最后用 FFmpeg 合并两个流。或者更省事的办法这种情况直接用 FFmpeg 一步到位让它自己处理多流的组装逻辑。4.2 判断链接还有没有效的三个手感写脚本之前先用 30 秒判断链接有效性可以省下大量调试时间。我总结出三个动作看 m3u8 地址里的签名参数。常见的参数名有token、expires、signature、auth_key。如果expires时间戳已经过去了那这个链接基本废了需要重新回到页面刷新拿新地址。用curl发一个 HEAD 或 GET 请求看返回状态码。200 说明可访问403 通常是防盗链404 才是真正的地址失效。先下载前三个分片用播放器试播。如果能正常播放说明整个清单是健康的再全量下载。第 3 个手感很重要也最容易忽略。我写过太多次“全量下载跑完才发现清单里某些分片已经 404”的脚本等到播放时卡在一半才发现问题。现在我会在任何全量下载前先做一个“抽样试播”几秒钟就能确认链路是否畅通。4.3 一个值得保留的“半自动”习惯用 Claude Code 这类工具久了我会刻意保持一种“半自动”的协作节奏让 AI 做侦察和初稿但每一步关键结论我都自己验证而不是直接全盘接受。具体做法是让 AI 输出结果后我会接着追问一句请把你得出结论的依据对应到具体代码行或具体请求上。如果 AI 能把答案落到“第几行代码里某个函数请求了某个接口”我才会信任它的结论。如果它含糊其辞那我宁可自己去页面上看一遍。这不是不信任工具而是避免把时间浪费在一条错误的链路上。5. 边界感这段技术能做的事和该做的事5.1 判断“该不该下载”的三个问题这类视频下载技术本身是中性的就像一把螺丝刀可以用来修书架也可以用来拆别人的东西。区别在于使用者的目的和对象。我给自己设了一个简单的检查标准做任何下载前先回答三个问题我是否有权访问这段视频的内容视频的作者或平台是否明确禁止下载下载后的用途是否局限于个人学习、备份或二次创作等合法场景如果三个问题都能给出正面的答案那么技术操作至少站在合理的一侧。如果任何一条不确定我就不会继续。特别是那些需要登录、付费、或者播放器里明确标注“禁止下载”的内容默认不要碰。5.2 关于DRM和加密的一句话说清如果分片清单里出现了#EXT-X-KEY字段说明视频分片带了一层 AES-128 加密播放器需要先拿解密密钥才能播放。这种做法的目的是保护内容和 DRM 属于同类思路。从技术角度讲这种加密确实有办法处理但我不建议把它写进日常操作里。原因很简单当内容方已经展示出保护意图时继续尝试绕过就进入了明确的侵权区间。所以我的处理方式非常简单——遇到#EXT-X-KEY就停下先确认自己有没有授权。公开课程、自有素材、演示页面基本都不会走到这一步。最后说个真实感受。这类“捕获隐藏视频链接”的任务本质上是理解网页播放器原理、熟悉网络请求过程、再写脚本处理流媒体分片三个能力的组合。Claude Code 在其中扮演的不是魔法师而是一个高效的搭档它替我快速完成重复性分析帮我少走弯路但最终的判断和边界感始终得由我自己把关。还有一个小技巧想分享。很多页面的 m3u8 地址虽然带签名参数但签名的过期时间通常和服务器时间绑定而不是你的本地时间。我第一次自己生成下载参数时一直用本地时间拼地址结果服务器反复报过期。后来我在脚本里加了一步先向服务器某个普通接口发一次请求拿响应头里的日期时间做校准再计算签名问题就彻底消失了。这个坑不太常见但遇到一次就知道值多少钱。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑