开源足球数据爬虫 Understat 上手教程:3 分钟抓取英超 xG 与球队战绩 开源足球数据爬虫 Understat 上手教程3 分钟抓取英超 xG 与球队战绩【免费下载链接】understatAn asynchronous Python package for https://understat.com/.项目地址: https://gitcode.com/gh_mirrors/un/understat想拿英超、西甲的 xG预期进球、PPDA 这类高级足球数据做分析你通常只有两个选择给商业 API 交一年几万美元的年费或者去 understat.com 手写爬虫——然后被它的动态数据接口和 JS 渲染折磨几个小时。开源足球数据爬虫Understat就是为解决这个问题而生的它是一个基于 aiohttp 的异步 Python 包免费、开源、pip install后 3 分钟就能跑通第一个查询。这篇文章带你从最小示例开始一路做到一个完整的赛季复盘脚本。项目速览它解决什么问题边界在哪里Understat 是一个异步 Python 包封装了 understat.com 的全部数据接口。你只需传入一个aiohttp.ClientSession就能拿到联赛球员统计、球队战绩、完整积分榜含 PPDA/OPPDA/xPTS、单场射门事件、球员逐场数据等。它适合你有 Python 3.6 和基础 asyncio 知识需要把 xG、xA、npxG 这类高级指标用于自己的分析项目想避开 Selenium/Playwright 这类重型爬虫方案它的边界也要说清楚数据只覆盖 EPL、La_liga、Bundesliga、Serie_A、Ligue_1、RFPL 六个联赛数据源是第三方站点不保证实时性和永久可用所有返回字段都是字符串后面会讲这个坑。不适合需要官方授权数据或毫秒级实时推送的场景。最小可运行示例3 分钟抓出博格巴的赛季数据先装依赖。包发布在 PyPI 上一条命令即可pip install understat源码安装方式也支持从https://gitcode.com/gh_mirrors/un/understat克隆后执行pip install .即可。安装文档见 docs/user/installation.rst。然后写一个脚本查英超 2018 赛季即 2018-19曼联球员博格巴的数据import asyncio import json import aiohttp from understat import Understat async def main(): async with aiohttp.ClientSession() as session: # 复用会话避免重复建连 understat Understat(session) # 核心类只需要一个会话 player await understat.get_league_players( epl, 2018, player_namePaul Pogba, # 用关键字参数做过滤 team_titleManchester United, ) print(json.dumps(player, indent2)) asyncio.run(main())逐行解释Understat(session)这是唯一的入口类session由你传入连接池归你管。get_league_players(epl, 2018, ...)第一个参数是联赛名epl/la_liga/bundesliga/serie_a/ligue_1/rfpl第二个是赛季年份后面的关键字参数都是过滤条件。asyncio.run(main())所有方法都是协程必须放在事件循环里跑。运行后你会看到博格巴当赛季 27 场比赛、11 进球、13.36 xG、9 助攻这类完整字段。恭喜你的第一条足球数据已经到手。由浅入深从会用、用熟到用精第一层会用摸清 API 全景找对方法先建立一张方法地图避免每次查文档。核心源码在 understat/understat.py共 16 个方法按数据主体可以分成四组understat Understat(session) │ ├── 联赛维度league_* │ ├── get_league_players 联赛全部球员统计 │ ├── get_league_results 已结束的比赛含比分与 xG │ ├── get_league_fixtures 未开始的赛程 │ ├── get_league_table 积分榜含 PPDA/OPPDA/xPTS已算好 │ └── get_teams 各队赛季汇总含逐场 history │ ├── 球队维度team_* │ ├── get_team_results / get_team_fixtures │ ├── get_team_players / get_team_stats │ └── 参数是队名字符串如 Manchester United │ ├── 球员维度player_*参数是球员 ID │ ├── get_player_shots 逐次射门事件 │ ├── get_player_matches 逐场比赛表现 │ ├── get_player_stats 按位置的平均表现 │ └── get_player_grouped_stats 按赛季/位置/进球方式分组 │ └── 单场维度match_*参数是比赛 ID ├── get_match_players 双方出场球员数据 ├── get_match_shots 全场逐次射门 └── get_match_stats 单场比分、xG、控球等汇总一个很实用的规律参数是队名/联赛名还是ID决定了你要不要先查一次拿到 ID。比如get_player_shots(619)里的 619 是阿圭罗的球员 ID你需要先从get_league_players的结果里拿到id字段。试试把上面的示例换成get_league_results(epl, 2018)你会看到每场比赛的比分、双方 xG 和胜负预测forecast 字段。第二层用熟用 options 过滤把数据量降下来数据接口返回的是全量列表比如英超一赛季几千条球员记录。直接在内存里过滤不如在请求时就缩小范围。utils.py中的filter_data实现了通用过滤只要字段名和值完全相等就保留。# 等价写法options 字典 或 关键字参数二者选一 players await understat.get_league_players( epl, 2018, {position: F S, yellow_cards: 3}) forwards await understat.get_league_players( epl, 2018, positionF S)这里有两个容易踩的点值是字符串。yellow_cards的值是3而不是3传错类型会过滤出空列表。代码见 understat/utils.py 的filter_data它做的是严格相等比较。嵌套字段要传完整对象。过滤主场比赛时h字段是一个字典必须整个传{h: {id: 89, title: Manchester United, short_title: MUN}}只传一部分匹配不上。建议先用一个不带过滤的调用拿到真实数据打印一条看看字段结构再写过滤条件能省掉大半调试时间。第三层用精并发拉取与现成的积分榜单次请求的瓶颈在网络延迟而不是计算。用asyncio.gather可以一次性并行拉取多个赛季或多支球队的数据这是异步方案相对同步爬虫的核心优势async def load_teams(session, season): understat Understat(session) teams [Liverpool, Manchester City, Arsenal, Chelsea] results await asyncio.gather( *(understat.get_team_results(t, season) for t in teams)) return dict(zip(teams, results))注意两点所有协程共享同一个session这正是连接池的用武之地但并发数要克制几十个并发对一个第三方站点不算友好建议限制在 5 到 10 个以内。另一个省力点如果你想算 PPDA每次防守动作允许的传球次数不必自己从ppda.att / ppda.def去算。get_league_table已经把所有球队的 PPDA、OPPDA、DC、xPTS 全部算好并排好序table await understat.get_league_table(epl, 2019) # 返回 表头 每行数据按积分降序、净胜球次降序排列它还支持h_ahome取主场积分榜以及start_date/end_date取某个日期区间的积分榜。实现细节见 understat/understat.py 的get_league_table。完整实战从原始数据到一份球队复盘报告把前面学到的串起来做一个真实任务复盘某支球队一个赛季的运气。思路是——用 xG 对比实际进球找出被高估和被低估的地方。import asyncio import aiohttp from understat import Understat async def team_report(team, season): async with aiohttp.ClientSession() as session: u Understat(session) # 1. 拉取该队整个赛季的逐场战绩 results await u.get_team_results(team, season) # 2. 拉取该队全队球员统计用于找该进没进的人 players await u.get_team_players(team, season) # 3. 汇总赛季 xG 与实际进球/失球 xg_for sum(float(r[xG][h if r[side] h else a]) for r in results) xg_ag sum(float(r[xGA]) for r in results) goals sum(int(r[goals][h if r[side] h else a]) for r in results) # 4. 找出实际进球明显低于 xG 的球员浪费机会者 wasteful sorted(players, keylambda p: float(p[xG]) - float(p[goals])) top wasteful[-3:] print(f{team} {season} 赛季复盘) print(f 进球 {goals} vs xG {xg_for:.1f} → {高估 if goals xg_for else 被低估}) print(f 失球 vs xGA → 预期差 {goals - xg_for:.1f}) for p in top: print(f {p[player_name]}: 进球 {p[goals]}, xG {float(p[xG]):.1f}) asyncio.run(team_report(Manchester United, 2019))这四条关键点对应了三种能力get_team_results提供逐场 xG会用r[xG][h if ...]处理嵌套结构、float()处理字符串字段用熟两次请求并发走同一个会话、拿到数据立即关会话用精。跑完之后你手上就是一份可以直接写进报告的结构化结论而不是一堆散落的 JSON。工程细节性能、缓存与合规要点会话复用整个生命周期只创建一次aiohttp.ClientSession结束时await session.close()。不要每个请求新建会话连接池开销远大于请求本身。本地缓存赛季数据几乎不变把结果按(方法名, 联赛, 赛季, 过滤条件)存成 JSON 或 SQLite能省掉 90% 的重复请求。简单做法是用functools.lru_cache包装协程注意缓存的是返回列表别让上层误改。请求节奏并发数建议 5 到 10脚本循环之间加asyncio.sleep(0.5)左右的间隔。这不是商业 API请把服务端当共享资源对待。异常与重试给每个请求包一层try/except aiohttp.ClientError配合指数退避重试 2 到 3 次对json.JSONDecodeError不要重试直接检查接口是否返回了 HTML 错误页。字段类型除get_league_table和get_team_stats等少数方法外大部分数值字段是字符串做运算前记得float()。合规遵守 understat.com 的服务条款控制请求频率数据仅供学习研究注意商业用途的版权限制。项目本身与 Understat 官方无关见 docs/index.rst 的声明。排坑手册常见报错与快速定位ModuleNotFoundError: aiohttpaiohttp 是独立依赖pip install aiohttp或确认你安装的 understat 版本包含依赖setup.py 的install_requires里声明了它。RuntimeError: no running event loop把代码包进asyncio.run(main())不要在模块顶层直接await。过滤后返回空列表十有八九是值类型不对。打印一条原始记录核对yellow_cards是3不是3嵌套对象必须完整匹配。ValueError: could not convert string to float返回字段是字符串先float()再计算个别字段如goals在 fixture 里是None过滤掉isResult为 False 的记录再算。请求返回 HTML 或 403可能是触发频率限制或会话过期。降低并发、加间隔重试确认请求头带上了X-Requested-With: XMLHttpRequestutils 的fetch已默认处理勿手动覆盖。赛季参数对不上2018 代表 2018-19 赛季不是自然年 2018。想查 2024-25 赛季就传 2024。拿不到某队数据先确认联赛属于六大赛区。队名用官方写法空格会自动转成下划线但大小写和拼写要精确例如Manchester United。收尾文档入口与一句话总结完整 API 参考含每个方法的示例输出在 docs/classes/understat.rst过滤与日期工具的实现在 understat/utils.py测试用例在 tests/里面几乎覆盖了每个方法的所有过滤组合是最快的用法速查表。一句话总结Understat 用不到一百行的核心代码把一个需要处理动态接口、JS 渲染和 JSON 清洗的数据获取问题压缩成了传个会话、调一个方法、拿回干净的 Python 对象它是你绕开商业 API 费用、快速上手足球数据爬虫的最短路径。现在打开你的编辑器先跑通第一个查询剩下的交给数据本身。【免费下载链接】understatAn asynchronous Python package for https://understat.com/.项目地址: https://gitcode.com/gh_mirrors/un/understat创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

