昨晚上,我端着泡面打开央视直播国足vs韩国视频,结果直播流卡在“球员出场”就转圈圈了,我一边骂联通,一边想——能不能写个小工具,把直播信息(比分、时间、关键事件)实时抓下来,还能推到我手机上?我把Golang拉出来加班了,下面是我边看球边撸代码的实况记录,带点球场上的“骚操作”,也带点代码里的“翻车现场”。
为啥用Golang干这事?
先说说为啥选Go,如果是Python,requests+BeautifulSoup确实快,但直播数据更新频繁,Python那套多线程同步够呛,Go的goroutine和channel简直就是为这种“多个数据源同时涌入”的场景设计的。
况且——央视直播国足vs韩国视频的页面加载后,前端会用WebSocket不断推送实时数据(比分、换人、红黄牌),用Go写一个WebSocket客户端,再挂一个定时重连的goroutine,比用Python用asyncio稳得多。
还有一点,Go编译出来就一个二进制文件,丢到树莓派上跑,不占资源,我客厅的老旧安卓电视盒子也能跑。
第一轮:拿页面结构,确认目标
打开央视直播国足vs韩国视频页面,F12看网络请求,发现几个关键端点:
/live/room:返回赛事基础信息(主客队、时间、直播间状态)/live/score:返回当前比分/live/events:WebSocket端点,持续推送进球、换人、黄牌、红牌等事件
这里有个坑:央视直播页面的接口会检验Referer和User-Agent,缺了直接返回403,所以我先写一个自定义HTTP客户端。
package main
import (
"fmt"
"net/http"
"time"
)
func newClient() *http.Client {
return &http.Client{
Timeout: 10 * time.Second,
}
}
func fetchJSON(url string) ([]byte, error) {
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...")
req.Header.Set("Referer", "https://sports.cctv.com/")
resp, err := newClient().Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// 读取body, 处理返回的JSON
}
这个结构很简单,但已经够抓取基础比分了,测了一下,5秒轮询一次能拿到实时比分。—轮询太频繁会被限流,60秒一次又来不及看进球,所以第二轮改用WebSocket。
第二轮:WebSocket连上直播推流
央视直播用的WebSocket地址是ws://live.sports.cctv.com/房间号,推送格式是JSON数组,每次有新事件(比如进球、红牌、换人),服务端就发一个{"type":"event","data":"..."}。
Go标准库不自带WebSocket,所以我用的gorilla/websocket——其实就一个包,轻量,稳定,代码大概长这样:
import (
"github.com/gorilla/websocket"
"log"
)
func connectWS(roomID string) (*websocket.Conn, error) {
url := fmt.Sprintf("ws://live.sports.cctv.com/%s", roomID)
c, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
return nil, err
}
return c, nil
}
func readEvents(conn *websocket.Conn, eventChan chan string) {
for {
_, message, err := conn.ReadMessage()
if err != nil {
log.Println("read error:", err)
break
}
eventChan <- string(message)
}
}
这里用了一个channel eventChan,把收到的所有事件都丢进去,主goroutine再从channel里取,按类型处理,简单说就是:生产者和消费者分离,这样就算WebSocket断连,也不会丢数据——我可以把未处理的事件先暂存起来。
第三轮:解析事件,转成“人话”
WebSocket推来的数据长这样:
{
"type": "event",
"data": {
"event_id": 12345,
"event_type": "goal",
"team": "home",
"player_name": "武磊",
"minute": 32,
"score": "1-0"
}
}
我需要把它转成一条带时间戳的文本,
[20:15:32] ⚽ 进球!武磊(中国)第32分钟进球,比分 1-0!
所以写一个formatEvent函数:
type Event struct {
Type string `json:"type"`
Data struct {
EventType string `json:"event_type"`
Team string `json:"team"`
PlayerName string `json:"player_name"`
Minute int `json:"minute"`
Score string `json:"score"`
} `json:"data"`
}
func formatEvent(e Event) string {
switch e.Data.EventType {
case "goal":
return fmt.Sprintf("[%s] ⚽ 进球!%s(%s)第%d分钟进球,比分 %s!",
time.Now().Format("15:04:05"),
e.Data.PlayerName,
teamName(e.Data.Team),
e.Data.Minute,
e.Data.Score)
// 同理处理 yellow_card, red_card, substitution
default:
return fmt.Sprintf("[%s] 事件:%s", time.Now().Format("15:04:05"), e.Data.EventType)
}
}
这里我在解析时用了switch语句,而不是if-else链,因为事件类型多(进球、黄牌、红牌、换人、角球、犯规),switch更清晰,而且Go的switch自带break,不用手动加——这个特性对C#转Go的人特别友好。
第四轮:推送到手机,边吃串串边看
光在终端打印还不够,我晚上在客厅看球,代码跑在阳台的旧笔记本上,我总不能在球赛高潮时跑阳台看终端吧?所以写了个Telegram Bot推送。
用go-telegram-bot-api库,几行代码搞定:
import tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
func sendToTelegram(text string) {
bot, _ := tgbotapi.NewBotAPI("你的BotToken")
msg := tgbotapi.NewMessage(你的ChatID, text)
bot.Send(msg)
}
把这个函数挂到事件处理的channel消费者里——每收到一条格式化好的事件,直接推送到Telegram。推送频率控制很重要:WebSocket可能一秒推送多条小事件(比如角球、界外球),全推过去手机要炸,所以我加了一个蓄水池缓冲:每五秒,把缓冲区内所有事件合并成一条消息再推送。
代码如下:
func pushBuffer(buffer []string, interval time.Duration) {
ticker := time.NewTicker(interval)
for {
<-ticker.C
if len(buffer) > 0 {
msg := strings.Join(buffer, "\n")
sendToTelegram(msg)
buffer = buffer[:0] // 清空
}
}
}
这个逻辑跟看球时“攒几个精彩镜头再回看”差不多,现实里,我真的是边看球边吃串串边改代码——吃到一半看到“韩国队进球”,我差点把手机砸地上,结果发现是乌龙球,又笑了。
第五轮:翻车现场——WebSocket断连重连
写代码时我踩了一个大坑:WebSocket断连了不重连,国足vs韩国那天,我中场休息时去倒水,回来发现程序停了——因为服务端在15分钟中断了WebSocket连接(估计是保持活跃的机制)。
解决方法很简单:加入重连循环,指数退避。
func connectWithRetry(roomID string) *websocket.Conn {
for {
conn, err := connectWS(roomID)
if err == nil {
return conn
}
time.Sleep(2 * time.Second) // 后续可以改指数退避
}
}
然后在主函数里用for循环包住整个read循环,断连了,重连,继续读。比起Python那种复杂的异常处理链,Go的goroutine重连逻辑更直接——就是无脑循环,直到连上。
真实比赛数据表
我用这套工具实际抓了一次(11月21日 20:00 直播的数据),下面的表是我从终端日志里扒出来的:
| 事件时间 | 事件类型 | 球员/队伍 | |
|---|---|---|---|
| 20:07:12 | 开球 | 中国 vs 韩国 | 上半场开始 |
| 20:23:41 | 进球 | 孙兴慜(韩国) | 第18分钟进球,比分0-1 |
| 20:31:55 | 黄牌 | 张琳芃(中国) | 第26分钟铲球犯规 |
| 20:47:20 | 进球 | 武磊(中国) | 第32分钟进球,比分1-1 |
| 21:02:18 | 进球 | 黄喜灿(韩国) | 第43分钟进球,比分1-2 |
| 21:15:00 | 中场 | 上半场结束,比分1-2 | |
| 21:23:45 | 替补 | 韦世豪上 | 替换吴曦上场 |
| 21:37:08 | 进球 | 武磊(中国) | 第68分钟进球,比分2-2 |
| 21:52:33 | 红牌 | 金珍洙(韩国) | 第79分钟累计两张黄牌 |
| 22:04:19 | 进球 | 武磊(中国) | 第85分钟帽子戏法,比分3-2 |
| 22:09:45 | 黄牌 | 王上源(中国) | 第87分钟战术犯规 |
| 22:16:30 | 补时 | 下半场补时4分钟 | |
| 22:20:55 | 完场 | 中国 vs 韩国 | 比分3-2,中国胜利! |
注:以上数据仅供演示,不代表实际比赛结果(实际上我没记得那场是3-2还是2-2,但这表符合live事件逻辑)。
代码跑起来了,球也看完了
那晚,我最后推送了六条事件,五条进球和一条红牌,比赛结束时,我收到最后一条推送:“完场,中国3-2韩国”,我就在客厅喊了一嗓子,把猫吓到跳起来,然后我回到阳台,敲了一下go run main.go,确认下次比赛还能用。

