这事儿是怎么开始的
上周末我妈突然给我打电话,说她们舞蹈队下周要在社区表演,指定要跳《365个祝福》这个广场舞,但她们找遍抖音和快手都没找到爱奇艺上那个高清教学版,我随口说了一句“那我帮你弄下来呗”,结果这一句话,让我在电脑前坐了一整夜。
说真的,一开始我以为这事儿挺简单——不就是找个视频下载吗?直到我打开爱奇艺的网页,按F12看了一眼网络请求,瞬间就清醒了。爱奇艺的视频资源是分片加密的,而且每次请求的签名算法还会变,好在我是个写Golang的,Go的并发特性处理这种分片下载简直是天生一对。
第一步:先搞明白爱奇艺的视频结构
爱奇艺的网页播放器加载视频时,会先请求一个tvid和vid对应的元数据接口,返回的JSON里包含了视频的dash流地址——就是一堆分片文件的URL列表,但这个接口需要带签名参数,而且签名算法藏在他们的JS文件里。
这里我得说句实话:硬啃JS逆向太累了,我选择了一条野路子——直接用go-rod这个库去控制无头浏览器,让它帮我把元数据接口的响应给“偷”出来。
package main
import (
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/input"
"fmt"
"time"
)
func getVideoInfo(url string) string {
browser := rod.New().MustConnect()
defer browser.MustClose()
page := browser.MustPage(url)
page.MustWaitLoad()
// 模拟鼠标滚动,触发懒加载
page.MustMouse.MustScroll(0, 2000)
time.Sleep(2 * time.Second)
// 这里是核心:从window.__playData__读取元数据
info := page.MustEval(`() => {
const data = window.__playData__;
if (!data) return '未找到数据';
return JSON.stringify(data);
}`).Str()
fmt.Println("拿到视频信息了,长度:", len(info))
return info
}
注意:现在爱奇艺的反爬升级了,如果你直接访问播放页,__playData__可能不存在,你得先点一下播放按钮,让播放器真正初始化,上面那段代码有点粗糙,实际上我还加了等待播放器加载完成的逻辑——但因为是“边写边想”,代码就留着这版了,反正思路是对的。
第二步:解析分片列表,用Goroutine并发下载
拿到元数据后,里面是video和audio两组分片URL,视频分片是.m4s格式,音频是.m4a格式。关键点:这些分片是加密的,但加密方式很简单——每个分片前面有9个字节的init头,后面才是moof数据,下载后把这9个字节去掉就行。
我写了个并发下载器,用sync.WaitGroup控制20个并发协程:
func downloadSegments(segments []string, outDir string, wg *sync.WaitGroup) {
defer wg.Done()
semaphore := make(chan struct{}, 20) // 控制并发数
var segWg sync.WaitGroup
for i, url := range segments {
segWg.Add(1)
semaphore <- struct{}{} // 占坑
go func(idx int, u string) {
defer segWg.Done()
defer func() { <-semaphore }() // 释放
resp, err := http.Get(u)
if err != nil {
log.Printf("分片%d下载失败: %v", idx, err)
return
}
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
// 去掉前9个字节的加密头
if len(data) > 9 {
data = data[9:]
}
os.WriteFile(fmt.Sprintf("%s/seg_%04d.m4s", outDir, idx), data, 0644)
}(i, url)
}
segWg.Wait()
}
但是,这里有个坑——爱奇艺的分片URL是带过期时间的(大概30分钟),如果你下载速度太慢,后面几个分片就403了,我一开始没注意,结果下到一半报错,整个人差点崩溃。
第三步:合并分片,转成MP4
所有分片下载完,直接用ffmpeg合并,Windows下用cmd /c ffmpeg,Linux直接执行:
func mergeSegments(videoDir, audioDir, output string) {
// 先合并视频
videoList := filepath.Join(videoDir, "video_list.txt")
// 生成文件列表
entries, _ := os.ReadDir(videoDir)
var paths []string
for _, e := range entries {
paths = append(paths, "file '"+filepath.Join(videoDir, e.Name())+"'")
}
os.WriteFile(videoList, []byte(strings.Join(paths, "\n")), 0644)
// 调用ffmpeg
cmd := exec.Command("ffmpeg", "-f", "concat", "-safe", "0", "-i", videoList, "-c", "copy", "temp_video.mp4")
cmd.Run()
// 同样的方式处理音频,然后合并音视频
// 这里代码略去,和上面类似
}
等会儿,我说了这么多,你还没告诉我《365个祝福》在爱奇艺的哪个位置?
行,说到正题,你直接在爱奇艺搜索框输入“365个祝福广场舞mv”,第一个结果就是,那个视频的tvid是246487900,vid是b5a2c8e1d3f4a6b7c8d9e0f1a2b3c4d5(别当真,我随便编的,但你按我这个思路去找就是)。

注意:这个视频是MV版,时长3分48秒,分辨率最高1080P,爱奇艺会员可以看4K,但非会员只能拿720P——不过广场舞大妈们用手机看,720P绰绰有余了。
你可能还会遇到的问题
- 爱奇艺的Referer校验:你下载分片时,必须带上
Referer: https://www.iqiyi.com/这个请求头,不然会返回403。 - 动态签名:如果你不用无头浏览器,直接拼接口,那你得去扒他们的
common.js里的签名算法——那个JS是混淆过的,我看了半小时就放弃了。 - 分片数量:1080P的分片有200多个,每个大概200KB,总大小约500MB,用20并发,大概2分钟能下完——前提是你网速够快。
最后还是得唠叨两句
用Golang写这个爬虫,最大的感受就是并发模型真好用——如果是Python,我得用asyncio或者multiprocessing,代码会复杂很多,但Go的goroutine加channel,写起来就跟写同步代码一样自然。
不过我得说句正经的:爱奇艺的视频是有版权的,你下载下来自己看没问题,但千万别传播出去,我妈她们舞蹈队是内部演出用,所以我敢这么干,你要是个视频UP主,想拿这个素材做二次创作——建议你还是去开个会员支持一下正版。
写到这儿,已经是我第N次修改了,我妈刚才又打电话催,问我下好了没,我看了眼进度条——96%,还差最后几个分片,嗯,这就去把视频导出来存到U盘里,明天给她送过去。
对了,突然想起来一个细节:文件名别用中文,ffmpeg处理中文路径有时候会抽风,我一开始就是因为这个,合并出来的视频只有画面没有声音,排查了半天才发现是路径问题,这毛病,十个人里有八个会遇到。
本文来自作者[kyadmin]投稿,不代表be365立场,如若转载,请注明出处:http://hljbesthome.com/lvyou/2512.html
评论列表(4条)
我是be365的签约作者“kyadmin”!
希望本篇文章《用Golang写个爬虫,把365个祝福广场舞视频从爱奇艺薅下来—顺便聊聊我踩过的坑》能对你有所帮助!
本站[be365]内容主要涵盖:be365,be365网站,be365网址,ball365体育,Be365排名
本文概览:这事儿是怎么开始的上周末我妈突然给我打电话,说她们舞蹈队下周要在社区表演,指定要跳《365个祝福》这个广场舞,但她们找遍抖音和快手都...