台北VS韩国棒球直播视频,用Golang写个爬虫,把这场硬仗存下来
- 技巧
- 2026-09-02 04:12:49
- 31
昨晚那场比赛,我看得差点把啤酒捏爆
老实讲,昨晚台北队对阵韩国队那场棒球直播,我是在客厅用平板看的,画面卡了三次,弹幕里全是“干”“守住”“三振他”这种词,我一边骂骂咧咧一边想:要是能把这直播视频直接抓下来,回头慢慢复盘每个投手的动作该多好,我这个写Go的码农,职业病犯了——用Golang写个抓取台北vs韩国棒球直播视频的工具,顺便把踩过的坑全记下来,这篇文章不是啥高深教程,就是边写边想的过程,你跟着走一遍,至少能明白怎么把一段线上直播流变成你硬盘里的MP4。
为什么是Golang?因为并发抓流真的爽
选Go不是因为它帅,是因为它的goroutine处理并发请求太顺手了,棒球直播视频通常是HLS(HTTP Live Streaming)格式,也就是一个.m3u8索引文件下面挂了几百个.ts切片文件,你要把这堆切片按顺序下载下来,再合并成一个完整视频,传统语言写起来要开线程池、管理锁,Go直接go func()丢出去就完事,channel收结果,干净利落。
Go的net/http标准库足够强大,处理重定向、自定义Header、Cookie这些直播平台喜欢搞的花活,基本不用引第三方包,你要是用Python,还得装requests、m3u8、ffmpeg-python啥的,Go这边一个os/exec调ffmpeg,或者干脆用github.com/grafov/m3u8这个库解析索引文件,就搞定了。
第一步:拿到直播流的真实地址
比赛直播视频通常不会直接把.m3u8链接甩给你,得从网页源码或者接口里挖,我用的是Chrome开发者工具的Network面板,先过滤掉图片和CSS,找带m3u8或者ts后缀的请求,台北这场比赛的源站是某个体育直播平台,链接长这样:
https://live.example.com/abc/playlist.m3u8?token=8f3k...
这个token会过期,所以你得在比赛开始前抓,或者像我不小心错过开场,只能去扒回放源,回放源更坑,有的直接给你一个加密的.key文件,跟.ts切片分开存放,这时候Go的http.Client就得带上Referer和User-Agent头部,不然返回403。
代码片段(边写边改):
client := &http.Client{
Timeout: 30 * time.Second,
}
req, _ := http.NewRequest("GET", hlsURL, nil)
req.Header.Set("User-Agent", "Mozilla/5.0...")
req.Header.Set("Referer", "https://live.example.com/")
resp, err := client.Do(req)
// 读完resp.Body,就是一个包含多个.ts文件名的m3u8文本
第二步:解析m3u8,别被嵌套加密坑了
那个m3u8文件内容大概长这样:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:8
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=AES-128,URI="key.php?id=123",IV=0x...
#EXTINF:8.0,
seg_0001.ts
#EXTINF:8.0,
seg_0002.ts
...
这里最关键的是#EXT-X-KEY,如果是AES-128加密,你得先下载那个key文件,然后用Go的crypto/aes解密每个ts切片,不过说实话,我昨晚抓的这场没加密,可能是地区限制不严,但为了保险,我还是写了个函数判断:
if strings.Contains(line, "#EXT-X-KEY") {
// 解析KEY的URI,下载key,设置decryptor
}
如果你不想自己处理加密,直接用ffmpeg -i playlist.m3u8 -c copy output.mp4也行,但那就不是纯Go了,我属于手痒型,非要自己写了才舒服。
第三步:并发下载ts切片,注意速度和顺序
比赛有九局,每局大概十几个投球,每个投球之间都有广告或者慢镜回放,整个视频下来差不多5到3小时,ts切片数量在1200到1500个左右,一个个串行下载,你睡着了还没下完,所以我用了工作池模式:开10个goroutine,一个channel往里面塞切片序号,worker拿到序号就下载,存到本地临时目录。
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for idx := range jobs {
// 下载seg_%04d.ts
}
}(i)
}
要注意的是,顺序不能乱,虽然并发下载快,但合并的时候必须按序号来,我先用fmt.Sprintf("seg_%04d.ts", idx)统一命名,下载完放一个temp_chunks文件夹里,最后用os.ReadFile按序号读回来写进一个大的.ts文件,这一步要是写错,合并出来的视频就会花屏或者音画不同步,昨晚我犯了一次蠢,把文件名排序用成了字符串排序,导致seg_10.ts排在seg_2.ts前面,结果球被打出去的那一瞬间画面是碎的。
第四步:合并成一个MP4,收工(差点忘了字幕)
所有.ts合并成一个.ts文件后,其实能播,但有些播放器不认,我习惯用ffmpeg转封装一下:
ffmpeg -i merged_video.ts -c copy final_video.mp4
Go里直接调exec.Command("ffmpeg", ...),注意要设置cmd.Stdout和cmd.Stderr,不然出错你都不知道为啥,棒球直播的字幕是烧在画面里的,所以不用额外抓,但如果你想要韩文解说音轨,那就得在m3u8里找#EXT-X-MEDIA:TYPE=AUDIO那个链接,单独下载,再合并,我试过,太麻烦,算了,还是听中文解说吧。
坑与踩坑:台北vs韩国这场比赛的特别之处
- 延迟高:直播源比实际比赛慢40秒,你懂的,实时比分都不敢看。
- 断流重连:第三局下半场,源断了一次,我的下载队列空转,得加个超时重试机制。
- 地域限制:韩国那边的CDN节点对海外IP不友好,我得挂个东京的代理,Go的
http.Transport里设置Proxy函数就行。
别以为比赛结束就能抓回放,很多平台会把回放源拆成上下半场两个m3u8,你得分别抓,再合并,我就是只抓了第一个,结果后半场全是黑的,重新跑了一遍脚本才搞定。
写到最后,给你个现成的思路清单
如果你也想抓一场棒球直播,记住这几步:
- 找到m3u8地址(浏览器F12,Network过滤)
- 用Go发请求带上Referer(有时候要带token)
- 解析m3u8,判断是否加密
- 并发下载ts切片(goroutine池+channel)
- 按序号合并(注意格式化,别用字符串排序)
- ffmpeg转封装成mp4
我还想啰嗦一句
其实抓下来的视频,画质大概1080p,码率8000kbps,一场比赛下来将近2.5GB,要是硬盘不够,你还可以用ffmpeg的-crf 28参数压一下,画质损失不大,但体积能降到700MB左右,不过那又是另一套折腾了,我这次先留着原始版,回头看完再考虑压不压。
对了,用Go写这个工具,最爽的是你甚至能在比赛直播期间就实时抓,不用等结束,我昨晚边看边跑脚本,最后差几分钟就跟直播同步下完了,那种“比赛刚结束,我这边文件刚好能拖进度条回看”的感觉,真挺爽,但你最好别用这个思路去搞什么付费内容,法律风险自己担着,我就图个自娱自乐。