最新新闻

whonow的演进与未来:DNS重绑定工具的CHANGELOG解读、现有局限与AAAA支持路线图

whonow的演进与未来:DNS重绑定工具的CHANGELOG解读、现有局限与AAAA支持路线图

whonow的演进与未来:DNS重绑定工具的CHANGELOG解读、现有局限与AAAA支持路线图 【免费下载链接】whonow A "malicious" DNS server for executing DNS Rebinding attacks on the fly (public instance running on rebind.network:53) 项目地址: https:/…

2026/8/19 15:34:59
免费NTFS读写工具Nigate保姆级上手教程:Mac读写U盘难题一招解决

免费NTFS读写工具Nigate保姆级上手教程:Mac读写U盘难题一招解决

免费NTFS读写工具Nigate保姆级上手教程:Mac读写U盘难题一招解决 【免费下载链接】Free-NTFS-for-Mac Nigate: An open-source NTFS utility for Mac. It supports all Mac models (Intel and Apple Silicon), providing full read-write access, mounting, and mana…

2026/8/19 15:34:59
如何在 AMD MI350 上部署 DeepSeek-V4-Flash-MXFP4:vLLM + ROCm 从零实战指南

如何在 AMD MI350 上部署 DeepSeek-V4-Flash-MXFP4:vLLM + ROCm 从零实战指南

如何在 AMD MI350 上部署 DeepSeek-V4-Flash-MXFP4:vLLM ROCm 从零实战指南 【免费下载链接】DeepSeek-V4-Flash-MXFP4 项目地址: https://ai.gitcode.com/hf_mirrors/amd/DeepSeek-V4-Flash-MXFP4 想在自己的 AMD MI350 服务器上跑起 DeepSeek-V4-Flash-M…

2026/8/19 15:34:59
RT-Thread快速入门:从裸机到多任务并发的嵌入式开发实战

RT-Thread快速入门:从裸机到多任务并发的嵌入式开发实战

1. 从裸机到RTOS:为什么我们需要一个“管家” 如果你是从51单片机或者STM32的HAL库、标准库开始接触嵌入式开发的,那你对“裸机编程”一定不陌生。主函数里一个 while(1) 大循环,里面轮询处理各种任务:检测按键、刷新屏幕、读取…

2026/8/19 15:34:59
基于LLM的二进制漏洞智能分析:从反汇编到自动化推理

基于LLM的二进制漏洞智能分析:从反汇编到自动化推理

1. 项目概述:当大语言模型遇上“裸奔”的二进制文件在安全研究领域,逆向工程和漏洞挖掘一直是技术门槛极高的“硬核”工作。传统的静态分析工具在面对经过编译、剥离了符号和调试信息的“裸”二进制文件(Stripped Binaries)时&…

2026/8/19 15:34:59
家庭自动化系统搭建指南:从核心概念到实战部署

家庭自动化系统搭建指南:从核心概念到实战部署

1. 从“智能”到“自动化”:一个被误解的起点提到“Home Automation”,很多人脑子里蹦出来的第一个画面,可能是用手机App远程打开客厅的灯,或者对着智能音箱喊一声“我回来了”。这没错,但这只是智能家居的冰山一角&am…

2026/8/19 15:29:59