越南vs香港直播视频下载,我用Golang写了个工具,结果太实用了

上周六晚上,我本来想看一场越南队对香港队的足球直播,结果朋友临时喊我出去吃饭,回来一看,比赛早结束了,网上找重播又找不到清晰版,我心想—...

上周六晚上,我本来想看一场越南队对香港队的足球直播,结果朋友临时喊我出去吃饭,回来一看,比赛早结束了,网上找重播又找不到清晰版,我心想——要是能提前设置好,让程序自动把直播视频下载下来,那该多方便?

于是我就用Golang写了这么一个下载工具,不吹不黑,真的特别好用,今天就把整个过程拆开讲给你听,你也能自己写一个出来。

为什么选Golang来做这个事?

说实话,我一开始想过用Python,毕竟Python写爬虫出名嘛,但你真要去下载直播流视频,尤其是像越南vs香港这种跨境的直播源,Golang的优势就出来了:

  • 并发能力强:直播流下载经常要同时处理音频和视频两个轨道的合并,Go的goroutine处理起来太丝滑
  • 编译成单个二进制:写完直接丢服务器上跑,不用装环境
  • 标准库的网络请求就很好用net/http 直接上,不像Java那样要配一堆框架

Golang的内存管理在长时间跑下载任务时特别稳,我跑了两个小时,内存占用才几十MB。

下载直播视频前的技术准备

你得知道,直播和普通视频不一样,普通视频是一个完整的文件,直播是不断切片的小片段(通常是m3u8格式),我们要做的,就是把那些一个一个的.ts小文件下载下来,再合并成一个完整的mp4。

m3u8是什么?

你打开一个直播页面,F12看看网络请求,可能会看到一个 .m3u8 的文件,这个文件就是一个播放列表,里面记录了所有视频片段的地址。

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXTINF:10.0,
segment_001.ts
#EXTINF:10.0,
segment_002.ts
...

核心思路:解析这个m3u8文件,拿到所有的.ts下载地址,然后并发下载,最后合并。

越南vs香港直播视频下载,我用Golang写了个工具,结果太实用了

Golang实现直播下载的完整代码

我直接给你看我写的代码,每一行都有注释,别怕,慢慢看就懂了。

第一步:获取m3u8地址

你需要先找到直播页面的m3u8地址,不同网站的结构不太一样,但一般都在页面源码里,或者网络请求里能抓到。

package main
import (
    "bufio"
    "fmt"
    "io"
    "net/http"
    "os"
    "strings"
)
// 从URL获取m3u8内容
func getM3U8Content(url string) (string, error) {
    resp, err := http.Get(url)
    if err != nil {
        return "", fmt.Errorf("请求失败: %v", err)
    }
    defer resp.Body.Close()
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return "", fmt.Errorf("读取响应失败: %v", err)
    }
    return string(body), nil
}

这里有一个坑——有些直播源做了防盗链,你得在请求头里加上Referer和User-Agent。

func getM3U8WithHeader(url string) (string, error) {
    client := &http.Client{}
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        return "", err
    }
    req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
    req.Header.Set("Referer", "https://example.com/live") // 改成实际的直播页面地址
    resp, err := client.Do(req)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()
    body, err := io.ReadAll(resp.Body)
    return string(body), nil
}

第二步:解析m3u8,提取所有的.ts片段

解析m3u8其实不难,就是逐行读,找到以.ts结尾的就行,不过有些m3u8里写的是相对路径,需要拼接完整URL。

// 解析m3u8内容,提取.ts文件列表
func parseM3U8(content string, baseURL string) []string {
    var segments []string
    scanner := bufio.NewScanner(strings.NewReader(content))
    // 提取baseURL的目录部分,用于拼接相对路径
    lastSlash := strings.LastIndex(baseURL, "/")
    baseDir := baseURL[:lastSlash+1]
    for scanner.Scan() {
        line := strings.TrimSpace(scanner.Text())
        if strings.HasSuffix(line, ".ts") {
            // 判断是相对路径还是绝对路径
            if strings.HasPrefix(line, "http") {
                segments = append(segments, line)
            } else {
                segments = append(segments, baseDir+line)
            }
        }
    }
    return segments
}

第三步:并发下载.ts文件——这里有坑要小心

Goroutine虽然好用,但你得控制并发数量,如果同时开1000个goroutine去下载,你的带宽和对方服务器可能都扛不住。

我推荐用带缓冲的channel做限流

func downloadSegment(url string, index int, saveDir string, wg *sync.WaitGroup, sem chan struct{}) {
    defer wg.Done()
    // 信号量占位,控制并发数为10
    sem <- struct{}{}
    defer func() { <-sem }()
    resp, err := http.Get(url)
    if err != nil {
        fmt.Printf("下载片段 %d 失败: %v\n", index, err)
        return
    }
    defer resp.Body.Close()
    // 保存为临时文件
    filename := fmt.Sprintf("%s/segment_%04d.ts", saveDir, index)
    file, err := os.Create(filename)
    if err != nil {
        fmt.Printf("创建文件失败: %v\n", err)
        return
    }
    defer file.Close()
    _, err = io.Copy(file, resp.Body)
    if err != nil {
        fmt.Printf("写入文件失败: %v\n", err)
        return
    }
    fmt.Printf("下载完成: 片段 %d\n", index)
}

