当前位置:首页 > 体育 > 正文

用Go语言搭建一个NBA直播小工具,猛龙VS勇士,边看球边写代码

  • 体育
  • 2026-07-24 00:34:27
  • 11
摘要: 事情是这样的,上周六晚上,我正窝在沙发上用手机看NBA直播,猛龙对勇士,库里投了个三分球,球还在空中画弧线呢,我手机突然卡住了,...

事情是这样的,上周六晚上,我正窝在沙发上用手机看NBA直播,猛龙对勇士,库里投了个三分球,球还在空中画弧线呢,我手机突然卡住了,那几秒钟简直比一个世纪还长,等画面恢复过来,球都进了,但我错过了那个瞬间,气得我想砸手机。

后来我琢磨,与其等别人家的直播平台优化,不如自己用Go写个小工具,把NBA直播的关键数据实时拉到本地,不求多花哨,至少别卡顿,别插广告,让我安安静静看完猛龙干勇士。

为什么选Go语言干这事

说实话,一开始我也想用Python,但一想到直播数据是持续的流式推送,Python那个GIL(全局解释器锁)在多线程场景下容易掉链子,Go就不一样,goroutine天生就是干这种活的——每一个数据请求开一个轻量级线程,互不干扰。

而且Go编译出来是单个二进制文件,丢服务器上就能跑,不依赖什么虚拟环境,我这台老MacBook Air,16GB内存,跑起来风扇都不怎么转。

第一步:搞清楚数据从哪来

写代码之前,得先搞清楚NBA的实时直播数据长什么样,官方的NBA API有一个开放的stats端点,不过直接调的话会遇到跨域问题,好在有人已经整理了一份非官方的接口文档,https://cdn.nba.com/static/json/liveData/scoreboard/todayScoreboard.json 这个。

我用curl试了一下,返回的是JSON格式,包含每场比赛的实时比分、节数、剩余时间、球员数据等等。

curl -s https://cdn.nba.com/static/json/liveData/scoreboard/todayScoreboard.json | jq '.'

响应结构大概是这样的:

{
  "scoreboard": {
    "games": [{
      "gameId": "0022300123",
      "gameStatus": "2",
      "homeTeam": { "teamTricode": "GSW", "score": 67 },
      "awayTeam": { "teamTricode": "TOR", "score": 72 },
      "period": 3,
      "gameClock": "8:45"
    }]
  }
}

注意那个gameStatus字段:1表示还没开打,2表示进行中,3表示结束了。period是当前节数,gameClock是剩余时间。

第二步:用Go写个实时比分获取器

我决定写一个命令行工具,每5秒拉一次数据,只打印猛龙和勇士那场。

先把项目初始化好:

mkdir nba-live
cd nba-live
go mod init nba-live

然后写主文件 main.go,思路很简单:开一个goroutine循环请求数据,解析JSON,过滤出猛龙VS勇士那场,然后格式化输出。

package main
import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "time"
)
type Scoreboard struct {
    Scoreboard struct {
        Games []Game `json:"games"`
    } `json:"scoreboard"`
}
type Game struct {
    GameID     string `json:"gameId"`
    GameStatus int    `json:"gameStatus"`
    HomeTeam   Team   `json:"homeTeam"`
    AwayTeam   Team   `json:"awayTeam"`
    Period     int    `json:"period"`
    GameClock  string `json:"gameClock"`
}
type Team struct {
    TeamTricode string `json:"teamTricode"`
    Score       int    `json:"score"`
}
func main() {
    url := "https://cdn.nba.com/static/json/liveData/scoreboard/todayScoreboard.json"
    for {
        resp, err := http.Get(url)
        if err != nil {
            fmt.Println("请求失败:", err)
            time.Sleep(10 * time.Second)
            continue
        }
        body, _ := io.ReadAll(resp.Body)
        resp.Body.Close()
        var sb Scoreboard
        json.Unmarshal(body, &sb)
        for _, game := range sb.Scoreboard.Games {
            // 只显示猛龙 (TOR) 对勇士 (GSW)
            if (game.HomeTeam.TeamTricode == "TOR" && game.AwayTeam.TeamTricode == "GSW") ||
               (game.HomeTeam.TeamTricode == "GSW" && game.AwayTeam.TeamTricode == "TOR") {
                status := map[int]string{1: "未开始", 2: "进行中", 3: "已结束"}[game.GameStatus]
                fmt.Printf("\r%s | %s %d - %d %s | 第%d节 %s", 
                    time.Now().Format("15:04:05"),
                    game.AwayTeam.TeamTricode, game.AwayTeam.Score,
                    game.HomeTeam.Score, game.HomeTeam.TeamTricode,
                    game.Period, game.GameClock)
            }
        }
        time.Sleep(5 * time.Second)
    }
}

跑起来看看效果:

22:15:33 | TOR 72 - 67 GSW | 第3节 8:45

那个\r回车符让同一行更新,看起来就像比分在动,蛮有成就感的。

第三步:给输出加点花样

