当前位置:首页 > 技术 > 正文

用Go语言写了一个vs开拓者直播视频爬虫,结果我自己成了被直播的那个

  • 技术
  • 2026-08-17 14:11:32
  • 21
摘要: 事情得从我想看一场开拓者的比赛说起作为一个在波特兰待过三年的老球迷,我对开拓者的感情那是真金白银换来的——光是利拉德那记超远三分...

事情得从我想看一场开拓者的比赛说起

作为一个在波特兰待过三年的老球迷,我对开拓者的感情那是真金白银换来的——光是利拉德那记超远三分绝杀雷霆的球,我就在酒吧里跟人击掌把啤酒泼了自己一身,回国之后,最大的痛苦不是吃不到In-N-Out汉堡,而是看不了开拓者的直播视频

国内平台版权分散,今天这家播明天那家不播,有时候打开页面就剩个“已结束”的灰按钮,我寻思着,咱也是写过两年Go的人,就不能自己写个工具,把“vs开拓者直播视频”的源地址扒出来吗?

然后我就掉坑里了,而且是那种越挖越深的坑。

第一步:你以为的抓取,和我实际干的活

正常人想法:找到直播页面 → 解析HTML → 提取视频流地址 → 用播放器打开,Go标准库里的net/http发个GET请求,golang.org/x/net/html解析DOM,完事。

resp, err := http.Get("https://example.com/live/blazers")
if err != nil {
    log.Fatal(err)
}
defer resp.Body.Close()
doc, err := html.Parse(resp.Body)

我当时觉得这活儿半小时就能搞定,结果呢?打开页面一看,好家伙,整个直播内容全是JavaScript动态渲染的,HTML源码里就一个空壳<div id="app">,关键数据全得靠浏览器执行JS才能拿到。

这意味着啥?意味着我得用到一个叫chromedp的库——一个通过Chrome DevTools Protocol控制无头浏览器的Go库,说白了就是让你的Go程序假装开了一个浏览器,等页面跑完所有JS脚本,再抓取最终渲染出来的DOM。

这还没完,更坑的是,很多直播平台用iframe嵌套播放器,而播放器内部又从另一个接口拿m3u8flv地址,你得一层层往里跳,跟剥洋葱似的,剥到眼泪直流。

直播视频的“硬核”难点:不是技术,是反爬

你以为拿到视频地址就完事了?太天真了,现在的反爬机制那叫一个丧心病狂:

  • 请求头校验RefererUser-AgentOrigin缺一不可,缺一个它就给你返回403
  • Token时效性:视频流地址的有效期可能就几十秒,你得在拿到地址后的瞬间拼接出带有效签名的完整URL
  • IP封禁:短时间内高频请求,直接封你IP,让你连官网都打不开
  • 防盗链:视频服务器会校验请求来源,非白名单域名一律拒绝

我举个例子,我写了个函数去获取播放器内部的m3u8地址:

func getM3U8(pageURL string) (string, error) {
    ctx, cancel := chromedp.NewContext(context.Background())
    defer cancel()
    var result string
    err := chromedp.Run(ctx,
        chromedp.Navigate(pageURL),
        chromedp.WaitVisible(`#player`, chromedp.ByID),
        chromedp.AttributeValue(`#player video`, "src", &result, nil),
    )
    if err != nil {
        return "", fmt.Errorf("获取播放地址失败: %v", err)
    }
    return result, nil
}

实际跑起来,发现src属性往往是空的——视频流是通过MediaSource对象动态拼接的,根本不在DOM属性里。你得劫持WebSocket消息或者监听fetch事件,才能捕获流地址。

那一刻我终于理解了,为什么有些老程序员说“爬虫拼的不是代码,是心力”。

折腾半天的真实收获:一个还凑合的方案

先说结论:纯用Go标准库+chromedp,理论上能爬到“vs开拓者直播视频”的真实地址,但工程复杂度远超预期,我把踩坑的过程记录下来,希望能帮你少走点弯路。

核心步骤拆解(附代码思路)

我对国内几个主流体育直播站点做了逆向分析(就提名字吧:腾讯体育、咪咕视频、直播吧的第三方源),总结以下通用流程:

第一步:获取网页初始数据
chromedp加载页面,等待networkidle事件(所有网络请求完成),这一步推荐设置超时,有些站点广告资源特别多,等个10秒都是常态。

options := append(chromedp.DefaultExecAllocatorOptions[:],
    chromedp.Flag("disable-blink-features", "AutomationControlled"),
    chromedp.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"),
)
allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), options...)

关键点:必须隐藏“自动化控制”特征。chromedp.Flag("disable-blink-features", "AutomationControlled")这一行能去掉navigator.webdriver属性,不然很多站点直接ban你。

第二步:拦截API响应
chromedp.ListenTarget监听网络事件,重点捕获包含m3u8mpdflv关键字的响应体,有些接口返回的是JSON,里面有个url字段指向视频流。

chromedp.ListenTarget(ctx, func(ev interface{}) {
    if ev, ok := ev.(*network.EventResponseReceived); ok {
        go func(reqID network.RequestID) {
            body, _ := network.GetResponseBody(ctx, reqID)
            if strings.Contains(string(body), "m3u8") {
                // 这里就拿到了流地址!
            }
        }(ev.RequestID)
    }
})

第三步:处理防盗链
拿到m3u8地址后,用Go的github.com/grafov/m3u8库解析,找出不同分辨率的子流,然后在发起下载或转发请求时,必须带上原始页面的RefererOrigin

req, _ := http.NewRequest("GET", m3u8URL, nil)
req.Header.Set("Referer", pageURL)
req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; MyBlazersBot/1.0)")
client := &http.Client{}
resp, err := client.Do(req)

为什么我最终还是选择了“半自动”方案

折腾了一周,代码写了八百多行,最后发现了一个尴尬的事实:每场比赛的页面结构都可能变,上一场能用,下一场就失效了,你永远在跟对方的运维人员打攻防战。

现在我的做法是:用Go写一个脚本,自动打开浏览器、做一次完整抓取、然后把地址打印出来,剩下的播放交给外部播放器,虽然没达到“一键观看”的终极目标,但好歹比手动找源强了十倍。

技术这东西,能解决80%的问题就用,剩下20%留给生活,现在就挺好,比赛开始前五分钟跑一下脚本,喝口啤酒,找到地址,点开播放器——一切刚刚好。

毕竟我只是想看个球,又不是要开发个视频平台,对吧?

用Go语言写了一个vs开拓者直播视频爬虫,结果我自己成了被直播的那个