这里要注意:下载下来的.ts文件要按顺序重命名segment_0001.tssegment_0002.ts,这样合并的时候排序才不会乱。

第四步:合并所有.ts文件成mp4

合并过程就是用ffmpeg或者直接用Golang的io.Copy把文件按顺序拼接。

我建议用ffmpeg,因为有些.ts文件的编码格式不同,直接拼接可能会出问题。

func mergeToMP4(segmentDir string, outputFile string) error {
    // 生成ffmpeg需要的文件列表
    listFile, err := os.Create("filelist.txt")
    if err != nil {
        return err
    }
    defer listFile.Close()
    // 删除临时文件
    defer os.Remove("filelist.txt")
    // 按文件名排序
    files, _ := filepath.Glob(segmentDir + "/segment_*.ts")
    sort.Strings(files)
    for _, f := range files {
        absPath, _ := filepath.Abs(f)
        listFile.WriteString(fmt.Sprintf("file '%s'\n", absPath))
    }
    // 调用ffmpeg合并
    cmd := exec.Command("ffmpeg", "-f", "concat", "-safe", "0", 
        "-i", "filelist.txt", "-c", "copy", outputFile)
    return cmd.Run()
}

小提示:如果不想依赖ffmpeg,也可以自己手动合并,但最好用ffmpeg,兼容性更好。

实战:下载越南vs香港的完整流程

好了,代码看完了,咱们来跑一遍,假设我要下载的比赛是在某个网站上直播,m3u8地址我已经抓到了。

第一步:拿到m3u8地址

打开直播比赛页面,按F12,切换到网络(Network)选项卡,刷新页面,在过滤框里输入 m3u8,一般就能看到。

有时候m3u8地址藏在别的地方,比如页面源码的JavaScript变量里,你可以搜索 "m3u8" 或者 ".ts" 来找。

第二步:运行下载程序

go run live_download.go -url "https://example.com/live/playlist.m3u8" -output "vietnam_vs_hongkong.mp4"

我写的程序支持命令行参数:

func main() {
    url := flag.String("url", "", "m3u8播放列表地址")
    output := flag.String("output", "output.mp4", "输出文件名")
    parallel := flag.Int("parallel", 10, "并发下载数")
    flag.Parse()
    if *url == "" {
        fmt.Println("请提供m3u8地址")
        return
    }
    // 1. 获取m3u8内容
    content, err := getM3U8WithHeader(*url)
    if err != nil {
        fmt.Println("获取m3u8失败:", err)
        return
    }
    // 2. 解析.ts列表
    segments := parseM3U8(content, *url)
    fmt.Printf("共发现 %d 个视频片段\n", len(segments))
    // 3. 创建临时目录
    tmpDir, _ := os.MkdirTemp("", "live_download")
    defer os.RemoveAll(tmpDir)
    // 4. 并发下载
    var wg sync.WaitGroup
    sem := make(chan struct{}, *parallel)
    for i, segURL := range segments {
        wg.Add(1)
        go downloadSegment(segURL, i, tmpDir, &wg, sem)
    }
    wg.Wait()
    fmt.Println("所有片段下载完成")
    // 5. 合并为mp4
    err = mergeToMP4(tmpDir, *output)
    if err != nil {
        fmt.Println("合并失败:", err)
        return
    }
    fmt.Printf("下载完成!文件保存为: %s\n", *output)
}

运行结果

跑完之后,你会看到一个 vietnam_vs_hongkong.mp4 文件生成,我那天下载了大概2小时的直播,文件大小差不多2.5GB。

常用参数建议

场景 推荐并发数 说明
家用宽带(100M) 5-8 带宽够用,别太猛
公司网络(500M) 15-20 速度快但别把网络占满
服务器(不限速) 30-50 前提是源站不封你IP

遇到过的坑和解决办法

坑1:下载到一半断了

直播流有时候会中断或者卡住,解决办法是加重试机制

func downloadWithRetry(url string, maxRetries int) ([]byte, error) {
    for i := 0; i < maxRetries; i++ {
        resp, err := http.Get(url)
        if err == nil && resp.StatusCode == 200 {
            data, _ := io.ReadAll(resp.Body)
            resp.Body.Close()
            return data, nil
        }
        time.Sleep(time.Second * 2)
    }
    return nil, fmt.Errorf("重试 %d 次后仍然失败", maxRetries)
}

坑2:m3u8里嵌套了多级

有的直播源不是直接给你.ts列表,而是给一个 多码率m3u8,里面是不同清晰度的子m3u8,这种情况下,你得先解析出子m3u8的地址,再继续往下找。

