Go语言开发者如何用爬虫优雅地获取CBA篮球比分网实时数据
- 新闻
- 2026-09-02 18:15:59
- 55
说实话,我写这篇文章的起因挺偶然的,前阵子跟朋友聊起CBA联赛,他说每次想查比分都得打开好几个网页,烦得很,我当时正好在研究Go语言的网络编程,就琢磨着——能不能用Go写个小工具,直接从cba篮球比分网抓数据?这一试,还真捣鼓出点门道来。
为什么偏偏选Go语言干这活?
你可能要问,Python写爬虫不香吗?确实,Python生态成熟,但Go有几个天生的优势特别对我的胃口。
- 部署简单:编译出来就是一个二进制文件,扔到服务器上就能跑,不用配一堆依赖
- 并发能力强:goroutine轻量得很,同时抓取多个页面的比分数据毫无压力
- 标准库够用:net/http、encoding/json这些都是现成的,少装很多第三方包
我当时写了个小demo,从cba篮球比分网拉数据,整个代码也就七八十行,第一次跑起来的时候,看着终端里刷刷刷蹦出来的比分数字,那种成就感,嘿,真挺带劲的。
先摸清楚目标网站的结构
在动手写代码之前,有一件事特别重要——你得先搞清楚cba篮球比分网的数据是怎么组织的,我一开始太心急,直接上手就写正则表达式去匹配,结果网页改版后全废了。
后来学聪明了,先用Go写了个简单的HTTP客户端,把网页源码拉下来瞅了瞅,发现这网站的数据藏在特定的class里,
resp, err := http.Get("https://www.cba-basketball-scores.com/live")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
fmt.Println(string(body)[:5000]) // 先打印前5000字符看看结构
这里有个很容易踩的坑——headers必须要伪装,我一开始没设置User-Agent,直接被403了,加上这个就好使了:
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
正则还是CSS选择器?这是个问题
说到解析HTML,很多从Python转过来的朋友习惯用BeautifulSoup,Go这边呢,我试过几个库,比如goquery(和jQuery语法几乎一样)和colly(一个挺全面的爬虫框架)。
不过我之前写过一个更“笨”的方法——纯正则匹配,虽然不够优雅,但对于结构固定的页面反而意外地稳定,比如匹配比分数据:
re := regexp.MustCompile(`<span class="score">(\d+)\s*:\s*(\d+)</span>`)
matches := re.FindAllStringSubmatch(string(body), -1)
for _, m := range matches {
fmt.Printf("比分: %s - %s\n", m[1], m[2])
}
对,我知道用正则解析HTML会被很多人吐槽,但怎么说呢,在快糙猛面前,谁用谁知道,不过要是你的需求比较复杂,那还是老老实实上goquery吧。
数据清洗,比想象中更磨人
你可能觉得,抓下来数据不就完事了?天真了,我拿到的第一份原始数据简直没法看——乱码、多余的空格、换行符混在一起。
这里有几个实操技巧:
- 统一编码:有的页面是UTF-8,有的搞GBK,你最好在读取body的时候顺手转一下
- 字符串清理:用
strings.ReplaceAll干掉多余的空格和换行 - 时间格式化:CBA比赛时间格式挺乱,“19:35”和“2024-01-15 19:35”都有
我当时写了个小函数专门处理这个:
func cleanData(raw string) string {
// 去掉多余的空白
space := regexp.MustCompile(`\s+`)
cleaned := space.ReplaceAllString(raw, " ")
// 去掉首尾空格
return strings.TrimSpace(cleaned)
}
动态加载的数据?那就得换个思路
我原以为cba篮球比分网是纯静态网页,直接拉HTML就完事了,结果发现了一个坑——部分比分数据是通过AJAX动态加载的。
这个时候单纯用http.Get就不灵了,页面源码里根本就没有那些数字,那怎么办?三个办法试下来,我觉得各有优劣:
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 分析XHR接口 | 最轻量、速度最快 | 需要抓包找接口规律 | 网站有开API或JSON |
| 用chromedp模拟浏览器 | 啥都能抓 | 吃内存,慢 | 页面逻辑复杂 |
| 找隐藏接口拼参数 | 不碰前端 | 参数加密困难 | 签名规则简单 |
我最喜欢的是第一种——直接找数据接口,只需要用浏览器的开发者工具,在Network栏里看看比分数据是从哪个URL返回的,多半是个JSON文件,那Go解析起来不要太爽:
type TeamScore struct {
TeamName string `json:"team_name"`
Score int `json:"score"`
}
type MatchData struct {
GameID string `json:"game_id"`
Home TeamScore `json:"home"`
Away TeamScore `json:"away"`
Status string `json:"status"`
}
再配合上encoding/json,一手的结构化数据,哪像HTML里那么难抠。
别忽略了并发控制的细节
Go的goroutine真的是把双刃剑,你要是无脑开几百个goroutine去扒网站,很容易把对方服务器搞崩,自己的IP也可能被封。
我当时写了个简单的工作池模式:
var wg sync.WaitGroup
sema := make(chan struct{}, 5) // 最多同时5个请求
for _, matchURL := range matchURLs {
wg.Add(1)
go func(url string) {
defer wg.Done()
sema <- struct{}{} // 占用一个槽位
fetchAndParse(url)
<-sema // 释放
}(matchURL)
}
wg.Wait()
这样既保证了速度,又不会太暴力,就像喝茶一样,一小口一小口地品,别一口闷。
存放数据,文件的尽头是数据库
刚开始我图省事,把比分数据直接写进了一个JSON文件,但后来发现不对劲——我要的是历史记录,每天抓一次,一个月下来文件就一万多行了,手动找数据得眼瞎。
后来换了思路,嵌入SQLite,Go这里有个go-sqlite3的包,虽然基于CGO有点烦人,但胜在稳定,你要是嫌CGO麻烦,就用modernc.org/sqlite,这是纯Go实现,不用装gcc。
存表结构大概这样:
CREATE TABLE IF NOT EXISTS cba_scores (
game_id INTEGER PRIMARY KEY,
match_date TEXT NOT NULL,
home_team TEXT NOT NULL,
away_team TEXT NOT NULL,
home_score INTEGER,
away_score INTEGER,
status TEXT,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
最后说说我踩过的一些坑
我这人写代码挺毛躁的,一轮调试下来,确实遇到了不少糟心事,挑几个说说,你以后可能用得上:
- 超时处理别忘了:
http.Client要设置Timeout,不然那边没响应你就永久卡住了 - 重定向问题:有些页面会302跳转,Go的http.Client默认跟着跳,但有时候会丢失header
- 随机User-Agent池:一个固定的ua用久了会被识别,准备个数组轮换着来
- 程序中断怎么恢复:做个断点续传的思路,记录下已经抓过哪些比赛
这个项目我断断续续搞了一周多,如今跑在服务器上,每天自动抓取,中午和晚上推送比分到我手机,别人问我CBA打得咋样了,我瞟一眼手机就能聊两句,挺有意思的,代码这东西本来就是为了解决实际问题嘛,顺手拿来主义一把,倒也让看球这件事变得更舒坦了,你要是也爱打球又爱敲代码,不妨自己动手试试。