仅仅看比分有点干巴巴的,我想在比分旁边显示最近一次得分事件,库里三分命中”、“西亚卡姆上篮得手”,NBA的API其实还有一个更细粒度的playByPlay端点,每个比赛都有独立的pbp数据。

https://cdn.nba.com/static/json/liveData/playbyplay/playbyplay_0022300123.json

这个JSON里包含每个事件的描述、时间、得分变化,我加了一个新函数来获取最近的一次得分事件:

type Play struct {
    ActionNumber int    `json:"actionNumber"`
    Clock        string `json:"clock"`
    Description  string `json:"description"`
    ScoreHome    string `json:"scoreHome"`
    ScoreAway    string `json:"scoreAway"`
}
type PBP struct {
    Plays []Play `json:"actions"`
}
func getLastScoreEvent(gameID string) string {
    url := fmt.Sprintf("https://cdn.nba.com/static/json/liveData/playbyplay/playbyplay_%s.json", gameID)
    resp, err := http.Get(url)
    if err != nil {
        return ""
    }
    defer resp.Body.Close()
    body, _ := io.ReadAll(resp.Body)
    var pbp PBP
    json.Unmarshal(body, &pbp)
    // 从后往前找,最近一个有得分变化的事件
    for i := len(pbp.Plays) - 1; i >= 0; i-- {
        p := pbp.Plays[i]
        if p.Description != "" {
            return fmt.Sprintf("%s | %s", p.Clock, p.Description)
        }
    }
    return ""
}

不过在暴力写代码的时候我发现一个问题:pbp接口有时候返回空数据,或者频率太快会被限流,我加了个简单的错误重试和间隔控制。

现在输出变成了:

22:16:01 | TOR 74 - 70 GSW | 第3节 6:12 | 6:12 — 库里突破上篮命中

有点直播那味儿了。

第四步:搞一个简单的Web界面

命令行虽然好用,但躺沙发上看球的时候,总不能抱着笔记本开终端吧,我决定再加一个HTTP服务器,把比分和事件用HTML展示出来,手机浏览器就能看。

Go标准库自带net/http,写个简单的处理器就行:

func handler(w http.ResponseWriter, r *http.Request) {
    // 复用之前的数据获取逻辑
    scoreData := getLiveScore("TOR", "GSW")
    pbpData := getLastScoreEvent(gameID)
    html := fmt.Sprintf(`
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="refresh" content="5">
        <style>
            body { font-family: Arial, sans-serif; padding: 20px; background: #1a1a2e; color: #eee; }
            .score { font-size: 3em; text-align: center; margin: 20px; }
            .event { text-align: center; font-size: 1.2em; color: #ffd700; }
            .info { text-align: center; color: #aaa; }
        </style>
    </head>
    <body>
        <div class="score">%s</div>
        <div class="info">%s</div>
        <div class="event">%s</div>
    </body>
    </html>`, scoreData, clockInfo, pbpData)
    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    fmt.Fprint(w, html)
}
func main() {
    http.HandleFunc("/", handler)
    fmt.Println("打开浏览器访问 http://localhost:8080")
    http.ListenAndServe(":8080", nil)
}

用手机连同一个Wi-Fi,输入 168.x.x:8080,就能看到实时更新的页面,等猛龙勇士第四节打到最后两分钟的时候,那个比分每5秒自动刷新一次,肾上腺素飙升。

踩的一些坑和真实感受

说实话,写这个工具花了我一个周末,中间遇到不少问题:

  • NBA的API有时候抽风,liveData端点偶尔返回404,我不得不在主循环里加了更健壮的重试逻辑。
  • 有些比赛的playByPlay数据特别长,好几千个事件,解析起来内存占用不小,后来我改成只保留最近50个事件,用环形缓冲区来存。
  • 手机浏览器自动刷新会打断阅读,我后来改成了服务器主动推送(WebSocket),但那个实现复杂一些,暂时没放进去。

还有,猛龙那场打勇士真的太激烈了,我用这工具看完了下半场,当比分来到98比96的时候,刷新间隔我改成了2秒,手心全是汗,工具本身也出了一个bug——有一段时间比分停在了82比82不动了,我以为是代码写死了,后来发现是现场暂停,比赛没走表。

挺妙的,写代码和看比赛原来可以是一回事。

你能怎么用这个灵感

这套东西虽然简陋,但可以玩出很多花样:

  • 加个钉钉/微信通知,喜欢的球队追分时提醒你
  • 把历史数据存进SQLite,赛后分析球员手感变化
  • 多场比赛同时监控,做一个小型体育数据面板

Go语言在处理这类实时数据流场景的优势很明显:并发好、部署简单、运行稳定,你甚至可以把二进制文件丢到树莓派上,7x24小时跑着,随时打开手机看一眼比分。

写这段代码的时候,我儿子跑过来问:“爸爸你在干嘛?”

我说:“我在用电脑看球赛。”

他盯着黑乎乎的终端看了半天:“明明只有数字在闪。”

我笑了笑没解释,有些东西,懂的人自然懂——就像库里那个超远三分,球还在空中,你已经知道它要进了。

用Go语言搭建一个NBA直播小工具,猛龙VS勇士,边看球边写代码