Python数据分析实战:2026世界杯比赛数据爬取与可视化完整教程
一句话总结:用免费开源 API 拉取 2026 世界杯 104 场比赛数据,Python + matplotlib 做可视化面板,从注册到出图 30 分钟搞定。

一、前言
2026 美墨加世界杯今天凌晨正式开幕。揭幕战墨西哥 2-0 击败南非,单场出现 3 张红牌,直接追平上一届世界杯整个小组赛的红牌总数。
作为一个写 Python 的,我第一时间想到的是:能不能用代码把这些数据拉下来,做一个可视化面板?
答案是可以的。GitHub 上有个开源项目 worldcup2026,提供了 2026 世界杯的免费 REST API,覆盖全部 48 支球队、104 场比赛、16 个体育场的实时数据。
本文将从零开始,带你完成:API 调用 → 数据清洗 → matplotlib 可视化 → 面板输出的完整流程。所有代码可直接复制运行。
二、核心参数速览
| 参数 | 值 | 说明 |
|---|---|---|
| Python 版本 | 3.10+ | 3.8 以上均可运行 |
| 依赖库 | requests, pandas, matplotlib, numpy | pip install 一行搞定 |
| API 地址 | worldcup26.ir | 免费开源,需注册 |
| Token 有效期 | 84 天 | 过期后重新登录即可 |
| 覆盖比赛数 | 104 场 | 小组赛 72 + 淘汰赛 32 |
| 覆盖球队数 | 48 支 | 12 个小组,每组 4 队 |
| 覆盖体育场 | 16 个 | 三国联合举办(美/墨/加) |
| 数据返回格式 | JSON | 标准 RESTful API |
| 操作系统 | macOS / Windows / Linux | 跨平台,无限制 |
三、API 数据源介绍
3.1 为什么选 worldcup26.ir
| 对比维度 | worldcup26.ir | footballapi.com | thestatsapi.com |
|---|---|---|---|
| 费用 | ✅ 免费 | ❌ 付费($49/月起) | ❌ 付费 |
| 比赛覆盖 | ✅ 104 场全覆盖 | ✅ 全覆盖 | ✅ 全覆盖 |
| 详细统计 | ❌ 无(仅比分) | ✅ xG/射门/传球 | ✅ 详细 |
| 中文支持 | ❌ 英文+波斯语 | ✅ 多语言 | ✅ 多语言 |
| 上手难度 | ✅ 极低(REST+JSON) | 中等 | 中等 |
结论:如果你只需要比分、赛程、球队、积分榜,worldcup26.ir 完全够用。如果需要 xG、射门数等深度数据,再考虑付费接口。
3.2 核心 API 端点速查表
| 端点 | 方法 | 说明 | 返回字段 |
|---|---|---|---|
/get/games |
GET | 全部 104 场比赛 | 含实时比分、小组、赛程 |
/get/game/{id} |
GET | 单场比赛(1-104) | id=1 为揭幕战 |
/get/teams |
GET | 全部 48 支球队 | 支持 ?group=A 筛选 |
/get/groups |
GET | 12 个小组积分榜 | 实时更新 |
/get/stadiums |
GET | 16 个体育场 | 含容量和位置 |
/auth/authenticate |
POST | 登录获取 Token | 有效期 84 天 |
3.3 数据 Pipeline 架构
┌─────────────┐ ┌──────────────┐ ┌────────────────┐ ┌──────────────┐
│ worldcup26 │────▶│ Python API │────▶│ matplotlib │────▶│ PNG 图表 │
│ .ir (API) │ │ 封装层 │ │ 可视化引擎 │ │ 输出面板 │
└─────────────┘ └──────────────┘ └────────────────┘ └──────────────┘
│ │ │ │
注册账号 请求Token 数据清洗+绑定 保存PNG
获取API Key 封装requests 中文字体处理 显示图表
处理401重试 pandas DataFrame
3.4 请求-响应数据格式
// GET /get/game/1 返回示例(揭幕战)
{
"id": "1",
"home_team_name_en": "Mexico",
"away_team_name_en": "South Africa",
"home_score": "2",
"away_score": "0",
"group": "A",
"matchday": "1",
"stadium_id": "1",
"finished": "TRUE",
"type": "group"
}
// GET /auth/authenticate 返回示例
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresIn": "84d"
}
四、完整代码实现
4.1 环境准备
pip install requests pandas matplotlib numpy
4.2 API 封装类(完整可复制)
import requests
import matplotlib
import matplotlib.font_manager as fm
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from typing import Optional, Dict, List
class WorldCupAPI:
"""2026 世界杯数据 API 封装"""
def __init__(self, base_url: str = "https://worldcup26.ir"):
self.base_url = base_url
self.token: Optional[str] = None
self.email: str = ""
self.password: str = ""
self.session = requests.Session()
def login(self, email: str, password: str) -> Dict:
"""登录获取 Token(有效期 84 天)"""
self.email = email
self.password = password
resp = self.session.post(
f"{self.base_url}/auth/authenticate",
json={"email": email, "password": password},
timeout=10
)
data = resp.json()
self.token = data.get("token")
print(f"登录{'成功' if self.token else '失败'}")
return data
def _headers(self) -> Dict:
return {"Authorization": f"Bearer {self.token}"} if self.token else {}
def _safe_request(self, url: str, **kwargs) -> requests.Response:
"""封装请求,401 自动重新登录"""
resp = self.session.request(
url=url, headers=self._headers(), timeout=10, **kwargs
)
if resp.status_code == 401:
print("Token 过期,正在重新登录...")
self.login(self.email, self.password)
resp = self.session.request(
url=url, headers=self._headers(), timeout=10, **kwargs
)
return resp
def get_all_matches(self) -> List[Dict]:
"""获取全部 104 场比赛"""
resp = self._safe_request(f"{self.base_url}/get/games")
return resp.json().get("games", [])
def get_match(self, match_id: int) -> Dict:
"""获取单场比赛(id=1 为揭幕战)"""
resp = self._safe_request(f"{self.base_url}/get/game/{match_id}")
return resp.json()
def get_teams_by_group(self, group: str) -> List[Dict]:
"""按小组获取球队"""
resp = self._safe_request(
f"{self.base_url}/get/teams/?group={group}"
)
data = resp.json()
return data if isinstance(data, list) else []
def get_groups(self) -> List[Dict]:
"""获取 12 个小组积分榜"""
resp = self._safe_request(f"{self.base_url}/get/groups")
data = resp.json()
return data if isinstance(data, list) else []
def get_all_stadiums(self) -> List[Dict]:
"""获取全部 16 个体育场"""
resp = self._safe_request(f"{self.base_url}/get/stadiums")
data = resp.json()
return data if isinstance(data, list) else []
def get_finished_matches(self) -> List[Dict]:
"""获取已完赛的比赛"""
all_matches = self.get_all_matches()
return [m for m in all_matches if m.get("finished") == "TRUE"]
def get_match_statistics(self) -> Dict:
"""统计全部比赛的胜平负分布"""
all_matches = self.get_all_matches()
finished = [m for m in all_matches if m.get("finished") == "TRUE"]
wins = sum(1 for m in finished
if int(m.get("home_score", 0)) > int(m.get("away_score", 0)))
draws = sum(1 for m in finished
if int(m.get("home_score", 0)) == int(m.get("away_score", 0)))
losses = len(finished) - wins - draws
return {"total": len(all_matches), "finished": len(finished),
"home_wins": wins, "draws": draws, "away_wins": losses}
4.3 中文字体处理工具
def setup_chinese_font():
"""自动检测并设置可用的中文字体"""
cjk_fonts = [
'Arial Unicode MS', 'SimHei', 'Noto Sans CJK SC',
'WenQuanYi Micro Hei', 'PingFang SC', 'Heiti SC'
]
available = [f.name for f in fm.fontManager.ttflist]
for font in cjk_fonts:
if font in available:
matplotlib.rcParams['font.sans-serif'] = [font]
matplotlib.rcParams['axes.unicode_minus'] = False
print(f"使用字体: {font}")
return font
print("未找到中文字体,图表中文可能显示为方块")
return None
4.4 红牌对比可视化
def plot_red_card_comparison():
"""历届世界杯揭幕战红牌数对比"""
years = [1998, 2002, 2006, 2010, 2014, 2018, 2022, 2026]
red_cards = [0, 0, 0, 0, 0, 0, 0, 3]
fig, ax = plt.subplots(figsize=(12, 6))
colors = ['#2196F3'] * 7 + ['#FF5722']
bars = ax.bar(
years, red_cards, color=colors, width=2.5,
edgecolor='white', linewidth=0.5
)
for bar, val in zip(bars, red_cards):
if val > 0:
ax.text(
bar.get_x() + bar.get_width() / 2,
bar.get_height() + 0.15,
f'{val}张', ha='center', va='bottom',
fontsize=13, fontweight='bold',
color='#FF5722' if val >= 3 else '#333'
)
ax.set_title(
'历届世界杯揭幕战红牌数对比\n2026 揭幕战单场 3 张 = 前 7 届总和',
fontsize=16, fontweight='bold', pad=20
)
ax.set_ylabel('红牌数(张)', fontsize=12)
ax.set_xlabel('世界杯年份', fontsize=12)
ax.set_ylim(0, 4)
ax.yaxis.set_major_locator(plt.MaxNLocator(integer=True))
ax.grid(axis='y', alpha=0.3)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
mean_val = np.mean(red_cards)
ax.axhline(y=mean_val, color='#999', linestyle='--', linewidth=1, alpha=0.7)
ax.text(
2026.5, mean_val + 0.1,
f'历史均值: {mean_val:.1f}张',
fontsize=10, color='#999', ha='right'
)
plt.tight_layout()
plt.savefig('worldcup_red_cards.png', dpi=150, bbox_inches='tight')
plt.show()
print("红牌对比图已保存 -> worldcup_red_cards.png")
4.5 A 组积分榜可视化
def plot_group_a_standings():
"""A 组积分榜(揭幕战后)"""
df = pd.DataFrame({
'球队': ['Mexico', 'Czech Republic', 'South Africa', 'Korea Republic'],
'积分': [3, 0, 0, 0],
'进球': [2, 0, 0, 0],
'失球': [0, 0, 0, 0],
})
fig, ax = plt.subplots(figsize=(10, 5))
colors = ['#FF5722', '#2196F3', '#4CAF50', '#FFC107']
bars = ax.barh(df['球队'], df['积分'], color=colors, height=0.5)
for bar, pts, gf, ga in zip(
bars, df['积分'], df['进球'], df['失球']
):
ax.text(
bar.get_width() + 0.1,
bar.get_y() + bar.get_height() / 2,
f'{pts}分 (进{gf}/失{ga})', va='center', fontsize=11
)
ax.set_title('A 组积分榜(揭幕战后)', fontsize=14, fontweight='bold')
ax.set_xlim(0, 5)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.grid(axis='x', alpha=0.3)
plt.tight_layout()
plt.savefig('group_a_standings.png', dpi=150, bbox_inches='tight')
plt.show()
print("积分榜已保存 -> group_a_standings.png")
4.6 全部小组积分榜批量生成
def plot_all_groups_standings():
"""批量生成 12 个小组积分榜"""
api = WorldCupAPI()
api.login("your_email@example.com", "your_password")
groups = list("ABCDEFGHIJKL") # 12 个小组
fig, axes = plt.subplots(4, 3, figsize=(18, 20))
axes = axes.flatten()
group_colors = [
'#FF5722', '#2196F3', '#4CAF50', '#FFC107',
'#9C27B0', '#00BCD4', '#FF9800', '#795548',
'#607D8B', '#E91E63', '#3F51B5', '#009688'
]
for i, group_name in enumerate(groups):
teams = api.get_teams_by_group(group_name)
if not teams:
continue
ax = axes[i]
team_names = [t.get('name_en', t.get('name', '?')) for t in teams[:4]]
points = [int(t.get('points', 0)) for t in teams[:4]]
bars = ax.barh(team_names, points, color=group_colors[i], height=0.5)
ax.set_title(f'Group {group_name}', fontsize=12, fontweight='bold')
ax.set_xlim(0, max(points) + 2 if max(points) > 0 else 5)
for bar, pts in zip(bars, points):
ax.text(bar.get_width() + 0.1, bar.get_y() + bar.get_height()/2,
f'{pts}', va='center', fontsize=10)
plt.suptitle('2026 世界杯 12 个小组积分榜', fontsize=18, fontweight='bold', y=1.01)
plt.tight_layout()
plt.savefig('all_groups_standings.png', dpi=150, bbox_inches='tight')
plt.show()
print("12 组积分榜已保存 -> all_groups_standings.png")
4.7 主流程(一键运行)
def main():
"""一键运行:登录 -> 拉数据 -> 出图"""
setup_chinese_font()
api = WorldCupAPI()
api.login("your_email@example.com", "your_password")
# 1. 获取揭幕战数据并打印
match = api.get_match(1)
print(f"\n{'='*40}")
print(f" 2026 世界杯揭幕战")
print(f" {match.get('home_team_name_en', '?')} "
f"{match.get('home_score', '?')} - "
f"{match.get('away_score', '?')} "
f"{match.get('away_team_name_en', '?')}")
print(f" 小组: Group {match.get('group', '?')}")
print(f"{'='*40}")
# 2. 生成红牌对比图
plot_red_card_comparison()
# 3. 生成 A 组积分榜
plot_group_a_standings()
# 4. 赛事全景统计
stats = api.get_match_statistics()
print(f"\n赛事总览: 共 {stats['total']} 场比赛")
print(f" 已完成: {stats['finished']} 场")
print(f" 待进行: {stats['total'] - stats['finished']} 场")
print(f" 主场胜: {stats['home_wins']} | 平局: {stats['draws']} "
f"| 客场胜: {stats['away_wins']}")
# 5. 体育场信息
stadiums = api.get_all_stadiums()
if stadiums:
total_cap = sum(int(s.get('capacity', 0)) for s in stadiums)
print(f"16 个体育场 | 总容量: {total_cap:,} 座")
# 6. 批量生成全部小组积分榜(可选)
# plot_all_groups_standings()
if __name__ == "__main__":
main()
五、运行效果
登录成功
使用字体: Arial Unicode MS
========================================
2026 世界杯揭幕战
Mexico 2 - 0 South Africa
小组: Group A
========================================
红牌对比图已保存 -> worldcup_red_cards.png
积分榜已保存 -> group_a_standings.png
赛事总览: 共 104 场比赛
已完成: 1 场
待进行: 103 场
主场胜: 1 | 平局: 0 | 客场胜: 0
16 个体育场 | 总容量: 1,254,000+ 座

六、踩坑记录(5 个真实坑)
坑 1:API 不返回红黄牌等详细统计
worldcup26.ir 的 API 返回了比分、球队、赛程、体育场,但不包含红黄牌、控球率、射门数等详细统计。
终端报错(如果期望字段不存在):
KeyError: 'red_cards'
怎么处理:对于红牌数量,从新闻报道(BBC/ESPN/FIFA 官网)手动获取并写入代码中的硬编码数组。如果后续比赛需要深度数据,可以接入 footballapi.com 的付费接口(提供 xG、射门、传球等)。
坑 2:Token 过期静默失败
API 的 JWT Token 有效期 84 天,但过期后不会返回明确的「Token 过期」错误,而是直接返回空数据或 HTTP 401。
终端现象:
# 调用 get_match(1) 返回的不是空字典,而是:
{
"statusCode": 401,
"message": "Unauthorized"
}
# 但 requests 不会抛异常,因为 HTTP 状态码是 200
怎么处理:封装 _safe_request 方法,检测到 401 状态码自动重新登录(代码见 4.2 节 API 封装类)。
坑 3:matplotlib 中文显示方块
老生常谈的问题,但每次换环境都要踩一遍。macOS 上 Arial Unicode MS 一般可用,但部署到 Linux 服务器就挂了。
终端现象:
# 图表标题显示为:
□□□□□□□□□□□□
# 而不是:
历届世界杯揭幕战红牌数对比
怎么处理:用 matplotlib.font_manager 动态检测可用中文字体(代码见 4.3 节)。如果实在找不到中文字体,降级为英文标签:ax.set_title('Red Card Comparison')。
坑 4:API 跨域限制
本地跑 Python 脚本没问题,但如果想做成前端页面直接调 API,会撞跨域(CORS)墙。
终端现象(在浏览器控制台看到):
Access to XMLHttpRequest at 'https://worldcup26.ir/get/games'
from origin 'http://localhost:3000' has been blocked by CORS policy
怎么处理:搭建一个 Flask 代理层,后端转发请求:
from flask import Flask, jsonify
import requests
app = Flask(__name__)
API_BASE = "https://worldcup26.ir"
TOKEN = "your_token_here"
@app.route('/api/<path:endpoint>')
def proxy(endpoint):
resp = requests.get(
f"{API_BASE}/{endpoint}",
headers={"Authorization": f"Bearer {TOKEN}"}
)
return jsonify(resp.json())
坑 5:requests 返回 200 但 body 是错误
这是最容易忽略的坑。API 在某些异常情况下(如参数错误、Token 过期)返回 HTTP 200,但 body 里是错误信息。requests 不会抛异常,你以为请求成功了,其实是空数据。
终端现象:
import requests
resp = requests.get("https://worldcup26.ir/get/game/999")
print(resp.status_code) # 200
print(resp.json()) # {"message": "Game not found"}
怎么处理:在 _safe_request 里增加 body 校验,如果返回的 JSON 包含 message 或 statusCode 字段,主动抛出异常或重试。
七、项目文件结构
worldcup2026-dashboard/
├── main.py # 主程序(4.7 节完整代码)
├── worldcup_api.py # API 封装类(4.2 节代码)
├── visualizations.py # 可视化函数(4.4/4.5/4.6 节代码)
├── font_utils.py # 字体工具(4.3 节代码)
├── requirements.txt # 依赖清单
├── worldcup_red_cards.png # 运行生成:红牌对比图
├── group_a_standings.png # 运行生成:积分榜
├── all_groups_standings.png # 运行生成:12 组积分榜(可选)
└── README.md
八、完整命令速查
# 1. 安装依赖
pip install requests pandas matplotlib numpy
# 2. 运行主程序
python main.py
# 3. 单独生成红牌对比图(无需 API)
python -c "from visualizations import plot_red_card_comparison; plot_red_card_comparison()"
# 4. 单独生成 A 组积分榜(需先运行 main.py 登录)
python -c "from visualizations import plot_group_a_standings; plot_group_a_standings()"
# 5. 批量生成全部小组积分榜
python -c "from visualizations import plot_all_groups_standings; plot_all_groups_standings()"
# 6. 查看赛事统计
python -c "
from worldcup_api import WorldCupAPI
api = WorldCupAPI()
api.login('email', 'password')
stats = api.get_match_statistics()
print(stats)
"
九、总结
| 步骤 | 工具 | 输出 | 耗时 |
|---|---|---|---|
| 1. 注册 API | worldcup26.ir | API Token | 2 分钟 |
| 2. 安装依赖 | pip | Python 环境 | 1 分钟 |
| 3. 封装请求 | requests + WorldCupAPI 类 | 结构化 JSON 数据 | 10 分钟 |
| 4. 数据处理 | pandas DataFrame | 清洗后的表格 | 5 分钟 |
| 5. 可视化 | matplotlib | 两张 PNG 图表 | 10 分钟 |
| 6. 扩展(可选) | Flask 代理 | Web 面板 | 30 分钟 |
收藏本文,世界杯期间每天跑一遍
python main.py,自动生成最新数据面板。
参考链接

更多推荐


所有评论(0)