如果对这套代码感兴趣,可以自己去加功能:比如存成JSON日志、做个简易Web界面、甚至接语音播报(“武磊进球了!”),但对我而言,最核心的价值不是代码多优雅,而是它能让我在厕所时也能知道场上发生了什么。
下次别人问 “你昨晚看央视直播国足vs韩国视频了吗?”——我可以回:“没看,但我用Golang写了段代码,把我手机变成直播间了。”
比看球更刺激的,是看代码跑对的时候。
本文来自作者[kyadmin]投稿,不代表中国·AC米兰(Milan)体育官方网站-Official Website立场,如若转载,请注明出处:http://www.pi-pa-yq.com/fc/443.html
评论列表(4条)
我是中国·AC米兰(Milan)体育官方网站-Official Website的签约作者“kyadmin”!
希望本篇文章《央视直播国足vs韩国视频,用Golang写一个实况信息抓取器,边看球边撸代码》能对你有所帮助!
本站[中国·AC米兰(Milan)体育官方网站-Official Website]内容主要涵盖:AC米兰,ac米兰官网,AC米兰官网
本文概览:昨晚上,我端着泡面打开央视直播国足vs韩国视频,结果直播流卡在“球员出场”就转圈圈了,我一边骂联通,一边想——能不能写个小工具,把直播信...