用Go语言扒一扒duidui365网站缓存的视频,这事儿真没那么玄乎
- 单站
- 2026-06-30 14:19:40
- 222
duidui365的视频缓存到底藏哪儿了?
前几天,我在duidui365网站上看一个挺有意思的短视频,结果网络突然卡了,视频加载到一半就停了,我寻思着,这视频能不能在本地找回来?毕竟缓存(cache)这东西,浏览器一般都存着,然后我翻了翻浏览器缓存目录,发现啥也没有——原来duidui365用的不是传统缓存方式,而是通过某种流媒体协议把视频切成了碎片。
这就勾起了我的好奇心,平时写Go语言比较多,干脆写个小工具,把duidui365网站缓存的视频抓下来试试,用Go写这种“爬虫”类的东西,最大的好处就是并发能力强,而且编译成单个二进制文件,丢到服务器上就能跑,省心。
先搞清楚:duidui365视频的缓存机制长啥样
要“取”缓存,首先得知道缓存是怎么存的,我观察了一下duidui365的视频播放流程:
- 视频不是一次性加载的,而是分成一个个小片段(通常是.ts文件,或者.m3u8索引)
- 每个片段大概几秒到十几秒不等
- 浏览器在播放时会按顺序请求这些片段,存到内存或临时缓存里
- 但浏览器关掉后,这些临时缓存就没了(除非你设了持久缓存策略)
如果用Go写个程序,关键就是模拟浏览器行为,找到那个.m3u8或者视频入口URL,然后一个一个把片段下载下来,最后合到一起,听起来像流水线作业,很符合Go语言的风格:goroutine负责并发下载,channel负责传递数据。
动手写Go代码前的准备工作
我平时写Go习惯用这几个库:
| 库名 | 用途 | 备注 |
| net/http | 发送HTTP请求 | 标准库,够用 |
| github.com/grafov/m3u8 | 解析m3u8文件 | 处理视频索引很方便 |
| os/io | 文件读写 | 标准库 |
| sync | 控制并发 | 小心别把服务器搞炸了 |
至于怎么找到视频的真实地址?我通常会在浏览器的开发者工具(F12)里看“网络”请求,筛选一下“media”或者“xhr”,一般能看到一个叫index.m3u8或者playlist.m3u8的文件,这就是duidui365视频缓存的“地图”。
有些网站会做防盗链,比如检查Referer或者User-Agent,这个也不难,Go里设置一下请求头就行:
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
req.Header.Set("Referer", "https://duidui365.com")
Go语言实战:把duidui365视频缓存拉到本地
来,咱们一步步走,假设我已经找到了一个.m3u8文件的地址,这个文件里面写的是:
- 视频的总时长
- 每个分片的URL(可能是相对路径)
- 分片的顺序
用Go的m3u8库解析一下,就像吃面条一样顺溜:
f, _ := os.Open("index.m3u8")
p, listType, _ := m3u8.DecodeFrom(bufio.NewReader(f), true)
mediaPlaylist := p.(*m3u8.MediaPlaylist)
解析出来后,mediaPlaylist.Segments里面存的就是每一个视频片段的信息,每个片段都有URI字段,指向具体的.ts文件,接下来我写一个下载器,用goroutine同时下载多个片段,不过要注意控制数量,不然duidui365可能会封你IP,我一般设5个并发,温柔一点。
var wg sync.WaitGroup
sem := make(chan struct{}, 5) // 限制并发数
for _, seg := range segments {
if seg == nil { continue }
wg.Add(1)
go func(uri string) {
defer wg.Done()
sem <- struct{}{}
defer func() { <-sem }()
// 下载并保存这个片段
downloadAndSave(uri)
}(seg.URI)
}
wg.Wait()
下载下来的是一堆.ts文件,还不能直接播放,需要用ffmpeg或者自己写个合并逻辑,把文件按顺序拼成一个mp4,ts文件本身是MPEG-TS格式,直接拼接就可以了,但注意不要漏掉任何片段,否则视频会卡顿或花屏。
合并代码大概长这样:
outFile, _ := os.Create("output.ts")
for i := 0; i < len(segments); i++ {
data, _ := os.ReadFile(fmt.Sprintf("seg_%05d.ts", i))
outFile.Write(data)
}
outFile.Close()
如果你想转成mp4,可以在命令行里调用ffmpeg:
exec.Command("ffmpeg", "-i", "output.ts", "-c", "copy", "output.mp4").Run()
遇到过的坑:duidui365网站的反爬和加密
写这个工具不是一路顺风的,duidui365网站有时候会给.m3u8文件做个加密,比如加个AES-128加密,这时候每个.ts片段都是加密的,你得先从.m3u8里找到#EXT-X-KEY这一行,里面有个URI指向密钥文件,还有个IV(初始化向量)。
用Go解密也很简单:
keyData, _ := downloadKey(keyURL)
block, _ := aes.NewCipher(keyData)
decrypter := cipher.NewCBCDecrypter(block, iv)
decrypter.CryptBlocks(ciphertext, ciphertext)
另外一个坑是:有些视频的.m3u8文件会动态生成,链接里带时间戳或者token,过几分钟就失效,所以我都是先打开视频页面,马上复制m3u8链接,再让工具去下载,这样成功率比较高。
还有,duidui365可能用了CDN,不同地区的用户拿到的分片URL不一样,这个倒不影响,因为.m3u8文件里写死的,跟着走就行了。
为什么用Go?因为快,而且写起来不费劲
我试过用Python写类似的工具,但duidui365的并发一高,Python的GIL(全局解释器锁)就卡得不行,Go就不一样了,goroutine轻量得要命,开几千个都没事(当然开那么多会把人家服务器搞炸,别干坏事)。
而且Go的交叉编译太香了,我在Windows上写代码,然后GOOS=linux GOARCH=amd64 go build,直接生成一个Linux可执行文件,丢到VPS上一跑,完事,不用装Python环境,不用装一堆依赖包。
错误处理虽然啰嗦了点,但贵在清晰,每个错误你都看得见,不会像Python那样静默吞掉异常然后段错误。
一些实用小技巧:怎么避免被封
你要是频繁抓取duidui365的视频缓存,肯定会触发反爬机制,我的经验是:
- 加随机延时:每个请求之间睡几百毫秒,别太规律
- 换User-Agent:每次请求换一个,模拟不同浏览器
- 不要一次性下载太多视频:一次拿一两集就行了,分时段搞
- 用代理池:如果资源重要,买点住宅代理,成本不高
Go语言里实现随机延时很简单:
time.Sleep(time.Duration(500+rand.Intn(1000)) * time.Millisecond)
完整工具的结构整理
我的小工具大概长这样,结构很清晰:
- main.go:入口,读入m3u8链接,启动流程
- downloader.go:负责下载单个分片,支持重试和进度显示
- merger.go:把分片合并成完整视频
- decrypt.go:如果要解密AES,就走这个逻辑
- config.go:设置并发数、重试次数等参数
代码量不大,大概300行左右,比起那些复杂的爬虫框架,这就是个“玩具”,但确实能解决问题,我有一次想重温duidui365上的一部老纪录片,页面都404了,但缓存还活着,硬是靠着这个工具把视频拽回来了,倍儿有成就感。
得说明一点:下载别人的视频要尊重版权,我只是用来学习Go语言和了解流媒体协议,可别拿来做商用或者恶意传播,费曼说过,不能理解的东西就不要声称学会了,我写这段代码,就是把“duidui365缓存视频”这个现象拆解成问题、方案、代码,一步步搞清楚,自己也算真的懂了。
最后说句实话,这工具我后来没怎么用了,因为duidui365改版了几次,加密方式也变了,但写这个工具的过程,让我对HTTP协议、流媒体封装、并发控制的理解上了一个台阶,如果你也对Go语言和视频缓存感兴趣,不妨自己动手试试——踩坑的过程,才是最值钱的学习。
