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

北京vs鹏程视频直播回放,用Golang写个爬虫,把失去的精彩瞬间抓回来

  • 技术
  • 2026-08-27 17:44:08
  • 78
摘要: 为啥Golang特别适合搞视频回放抓取?前两天朋友问我,说错过了北京队和鹏程队那场直播,到处找回放都找不到完整的,我笑了笑,跟他...

为啥Golang特别适合搞视频回放抓取?

前两天朋友问我,说错过了北京队和鹏程队那场直播,到处找回放都找不到完整的,我笑了笑,跟他说:“这事儿啊,用咱Go语言写个爬虫,半小时搞定。”他一脸懵,但我是认真的——Golang的并发模型和高性能HTTP客户端,天生就是干这个的料。

你可能会问,为啥不用Python?Python当然也行,但Go有它独有的优势:

  • 编译成单一可执行文件,丢到服务器上就能跑,不用装一堆依赖
  • goroutine轻量级并发,同时抓几十个视频分片毫无压力
  • 标准库net/http就够用,不用引入一堆第三方库
  • 内存占用小,跑在2G内存的云主机上都绰绰有余

第一步:搞清楚视频回放从哪来

北京vs鹏程这场比赛的回放,常见来源有这么几个:

来源 特点 抓取难度
官方直播平台 画质好,但可能有防盗链
第三方聚合站 来源杂,画质参差不齐
论坛/网盘分享 可能不完整,但容易抓

大多数情况下,直播平台会把视频切成TS分片文件(MPEG-TS格式),通过m3u8索引文件来按顺序播放。这个m3u8文件就是我们爬虫的钥匙

第二步:写个最基本的m3u8下载器

咱们别整那些虚的,直接上代码,下面这个例子假设你已经从浏览器开发者工具里找到了m3u8的地址(Networking面板,筛选media类型):

package main
import (
    "fmt"
    "io"
    "net/http"
    "os"
    "strings"
    "sync"
)
func fetchM3U8(url string) ([]string, error) {
    resp, err := http.Get(url)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }
    var segments []string
    for _, line := range strings.Split(string(body), "\n") {
        line = strings.TrimSpace(line)
        if strings.HasPrefix(line, "http") {
            segments = append(segments, line)
        }
    }
    return segments, nil
}
func downloadSegment(url string, wg *sync.WaitGroup, index int) {
    defer wg.Done()
    resp, err := http.Get(url)
    if err != nil {
        fmt.Printf("第%d个分片下载失败: %v\n", index, err)
        return
    }
    defer resp.Body.Close()
    out, err := os.Create(fmt.Sprintf("seg_%04d.ts", index))
    if err != nil {
        fmt.Println("创建文件失败:", err)
        return
    }
    defer out.Close()
    _, err = io.Copy(out, resp.Body)
    if err != nil {
        fmt.Printf("第%d个分片写入失败: %v\n", index, err)
    }
}
func main() {
    m3u8URL := "替换成实际的m3u8地址"
    segments, err := fetchM3U8(m3u8URL)
    if err != nil {
        fmt.Println("获取m3u8失败:", err)
        return
    }
    fmt.Printf("共发现%d个分片\n", len(segments))
    var wg sync.WaitGroup
    for i, seg := range segments {
        wg.Add(1)
        go downloadSegment(seg, &wg, i)
    }
    wg.Wait()
    fmt.Println("所有分片下载完成")
}

这段代码已经能并发下载所有TS分片了。注意并发数——别一口气开500个goroutine,你的路由器会抗议的,建议用个带缓冲的channel,限制同时下载数量在10个左右。

第三步:把分片合成一个完整的视频文件

分片下载完了,还得合并,这事儿在Linux上直接:

cat seg_*.ts > beijing_vs_pengcheng_full.ts

但在Windows上,命令行就不那么爽了,咱用Go合并:

func mergeSegments(pattern string, output string) error {
    out, err := os.Create(output)
    if err != nil {
        return err
    }
    defer out.Close()
    for i := 0; ; i++ {
        fname := fmt.Sprintf(pattern, i)
        if _, err := os.Stat(fname); os.IsNotExist(err) {
            break
        }
        data, err := os.ReadFile(fname)
        if err != nil {
            return err
        }
        if _, err := out.Write(data); err != nil {
            return err
        }
    }
    return nil
}

这样匹配seg_0000.ts, seg_0001.ts...这样的文件顺序合并。合并时注意顺序,有的分片命名不是从0开始,或者有跳号的情况,最好先把文件名解析出来排个序。

第四步:处理防盗链和加密问题

这边要泼点冷水:很多直播平台的m3u8不是直接能访问的,可能会遇到这些情况:

  • Referer检查——需要带上来源页面地址
  • User-Agent伪装——别用Go的默认UA(一眼假)
  • AES-128加密——m3u8里会有#EXT-X-KEY:METHOD=AES-128,URI="key.key"这样的标记

对于加密的分片,你得先下载key文件,再用crypto/aes包解密每个分片:

func decryptSegment(data []byte, key []byte) ([]byte, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }
    if len(data) % aes.BlockSize != 0 {
        return nil, fmt.Errorf("数据长度不是块大小的整数倍")
    }
    iv := make([]byte, aes.BlockSize) // 根据实际情况,IV可能在m3u8里给出
    mode := cipher.NewCBCDecrypter(block, iv)
    decrypted := make([]byte, len(data))
    mode.CryptBlocks(decrypted, data)
    return decrypted, nil
}

第五步:加个断点续传,免得中途挂了从头来

看比赛回放经常是十几分钟到几个小时,视频文件大,分片多,网络不稳定时,断点续传是刚需,咱可以在下载前先检查本地是否已有同名文件:

  • 文件存在且大小不为0,跳过(或者校验哈希)
  • 文件不完整(比如下载了一半),重新下载
  • 记录已下载的分片索引到progress.json,下次启动时读取
func loadProgress() map[string]bool {
    data, err := os.ReadFile("progress.json")
    if err != nil {
        return make(map[string]bool)
    }
    var progress map[string]bool
    json.Unmarshal(data, &progress)
    return progress
}
func saveProgress(progress map[string]bool) {
    data, _ := json.Marshal(progress)
    os.WriteFile("progress.json", data, 0644)
}

那场北京vs鹏程,我最后看到了吗?

回到开头那个朋友的问题——我用这套Golang爬虫,花了个把小时(大部分时间在等下载),真的抓到了完整的回放,那个晚上,我泡了杯茶,把错过的第四节和加时赛,从头看到尾。鹏程队那记压哨三分,我还反复放了好几遍

技术这东西,本质上不就是为了让人能更方便地看到自己想看的内容吗?从m3u8解析到TS合并,从防盗链到解密,每一步都碰了点壁,但都过来了,你要是也遇到过这种想看回放却找不到资源的时刻,不妨搭个Go环境,照着上面这段逻辑,自己动手写一个,反正代码又不复杂,跑起来稳得很——就像那场比赛的最后两分钟,心跳加速但终究没崩。

北京vs鹏程视频直播回放,用Golang写个爬虫,把失去的精彩瞬间抓回来