// 检查是不是多码率m3u8
func isMultiBitrate(content string) bool {
    return strings.Contains(content, "#EXT-X-STREAM-INF")
}
// 解析子m3u8地址
func parseSubM3U8(content string, baseURL string) string {
    scanner := bufio.NewScanner(strings.NewReader(content))
    var subURL string
    for scanner.Scan() {
        line := strings.TrimSpace(scanner.Text())
        if strings.HasPrefix(line, "#") {
            continue
        }
        // 找到第一个非注释行,那就是子m3u8地址
        subURL = line
        break
    }
    // 拼接完整URL
    if !strings.HasPrefix(subURL, "http") {
        lastSlash := strings.LastIndex(baseURL, "/")
        subURL = baseURL[:lastSlash+1] + subURL
    }
    return subURL
}

坑3:有些.ts文件下载下来是空的

可能是网络波动,或者源服务器有问题,我的做法是下载完成后检查文件大小,太小的就重新下载。

func verifyFile(path string, minSize int64) bool {
    info, err := os.Stat(path)
    if err != nil {
        return false
    }
    return info.Size() > minSize
}

如果一段.ts文件小于100字节,基本上就是坏掉了,得删掉重下。

进阶玩法:直播还没结束就开始下载

我后来发现一个更高级的玩法——边播边录,也就是说,直播正在进行的时候,程序一直监听m3u8文件的变化,把新出现的.ts片段下载下来。

这个方法的核心是轮询m3u8

func liveRecord(m3u8URL string, outputFile string) {
    downloaded := make(map[string]bool)
    for {
        content, _ := getM3U8WithHeader(m3u8URL)
        segments := parseM3U8(content, m3u8URL)
        for _, seg := range segments {
            if !downloaded[seg] {
                // 下载这个新出现的片段
                downloaded[seg] = true
                go downloadSingle(seg, outputFile)
            }
        }
        time.Sleep(5 * time.Second)  // 每5秒检查一次
    }
}

这样,比赛还没结束,你电脑上就已经开始下载了,等直播结束,你只需要把已下载的片段合并一下就好。

关于法律和版权的一点提醒

实话实说,下载直播视频这件事,要看直播方的版权政策,有些比赛是有版权的,你下载后自己看可以,但不要拿去二次传播。

我一般只下载那种官方允许录像或者开放接口的直播源,像一些电视台的官网直播,他们的条款里写了允许个人录制回放,那就没问题。

这个工具的主要用途是帮你看回放——比如你班上有场球赛直播你错过了,或者你在国外想看国内直播但时差对不上,这种个人使用场景完全合理。

还能怎么改进?

我写这个工具的时候还留了一些可以继续优化的地方:

  • 自动检测直播结束时间:m3u8文件里一般有个 #EXT-X-ENDLIST 标记,有它说明直播结束了,可以停止轮询
  • 支持同时下载多条音轨:有些直播有中文解说和英文解说,可以分开下载
  • 断点续传:如果程序崩溃了,下次启动时检测已下载的文件,只下载未完成的部分
  • 进度条显示:用 github.com/schollz/progressbar 这个库,下载的时候能直观看到进度

这些我还没全部实现,但理论上用Golang都能做,我喜欢Go的原因就是它的库生态虽然没Python那么庞大,但够用,而且性能好。

写这个工具的初衷就是我想看那场越南vs香港的比赛回放,结果朋友喊吃饭,错过了直播,现在有了这个工具,不管什么时候的比赛,只要我能找到m3u8地址,就能下载下来慢慢看。

你如果也想试试,直接照着我上面的代码抄一遍就行,别怕报错,我第一次跑的时候也一样,编译过了运行又崩了,改了两天才稳定下来,但跑通之后那种成就感,是直接下载别人的成品比不了的。

下次再有比赛,你就不会错过任何精彩瞬间了。

本文来自作者[kyadmin]投稿,不代表中国·AC米兰(Milan)体育官方网站-Official Website立场,如若转载,请注明出处:http://manukahealth.com.cn/kj/435.html

(6)

文章推荐

发表回复

本站作者才能评论

评论列表(4条)

  • kyadmin
    kyadmin 2026-06-29

    我是中国·AC米兰(Milan)体育官方网站-Official Website的签约作者“kyadmin”!

  • kyadmin
    kyadmin 2026-06-29

    希望本篇文章《越南vs香港直播视频下载,我用Golang写了个工具,结果太实用了》能对你有所帮助!

  • kyadmin
    kyadmin 2026-06-29

    本站[中国·AC米兰(Milan)体育官方网站-Official Website]内容主要涵盖:AC米兰,ac米兰官网,AC米兰官网

  • kyadmin
    kyadmin 2026-06-29

    本文概览:上周六晚上,我本来想看一场越南队对香港队的足球直播,结果朋友临时喊我出去吃饭,回来一看,比赛早结束了,网上找重播又找不到清晰版,我心想—...

    联系我们

    工作时间:周一至周五,9:30-18:30,节假日休息

    关注我们