blob链接真相揭秘:从流媒体MSE分片到视频下载的完整指南
做前端或者成天折腾网页抓取的人应该都对这种链接不陌生blob:https://example.com/5f6d7a8b-9c10-4d1e-8f2a-3b4c5d6e7f8a。视频明明放得好好的右键想复制播放地址拿到的却是这串东西粘到新标签页直接白屏。很多人第一反应是“这又是哪家平台的防盗链新招数”其实把blob当成防盗链是一个很常见的误会。它背后真正牵着的是一整套流媒体分片播放和 Media Source Extensions 的链路。这篇文章我想把 blob 链接的底细彻底拆开——它怎么来的、视频数据走的是什么路径、防盗链到底发生在哪个环节再聊聊实际情况下保存视频时那些能落地的做法。这篇文章适合这几类人前端开发者想知道视频标签为什么挂了个诡异的 blob 地址做爬虫和数据分析的同学想搞清楚视频真实地址在哪还有视频站点的运营者想弄明白自己辛辛苦苦做的防盗链到底防了什么、没防什么。看完你至少能判断一个网页视频能不能保存、该走哪条路去拿以及哪些情况想都不要想。1. 先拆开看blob链接到底是什么东西1.1 blob协议和http协议一个门牌号一个座位号要理解 blob 链接最核心的一点是它不是网络地址根本不指向任何服务器上的资源。http://开头的 URL相当于一个餐厅的门牌号浏览器拿着它走网络请求服务器响应内容才拿到数据。而blob:开头的 URL是浏览器在本地创建的一个“座位号”——数据已经在你电脑的内存里了这个地址只是告诉你“去第几桌找你那份菜”。这个“座位号”由一行代码生成// 把一段二进制数据交给浏览器生成一个本地引用地址 const videoBlob new Blob([arrayBuffer], { type: video/mp4 }); const blobUrl URL.createObjectURL(videoBlob); video.src blobUrl;URL.createObjectURL()接收一个Blob或File对象返回一个类似blob:https://域名/uuid的字符串。这个字符串的有效期和页面生命周期绑定页面关掉或者你调用URL.revokeObjectURL()它就彻底失效。所以你会发现从旧页面复制的 blob 链接换个地方打开必然白屏——因为那个“座位”在原地不在新餐厅里。这里有个值得注意的细节blob 链接里带着当前页面的域名所以它表面看像是一个指向这个域名的资源其实域名只是用来标记这个 blob 的“归属地”真正的数据此刻可能就躺在http://站点域名/对应的那个标签页进程的内存里。1.2 视频为什么需要这种“临时门牌号”很多人问为什么视频播放不用正经的.mp4地址要搞一个临时的 blob答案要分几个场景看。第一个场景是本地预览。用户上传了一个视频你在没上传成功之前想让用户看到效果这时候文件在浏览器本地从input[typefile]拿到File对象直接createObjectURL塞给video就能播不需要先传服务器再拉回来。这一步几乎是所有带视频上传功能的站点都在用的方案。第二个场景也是更核心的场景流媒体分片。当视频动辄几十分钟、几个GB平台不可能让你一口气把整个文件拉下来。它们会把视频切成几百上千个小分片TS 或 MP4 片段浏览器拿到分片后需要通过MediaSource对象把一块块数据“拼接”成一段连续的媒体流再喂给video。而video的src属性只接受 URL这时候就需要一个“本地地址”来占位指向这个不断在生长的 MediaSource——blob 就是干这个的。这个场景后面我会详细展开它是理解整个问题的关键。第三个场景是绕开跨域限制。如果你在一个网页里动态生成了一段音频或视频数据想直接在页面上播放用data:URI 会碰到长度限制和性能问题用blob:则可以在不产生网络请求的前提下完成播放而且不会触发跨域问题。1.3 blob、data URI和File三者的关系我画个表格放在这里方便你从全局理解这几个概念的边界。类型地址/内容长度数据存储位置跨页面有效典型用途http(s)://短服务器磁盘/缓存长期可被收藏分享页面资源、API接口blob:https://域名/xxx短浏览器内存或临时文件仅当前页面生命周期内本地预览、MSE分片喂流data:video/mp4;base64,....随内容变长当前文档内单次页面内小图片、小文件内嵌File对象不参与URL浏览器内存/用户磁盘属于JS运行时对象文件上传、本地处理Blob是数据类型File继承自Blobdata:URI 是把数据本身嵌入进地址而blob:URI 只是一个引用。用在视频场景时blob:几乎总是配合MediaSource使用——这一点在下一章会明白。2. 防盗链和blob很多人的第一个误会2.1 传统防盗链手段都有哪些在聊清楚误会之前先看看真正的防盗链长什么样。防盗链的本质是两个问题第一“你有没有资格拿这个资源”第二“你能拿多久”。围绕这两个问题业内有一套组合拳。最基础的是Referer校验。服务器检查请求头里的Referer字段如果发现来源页面不在白名单里直接 403。这个方案因为Referer可以被伪造现在已经只是入门级防护。稍微进阶的是Token签名和时效 URL。服务器生成一个带过期时间的签名塞进 URL 参数里比如https://cdn.example.com/video.mp4?signabcexpires1720000000。CDN 校验签名过期时间过期就拒。这种方案的目的是防止有人把地址扒下来到处传播但你拿到地址后在一定时间内还是能直接下载的。再往上是更严格的组合请求头自定义字段、Cookie 鉴权、IP 限频、UAUser-Agent校验。比如要求请求时必须带一个某某自定义的 Header值为一段加密字符串。这种主要是为了防脚本因为浏览器播放器本身发出的请求所有 Header 都是相对固定的而你用脚本去下载时很容易漏掉自定义字段。注意这些防盗链手段全部发生在网络请求层——也就是客户端向服务器拿数据的那一刻。它们的共同特点是要么让你拿不到真实地址要么让你拿到地址但没权限用要么让你能用但在短时间内失效。2.2 blob凭什么被当成防盗链那么 blob 和防盗链有什么关系答案是没有直接关系但它经常给人造成“防盗链很成功”的感觉。场景还原一下你打开一个视频页面播放器正常播放开发者工具里Network面板全是些看不懂的m4s或ts请求视频标签的src却是blob:https://...。你没法从开发者工具里直接右键复制一个.mp4地址也没法用下载工具直接抓到一个静态文件。看起来像是防盗链把你挡住了。真实情况是视频成功在你这播放了数据也已经到了你电脑上。只是客户端用的是“播放器代码 分片请求 自定义协议”的方式在取数据而不是简单地访问一个.mp4。blob 在里头只是一个“中转容器”承载的是浏览器本地MediaSource的输出——它既不是服务器地址也不是加密产物。换句话说blob 把“获取真实媒体地址”这件事变成了“必须搞清楚它的播放器逻辑才能拿到真实地址”变相提高了门槛。但这只是门槛高不是拿不到。真正的防盗链是那层在请求分片时校验的东西比如动态签名的 m3u8 地址、几秒过期的一次性 token、要求带特定 Header 的 CORS 策略。你在网页里看见 blob只能说明这个站点用了流媒体播放方式不能说明它还额外加了几道锁。2.3 想保护视频资源真正该做的是这些如果你是自己运营视频站点、正在纠结要不要把 mp4 换成 blob 来防盗链我的建议是把 blob 当“体验方案”别当“安全方案”。blob MSE 解决的核心问题是首屏秒开、按需加载、断网续播、码率自适应。这些是产品体验层面的价值。而在保护内容层面你应该把精力放在这几个地方一是分片地址动态化。每个分片都用短时效签名过期之后即使有人拿到 URL 也访问不了。这是目前性价比最高的手段几乎所有主流平台都在用。二是请求鉴权前移。播放前先让你拿到一个带权限的播放凭证每个凭证绑定会话、设备指纹和过期时间。分片请求时校验凭证。一旦凭证过期或换设备重新申请。三是合理使用 DRM。对于高价值版权内容使用 Widevine、FairPlay 这类 DRM 方案结合硬件级的安全等级。但要注意DRM 是有体验代价的它限制你播放器和浏览器版本还会带来兼容性问题。如果你不是平台级产品其实不太需要上。这层想明白之后你就会懂得看到 blob 不用觉得“完了这拿不到”而是应该去想“服务器给的播放清单m3u8/mpd在哪、分片签名有效期多长”。顺着这个思路保存视频就有了明确的切入点。3. 流媒体分片blob背后真正的数据管线3.1 为什么主流视频平台不直接传MP4假设你在写一个视频平台简单粗暴的方案是后端存一个movie.mp4前端video srchttps://.../movie.mp4完事。这个方案在视频很小、用户很少、网络环境理想的时候没问题但一旦规模上来痛点立刻出现。首先是首播等待。一个 2GB 的 MP4用户在弱网环境下要等 MP4 的moov元数据加载到足够位置才可能开始播放那个转圈圈的几十秒足够劝退用户。其次是带宽浪费。用户只看前 3 分钟服务器却要按最大码率把整个文件吐出来。然后是自适应做不到——4K 要推送 4K 分片720p 要推送 720p 分片整段文件不好灵活切换。流媒体分片的核心思路和杂志连载一个道理。你不一次性把整本书给读者而是每期发一章。读者看了这章觉得好再给他发下一章。视频平台把完整文件切成若干时长 2~10 秒的碎块浏览器播完一个再请求下一个。同时视频和音频还可以分轨存储视频轨一段段发音频轨一段段发播放器把它们对齐组合。分片模式下用户看到第 5 秒时浏览器才拉完第 1 个分片看到第 2 分钟时才拉完第 12 个分片。网络差就切换低码率分片网络好就切高码率分片这一切都在播放过程中无缝完成。3.2 HLS和DASH两套主流分片协议目前最主流的两个分片协议分别是 HLS 和 DASH。HLSHTTP Live Streaming是苹果提出的方案分片格式为 TSMPEG-TS或较新的 FMP4索引文件是m3u8。它的优势是生态成熟iOS 和 Android 的浏览器几乎都原生支持服务器端的基础设施也非常丰富。我们今天看很多视频站点的流地址基本上都是.m3u8结尾。DASHDynamic Adaptive Streaming over HTTP是更通用的标准索引文件叫 MPD分片一般是 FMP4。它在多音轨、多字幕、多码率的组织上比 HLS 更灵活标准也更现代但因为各家浏览器支持不统一在网页端通常需要搭配MediaSource和dash.js这类 SDK 来播放。这两个协议的共同点是都通过一个索引文件去描述“视频有哪些分片、每个分片 URL 是什么、音视频轨怎么组合”。也就是说拿到了 m3u8 或 MPD就等于拿到了整部视频的地址清单。3.3 MSE浏览器里的分片“装配线”有了分片和索引浏览器怎么把它们变成连续的视频流这就是 Media Source ExtensionsMSE的活。打个比方video标签是一个需要被投喂的“放映机”它不认一堆碎片文件只认一段连续的媒体流。MSE 就好比在浏览器里搭了一个“装配车间”你用 JS 创建一个MediaSource对象等它触发sourceopen事件后添加一个或多个SourceBuffer分别用于视频轨道、音频轨道然后不断把下载好的分片数据通过appendBuffer()塞进 SourceBuffer。装配线一边往缓冲池里加料放映机一边播放。MSE 和 blob 的关系在这一刻才真正显现MediaSource对象本身不是一个 URL但video的src需要一个 URL所以浏览器用URL.createObjectURL(mediaSource)生成一个 blob 地址来占位。你在开发者工具里看到的blob:其实指向的是这个正在被动态填充的 MediaSource。完整的播放链路是这样的页面加载播放器 JS拿到播放凭证播放器请求 m3u8 或 MPD 索引文件解析索引拿到分片列表创建MediaSource生成 blob URL赋给video循环请求分片文件appendBuffer喂给 SourceBuffer视频播放过程中按需淘汰缓冲里面不需要的数据。理解了这条链路再去看 Network 面板里那些零零碎碎的视频请求你就知道它们不是一个一个孤立的下载而是整条流水线上的工序。想保存视频本质上是把这条流水线上“喂”给浏览器的那份完整数据在本地重新组装成文件。4. 实操怎么把blob视频完整保存下来4.1 第一步判断视频到底走没走MSE保存视频之前先判断你面对的是哪种情况。这决定了后续用什么手段。打开开发者工具的Media面板Chrome 系浏览器都有播放视频看看里面有没有显示Player Properties、Buffer、Audio/Video Tracks等信息。如果有说明走的是 MSE 分片播放。这一步非常关键甚至比抓地址还重要——它能帮你快速区分“真 blob内存数据”和“流媒体 blobMSE 占位”。两种情况的表象都是srcblob:...但本质完全不同。纯 blob 视频数据在浏览器内存里可能来自用户上传、JS 拼接、Canvas 录制等。媒体面板里不会出现 MSE 信息。MSE 流媒体视频分片从网络请求而来通过 SourceBuffer 喂给播放器。媒体面板里会出现明确的分片缓冲情况和音视频轨道信息。看Network面板也能辅助判断。如果视频在播放过程中持续出现大量.ts、.m4s、.m4a、.mp4的请求且 URL 的域名和路径风格与普通静态资源明显不同那基本就是流媒体。4.2 纯blob视频的保存方法如果是纯 blob 视频保存思路最直接数据本来就在浏览器内存里你只需要把 blob 转成一个文件并触发下载。在页面控制台里执行这样一段脚本可以把这个 blob 视频导出来// 找到 video 标签 const video document.querySelector(video); // 拿到 blob url const blobUrl video.src; // 拉取 blob 数据 const resp await fetch(blobUrl); const blob await resp.blob(); // 创建本地下载链接 const a document.createElement(a); a.href URL.createObjectURL(blob); a.download video.mp4; a.click();这段脚本的原理是fetch能直接请求 blob URL 并把数据以Blob形式返回然后我们用URL.createObjectURL生成一个纯本地的下载链接用a download触发下载。注意download属性填的文件名后缀最好与blob.type一致否则有的设备不认识。如果页面有 JS 对 blob 数据做了分片拼接你直接取video.src拿到的可能是不完整的。更稳妥的做法是找到页面代码里真正持有完整Blob或ArrayBuffer的那个对象但这比较依赖具体页面结构通常只在写定制脚本时才会去搞。普通场景下直接下载就是最快路径。这种场景常见于用户在网页里本地预览上传视频、你用 Canvas MediaRecorder 录制的 WebM 视频、以及某些通过 API 一次性返回完整视频二进制并在本地播放的轻量站点。4.3 流媒体分片视频的抓流与合并遇到流媒体分片视频直接下载 blob 没有意义——因为那个 blob 指向的 MediaSource 是一个“活的水池”数据流是被不断灌进去又放出来的你 fetch 不到完整影片。正確的做法是去拿它背后的索引文件和分片列表。操作路径通常是这样先在Network面板里过滤m3u8或mpd刷新页面开始播放找到那个索引文件请求。如果找不到也可以搜m3u8、mpd、playlist这几个关键词。拿到索引 URL 后直接在浏览器地址栏访问可以看到里面是一长串分片地址列表。注意很多平台给的 m3u8 地址带动态签名有效期可能只有几分钟到几小时所以拿到地址之后要尽快处理。拿到索引地址之后最简单省事的工具是 ffmpeg。# 直接拉取整个流并合并视频音频一起处理 ffmpeg -i https://example.com/path/index.m3u8 -c copy -bsf:a aac_adtstoasc output.mp4-c copy表示不重新编码直接拷贝原始数据流速度很快且没有画质损失。-bsf:a aac_adtstoasc是为了处理 TS 里面的 AAC 音频流在封装到 MP4 时的一个格式转换问题不加这条部分视频放出来会没有声音或者音画不同步。如果平台做了更严的防护比如要求每个分片都有独立的临时签名那就需要脚本监听Network面板把分片请求的 URL 逐个保存下来再用拼接工具合并。开源工具N_m3u8DL-RE是这方面比较靠谱的选择它支持自定义请求头、token、多线程下载、自动合并比 ffmpeg 在需要精细控制请求参数时更好用。我自己在实际操作中遇到最多的情况是 M3U8 里有 KEY 加密或 parse 特殊字段的情况。此时 ffmpeg 会自动在解密时尝试读取 key 文件的地址如果 key 地址也是动态的就需要手动把 key 文件先保存到本地再通过修改 m3u8 的#EXT-X-KEY那一行来指向本地文件。# 先把 key 文件拿下来然后改 m3u8 里的 key 地址为本地路径 ffmpeg -i local_playlist.m3u8 -c copy output.mp4这种做法在抓自己账号有权访问的流时是可行的但要注意如果平台使用了 DRM数字版权管理加密比如 Widevine 的pssh字段这条路就走不通了。那意味着视频内容经过了硬件级加密不是靠抓包能解的。任何声称能“解密 DRM”的工具都不该碰。4.4 “把blob url转file”的实现细节“把 blob url 转 file”是很多人搜过的需求也是处理上传业务时常见的逆向场景。它本质上是把blob:地址对应的二进制数据还原成一个File对象方便塞进FormData提交给后端。// 将 blob url 转为 File 对象 async function blobUrlToFile(blobUrl, filename) { const response await fetch(blobUrl); const blob await response.blob(); // File 继承自 Blob可以直接构造 return new File([blob], filename, { type: blob.type }); } // 使用示例 const file await blobUrlToFile(blob:https://example.com/xxxx, cover.jpg); const formData new FormData(); formData.append(file, file); await fetch(/api/upload, { method: POST, body: formData });这里有个容易被忽略的小坑File构造函数里的filename不能随便写。如果后端按扩展名判断文件类型而你的文件名没有后缀后端可能识别失败。比较好的习惯是根据blob.type推断出扩展名赋给filename。比如blob.type是image/jpeg文件名就写成.jpg后缀。另外如果页面里做了 CSP内容安全策略限制fetch(blobUrl)有可能被拦截。这时可以尝试直接用XHR请求或者直接把Blob对象本身从页面上下文里取出来——这需要你熟悉页面的 JS 运行环境一般配合油猴脚本才能搞定。5. 常见报错与排查技巧实录5.1 blob链接为什么换个页面就打不开这是最经典的一个问题你在视频页里把 blob 地址复制下来粘到新标签页或者发给朋友打开是白屏。原因前面已经说了blob 是浏览器的本地临时地址作用域仅限于创建它的标签页和对应的这些二进制对象。换页面等于换了餐厅座位号当然失效。知道了这个原理你在调试时就该明白别指望收藏 blob URL也别问别人要 blob URL。正确做法是抓它背后的 m3u8 或分片地址而不是 blob 地址本身。很多新手在这里绕了很久以为是自己没抓到真正的资源其实方向就错了。5.2 合并完只有画面没有声音用 ffmpeg 下载 m3u8 合并成 MP4 之后发现没声音大概率是两个原因。第一种原因是 TS 分片里音频是 AAC 格式直接封装 MP4 需要aac_adtstoasc滤镜转换。解决方法是把命令改成-bsf:a aac_adtstoasc。第二种原因是播放器把视频轨和音频轨分成了两个不同的分片列表你在 m3u8 里看到的是纯视频轨音频轨是另一个 m3u8文件名通常带audio字样。解决方法是先把两条流都下载下来再用 ffmpeg 合并# 先分别下载视频轨和音频轨 ffmpeg -i video_only.m3u8 -c copy video.mp4 ffmpeg -i audio_only.m3u8 -c copy audio.m4a # 再合并 ffmpeg -i video.mp4 -i audio.m4a -c copy merged.mp4这里建议先单独检查一下两个文件是否能正常播放再合并能更快定位问题出在哪一路。5.3 播几秒黑屏动态签名的锅有些平台的 m3u8 或者分片 URL 里带一个很短的签名过期时间比如 30 秒。你慢悠悠地打开 m3u8、看一会儿、再发起下载结果发现用工具下载时前面几个分片下下来了后面的全 403。这种情况解决思路是把整个下载过程自动化拿到 m3u8 后立刻开始下载中间不要停顿。如果你用的是 N_m3u8DL-RE可以设置较高的并发数它在分片过期前把任务拉完。如果手动用 ffmpeg遇到 403 就立刻重新抓一次新签名重新执行命令。多试几次你会发现整个抓取过程最好限制在签名有效期内完成。5.4 遇到DRM加密怎么办有的视频服务用的是 Widevine 或 FairPlay 这类 DRM 方案播放器请求的是加密分片媒体数据在解密和渲染之前都是加密状态。你即使把分片全部下载下来没有授权也就无法解码。这种情况下网页端的 blob 只是表象真正的版权保护在更底层。面对 DRM我的建议很简单放弃。不是技术上的完全不可行而是这个方向涉及的东西牵扯到法律风险和技术复杂度普通人碰它百害无利。如果你的需求是合法的——比如你是某个平台的付费用户想把视频存到本地缓存看——正常的平台都会提供官方离线缓存功能请用官方渠道。如果是想录屏或二次分发那本身就是违规甚至违法行为。这个边界我觉得每个做技术的人都得有。技术可以用来解决正当需求比如自己拍的视频、自己网站的播放器调试、研究流媒体协议但不要去拿它破解别人的版权保护机制。这不仅是对别人劳动成果的尊重也是在保护你自己。写在最后一点经验之谈这套东西我刚开始折腾的时候也绕了不少弯路。看到 blob 链接就以为是无解后来弄明白 MSE 的原理才反应过来blob 只是个影子真正藏在水面下的是那条“索引文件 分片 鉴权”的流媒体链路。现在我拿到一个网页视频第一件事不是去复制地址而是打开开发者工具的 Media 面板和 Network 面板先判断走的是纯内存 blob 还是 MSE 流媒体然后再决定下一步。这个判断做对了后面 80% 的时间都能省下来。最后再给你一个实操小技巧处理这类问题时务必保持开发者工具全程开着尤其是Network面板的Preserve log选项一定要勾上。因为很多平台的播放器在首屏就请求了 m3u8之后就不再请求你不开 Preserve log刷新后很容易眼睁睁看着那个关键请求消失还得来回折腾几遍。这个细节我踩过太多次希望你别再踩一遍。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →