很多人可能都有过这样的经历:想看一场比赛的实时比分,又懒得一直守着直播页,或者想统计一下某支球队最近的主客场胜率、场均进球数——手动刷网页、复制粘贴表格,搞到一半就放弃了。
我这次做的就是一件很“省事”的事:用 Python 写了一个体育赛事比分的采集工具,定时去抓取赛程和实时比分,落库之后再做结果统计。它不复杂,但把“采集 -> 清洗 -> 存储 -> 统计”这条链路完整走了一遍。适合刚学完 requests 和 BeautifulSoup、想找个真实项目练手的读者,也适合那些确实需要批量拉取赛事数据做分析的朋友。
先说结论:整个项目里最难的并不是“爬”,而是“如何优雅地处理实时变化的数据”和“如何让统计结果真的有用”。这两个问题想明白了,代码量其实不大。
1. 项目目标与技术选型
1.1 需求拆解:到底要做什么
在动手之前,我先把需求拆成了三个问题:
第一,数据从哪来。我需要的是比赛的基本信息(主队、客队、比赛时间、赛事名称)和实时变化的比分。如果数据源选错,后面解析写得再漂亮都是白搭。
第二,怎么做到“实时更新”。比分会随着比赛进行而变化,90分钟的比赛可能在几十秒内连续进球。我的需求是每30秒到1分钟拉一次最新状态,检测比分变化并更新数据库。
第三,结果统计要统计什么。比如某支球队的主场胜率、近10场进球分布、大小球倾向、平均进球时间等。这些统计口径需要提前定好,否则采集下来的字段不够用,后面补数据会很痛苦。
我在这儿的建议是:先做数据字典,再写爬虫。把你要统计的每一个指标列出来,反推需要哪些字段。比如算“平均进球时间”就需要记录每个进球的发生时间;算“让球胜率”就需要知道亚洲盘口的让球数。宁可多存两个字段,也不要等到分析时才后悔。
1.2 为什么优先走 API 接口,而不是解析 HTML
很多人一上手就打开网页,看到一堆 div 和 class,立刻 start 写 CSS 选择器。但我的第一反应永远是先打开开发者工具,切到 Network 面板,刷新页面看看有没有 XHR 请求。
绝大多数据体育数据平台,网页上的实时比分都是由 JavaScript 动态渲染的,真正的数据藏在背后的 JSON 接口里。比如一个比赛列表页面,HTML 里只有一个骨架,等页面加载后浏览器才会去请求类似/match/live?date=2025-01-01这样的接口,拿到 JSON 后前端再渲染。
直接用 requests 请求这个 JSON 接口,比解析 HTML 不知道省多少事。原因有三个:
- JSON 天然结构清晰,字段名是现成的
home_score、away_score,不需要从一堆嵌套标签里抠文本; - JSON 的数据量通常比 HTML 小得多,传输快、解析快;
- 接口不经常变,而页面结构换个版本就可能重写。
所以我的选型是:优先找 JSON 接口;找不到再考虑直接请求 HTML;HTML 确实是动态渲染的话,才上浏览器自动化工具。这个顺序决定了整个项目的开发效率。
1.3 技术栈与框架选择
这个项目技术栈很轻:
- 请求:
requests,配Session保持连接;需要并发时用ThreadPoolExecutor; - 解析:优先
json模块处理接口数据,兜底用lxml+xpath处理 HTML; - 存储:
SQLite。数据量在几万行以内完全够用,零配置、单文件、好迁移;等数据量大到一定程度再迁 MySQL 也不迟; - 定时调度:
APScheduler的BlockingScheduler,支持 cron 表达式,比 while True + sleep 可控得多; - 统计展示:
pandas做聚合,matplotlib画简单图表,最后输出 Markdown 报告。
如果你要抓的数据源比较多,每小时请求量上万,建议加一个代理池。我这个项目频率不高,单 IP 就够了,重点放在请求间隔和重试策略上。
2. 数据源分析与请求处理
2.1 从抓包开始:找到真实的数据接口
我用了一个常见的体育数据平台做演示。打开比赛列表页,按 F12 进入开发者工具,切到 Network 面板,刷新页面,过滤 XHR 请求,很快就看到一条长这样的请求:
GET https://api.example-sports.com/match/live?date=2025-01-01&league_id=55
返回内容是一段 JSON,大致长这样:
{ "code": 0, "data": { "matches": [ { "match_id": "123456", "league_name": "某足球联赛", "home_team": "主队A", "away_team": "客队B", "home_score": 2, "away_score": 1, "match_status": "live", "match_time": "2025-01-01 20:00:00", "events": [ {"type": "goal", "team": "home", "minute": 23, "player": "球员X"}, {"type": "goal", "team": "away", "minute": 45, "player": "球员Y"} ] } ] } }这一步是整个项目的命脉。我花了可能有40%的时间在这里:翻遍了所有请求,逐条看返回数据,确认哪个接口是赛程列表、哪个接口是实时比分、哪个接口是赛果详情。这里给新手一个非常实际的建议:不要盯着一条接口就开写,至少找到以下三类接口:
- 赛程接口:返回未来或过去的比赛列表,用于初始化数据库;
- 实时比分接口:返回正在进行的比赛及比分,用于轮询更新;
- 赛果详情接口:返回完场后的详细事件,用于补充进球时间等统计字段。
把这三类接口的 URL、参数、返回结构记录下来,做成一份接口文档。之后写代码就有据可查,不再靠记忆。
2.2 参数构造:日期、分页与语言
接口参数每个平台不同,但核心就几类:
- 日期参数:很多平台默认只返回当天的比赛,所以必须显式传
date。我一般是动态生成最近7天的日期,逐个请求; - 分页参数:
page和page_size。