ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python实战:从零搭建足球数据分析项目,复盘08-09赛季

Python实战:从零搭建足球数据分析项目,复盘08-09赛季 最近在整理历史足球数据时发现08-09赛季是一个充满故事的赛季。无论是英超的“四大豪门”争霸、西甲的“梦三”巴萨启航还是意甲的国米连冠其背后都隐藏着海量的数据故事。单纯看比分和集锦我们只能知道“发生了什么”但结合数据我们才能理解“为什么会发生”以及“如何发生的”。本文将以08-09赛季为背景手把手带你从零开始搭建一个足球数据分析项目。我们将使用Python作为主要工具涵盖数据获取、清洗、存储、分析和可视化全流程最终目标是能通过数据复盘那个经典赛季并挖掘出一些有趣的洞察。无论你是足球爱好者想深入了解历史还是数据分析初学者想找一个有趣的项目练手这篇文章都能为你提供一套完整的、可复现的实战方案。1. 背景与核心概念为什么是08-09赛季的数据在开始敲代码之前我们有必要先明确分析对象的价值。2008-2009赛季在欧洲足坛是一个承上启下的关键节点。从足球技战术演变看这个赛季是“控球至上”哲学开始统治欧洲的起点。巴塞罗那在瓜迪奥拉的带领下开启了“梦三王朝”其极高的控球率和精准的传球网络改变了人们对足球比赛的认知。同时穆里尼奥的国际米兰展示了极致的防守反击英超则延续着高节奏、强对抗的风格。多种战术流派同台竞技为数据分析提供了丰富的对比样本。从数据可得性看08-09赛季处于现代足球数据统计开始系统化、但尚未爆炸性增长的时期。像“预期进球xG”这类高阶数据在当时还未普及但基础的比赛数据射门、传球、犯规等已经相对完善。这为我们进行“传统数据”分析提供了良好的基础同时也可以尝试用现代的数据思维去重新审视这些历史数据比如计算球队的“控球效率”或“射门转化率”。从项目学习角度看分析一个完整的历史赛季涉及从数据采集到结论呈现的全链路。你将会遇到真实数据中常见的各种问题如数据缺失、格式不一致、定义模糊等并学习如何解决它们。最终我们不仅能得到关于08-09赛季的结论更能掌握一套处理时序体育数据的方法论这套方法可以轻松迁移到分析其他赛季或其他体育项目上。简单来说数据是凝固的历史分析是让历史开口说话的工具。我们本项目要做的就是为08-09这个精彩的赛季打造一套专属的“数据翻译器”。2. 环境准备与版本说明工欲善其事必先利其器。为了保证教程的可复现性下面列出本项目推荐的环境配置。如果你的环境有所不同请根据实际情况调整依赖库的版本。操作系统Windows 10/11, macOS, 或 Linux (如 Ubuntu) 均可。本文示例命令以 macOS/Linux 的终端为主Windows 用户可在 PowerShell 或 WSL 中执行相应命令。编程语言与核心版本Python 3.8这是我们的主力编程语言。建议使用 3.8 及以上版本以确保所有库的兼容性。PipPython 的包管理工具通常随 Python 安装。主要第三方库 我们将使用以下库请通过pip安装。创建一个requirements.txt文件来管理依赖是个好习惯。# requirements.txt pandas1.5.3 # 数据分析核心库用于数据清洗、处理和分析 numpy1.24.3 # 数值计算基础库pandas的依赖也用于数学运算 requests2.31.0 # 用于从网络API或网页获取数据 beautifulsoup44.12.2 # 用于解析HTML网页爬取数据 lxml4.9.3 # BeautifulSoup的解析器速度较快 sqlalchemy2.0.23 # ORM工具用于将数据方便地存入数据库 matplotlib3.7.2 # 基础绘图库用于创建静态图表 seaborn0.12.2 # 基于matplotlib的统计图表库绘图更美观 jupyter1.0.0 # 可选用于在笔记本环境中交互式分析在项目根目录下执行以下命令一键安装所有依赖pip install -r requirements.txt开发工具代码编辑器/IDE强烈推荐使用VS Code或PyCharm。它们对Python、Jupyter Notebook和数据科学有很好的支持。数据库为了持久化存储数据我们将使用轻量级的SQLite。它无需安装Python 内置sqlite3模块即可操作。这对于学习和中小型项目完全足够。项目结构 在开始前建议先建立如下清晰的目录结构这有助于管理代码和数据。football-data-0809/ ├── data/ # 存放原始和清洗后的数据文件 │ ├── raw/ # 原始数据如爬取的CSV、JSON │ └── processed/ # 清洗后的数据 ├── src/ # 源代码 │ ├── crawler.py # 数据爬取脚本 │ ├── cleaner.py # 数据清洗脚本 │ ├── analyzer.py # 数据分析脚本 │ └── visualizer.py # 数据可视化脚本 ├── notebooks/ # Jupyter Notebook文件用于探索性分析 ├── output/ # 生成的图表、报告 ├── requirements.txt # 项目依赖列表 └── README.md # 项目说明文档现在你的基础环境已经就绪。接下来我们将进入最关键的环节获取数据。3. 核心步骤一数据获取与爬虫实践对于历史赛季数据一个可靠的来源至关重要。我们可以从一些公开的足球数据网站获取。请注意在爬取任何网站数据前务必查看该网站的robots.txt文件和使用条款尊重网站的爬虫规则避免高频请求对服务器造成压力。本教程将以教育和学习为目的演示基础爬虫思路。我们计划爬取08-09赛季英超联赛的球队基本数据例如积分、胜平负、进球、失球等。这里以一个假设的、结构简单的公开数据页面为例。3.1 分析目标页面结构假设我们目标页面的URL结构为http://example.com/league/EPL/2009。首先我们需要用浏览器开发者工具F12查看网页结构找到包含联赛积分表的HTML元素。通常这类数据会放在一个table标签中。我们需要找到这个table的独特标识比如id或class。3.2 编写爬虫脚本在src/crawler.py中我们编写爬取逻辑。# src/crawler.py import requests from bs4 import BeautifulSoup import pandas as pd import time import os def scrape_premier_league_0809(): 爬取08-09赛季英超联赛积分榜数据。 这是一个示例函数实际URL和解析规则需要根据真实网站调整。 # 目标URL (示例请替换为真实可用的数据源) url http://example.com/league/EPL/2009 # 设置请求头模拟浏览器访问 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 } try: print(f正在请求页面: {url}) response requests.get(url, headersheaders, timeout10) response.raise_for_status() # 如果状态码不是200抛出HTTPError异常 except requests.exceptions.RequestException as e: print(f网络请求失败: {e}) return None # 使用BeautifulSoup解析HTML内容指定lxml解析器 soup BeautifulSoup(response.content, lxml) # 假设数据在id为‘league-table’的table中 # 你需要根据实际网页结构修改这里的选择器 table soup.find(table, {id: league-table}) if not table: print(未找到积分榜表格请检查网页结构或选择器。) return None # 使用pandas直接读取HTML中的表格这通常是最快的方法 # 注意read_html返回的是一个DataFrame列表 df_list pd.read_html(str(table)) if df_list: df df_list[0] # 假设第一个表格就是我们需要的 print(数据爬取成功) print(df.head()) # 预览前5行数据 return df else: print(未能从表格中解析出数据。) return None def save_data(df, filenamepremier_league_0809_raw.csv): 将DataFrame保存为CSV文件到data/raw目录 raw_data_path os.path.join(data, raw) os.makedirs(raw_data_path, exist_okTrue) # 如果目录不存在则创建 file_path os.path.join(raw_data_path, filename) df.to_csv(file_path, indexFalse, encodingutf-8-sig) # utf-8-sig避免中文乱码 print(f原始数据已保存至: {file_path}) if __name__ __main__: # 执行爬虫 data_df scrape_premier_league_0809() if data_df is not None: # 保存数据 save_data(data_df) print(爬虫任务完成) else: print(爬虫任务失败未获取到数据。)关键点解释与注意事项请求头User-Agent许多网站会屏蔽没有User-Agent的请求被认为是爬虫。添加一个常见的浏览器UA可以绕过简单的反爬。异常处理网络请求可能失败超时、404等使用try...except包裹并给出友好提示是必须的。解析器选择lxml解析器需要单独安装已在requirements中它比Python内置的html.parser更快更强大。选择器soup.find(‘table‘, {‘id‘: ‘league-table‘})是定位元素的关键。你需要根据实际网页的HTML结构来调整id或class的值。这是爬虫编写中最需要耐心调试的部分。pd.read_htmlPandas的这个函数非常强大可以直接将HTML中的表格转换为DataFrame。但它可能无法完美解析所有复杂表格必要时仍需手动遍历table的tr和td标签。延迟与礼貌在循环爬取多个页面时务必在请求之间添加time.sleep(2)等延迟避免对目标服务器造成过大负担。运行这个脚本后你将在data/raw/目录下得到一个premier_league_0809_raw.csv文件里面就是爬取到的原始积分榜数据。4. 核心步骤二数据清洗与预处理爬取到的原始数据往往很“脏”可能存在缺失值、重复行、格式不一致、无关字符等问题。数据清洗是数据分析中至关重要且耗时的一步直接决定了后续分析的质量。让我们在src/cleaner.py中创建一个数据清洗函数。# src/cleaner.py import pandas as pd import numpy as np import os def load_raw_data(filenamepremier_league_0809_raw.csv): 从data/raw目录加载原始CSV数据 file_path os.path.join(data, raw, filename) if not os.path.exists(file_path): raise FileNotFoundError(f原始数据文件未找到: {file_path}) df pd.read_csv(file_path, encodingutf-8-sig) print(f成功加载数据形状: {df.shape}) return df def clean_league_data(df): 清洗联赛积分榜数据。 这是一个通用流程具体清洗步骤需视原始数据情况而定。 # 1. 创建副本避免修改原数据 df_clean df.copy() print( 开始数据清洗 ) # 2. 查看基本信息 print(1. 数据前5行:) print(df_clean.head()) print(\n2. 数据信息:) print(df_clean.info()) print(\n3. 数值列统计描述:) print(df_clean.describe()) # 3. 处理列名去除空格、换行符统一为小写 df_clean.columns df_clean.columns.str.strip().str.lower().str.replace( , _) print(f\n4. 标准化后的列名: {list(df_clean.columns)}) # 4. 处理缺失值 print(f\n5. 缺失值统计:\n{df_clean.isnull().sum()}) # 策略数值列用中位数填充球队名列等关键列若有缺失则删除整行 for col in df_clean.columns: if df_clean[col].dtype in [int64, float64]: # 数值列用中位数填充 if df_clean[col].isnull().any(): median_val df_clean[col].median() df_clean[col].fillna(median_val, inplaceTrue) print(f - 数值列 {col} 的缺失值已用中位数 {median_val} 填充。) else: # 非数值列如球队名删除缺失行 null_count df_clean[col].isnull().sum() if null_count 0: df_clean.dropna(subset[col], inplaceTrue) print(f - 因列 {col} 有 {null_count} 个缺失值已删除对应行。) # 5. 处理重复行 duplicate_count df_clean.duplicated().sum() if duplicate_count 0: df_clean.drop_duplicates(inplaceTrue) print(f\n6. 发现并删除了 {duplicate_count} 个完全重复的行。) else: print(f\n6. 未发现完全重复的行。) # 6. 数据格式转换与创建新特征 # 假设原始数据有‘matches_played‘比赛场次, ‘points‘积分等列 # 我们可以计算‘points_per_match‘场均积分这个新特征 if all(col in df_clean.columns for col in [points, matches_played]): df_clean[points_per_match] df_clean[points] / df_clean[matches_played] df_clean[points_per_match] df_clean[points_per_match].round(2) print(f - 已创建新特征 ‘points_per_match‘ (场均积分)。) # 7. 重置索引 df_clean.reset_index(dropTrue, inplaceTrue) print(f\n 清洗完成 ) print(f清洗后数据形状: {df_clean.shape}) return df_clean def save_clean_data(df, filenamepremier_league_0809_clean.csv): 将清洗后的DataFrame保存为CSV文件到data/processed目录 processed_data_path os.path.join(data, processed) os.makedirs(processed_data_path, exist_okTrue) file_path os.path.join(processed_data_path, filename) df.to_csv(file_path, indexFalse, encodingutf-8-sig) print(f清洗后的数据已保存至: {file_path}) if __name__ __main__: # 加载原始数据 try: raw_df load_raw_data() except FileNotFoundError as e: print(e) exit(1) # 清洗数据 clean_df clean_league_data(raw_df) # 保存清洗后的数据 save_clean_data(clean_df)清洗逻辑详解探索性查看使用head(),info(),describe()快速了解数据全貌这是制定清洗策略的基础。列名标准化混乱的列名是后续分析的噩梦。统一为小写并用下划线连接能极大提升代码可读性。缺失值处理这是核心步骤。没有放之四海而皆准的方法需要根据业务逻辑判断。数值列积分、进球用中位数填充比用均值更稳健不易受极端值影响。关键标识列球队名如果缺失这行数据很可能无效直接删除。重复值处理使用duplicated()和drop_duplicates()轻松处理。特征工程在清洗阶段就可以开始创造新的、更有意义的特征。例如“场均积分”比单纯的“总积分”更能反映球队在整个赛季的稳定表现。保存结果将清洗后的干净数据单独保存与原始数据隔离。这是数据管道中良好的实践。运行清洗脚本后你将在data/processed/目录下得到一份干净、规整的数据集可以放心地用于后续分析。5. 核心步骤三数据分析与洞察挖掘有了干净的数据我们就可以开始提出具体问题并用数据来回答了。我们创建一个src/analyzer.py文件来进行分析。假设我们的清洗后数据包含以下列team,matches_played,wins,draws,losses,goals_for,goals_against,points,points_per_match。# src/analyzer.py import pandas as pd import numpy as np import os import matplotlib.pyplot as plt import seaborn as sns # 设置中文字体和图表样式可选如需显示中文 # plt.rcParams[font.sans-serif] [SimHei] # 用来正常显示中文标签 # plt.rcParams[axes.unicode_minus] False # 用来正常显示负号 sns.set_style(whitegrid) # 设置seaborn绘图风格 def load_clean_data(filenamepremier_league_0809_clean.csv): 加载清洗后的数据 file_path os.path.join(data, processed, filename) df pd.read_csv(file_path, encodingutf-8-sig) print(f分析数据加载成功共 {df.shape[0]} 支球队{df.shape[1]} 个特征。) return df def basic_analysis(df): 基础统计分析 print(*50) print(基础统计分析) print(*50) # 1. 冠军、降级区 champion df.loc[df[points].idxmax()] print(f冠军球队: {champion[team]}积分: {champion[points]}) # 假设最后三名降级 (真实情况需查规则如英超当季是最后三名降级) relegated df.nsmallest(3, points) print(f\n降级球队 (积分最低的三支):) for _, row in relegated.iterrows(): print(f - {row[team]}: {row[points]} 分) # 2. 进攻与防守排行榜 print(f\n进攻最强 (进球最多) 前三名:) top_attack df.nlargest(3, goals_for) for _, row in top_attack.iterrows(): print(f - {row[team]}: {row[goals_for]} 球) print(f\n防守最佳 (失球最少) 前三名:) top_defense df.nsmallest(3, goals_against) for _, row in top_defense.iterrows(): print(f - {row[team]}: {row[goals_against]} 球) # 3. 净胜球分布 df[goal_difference] df[goals_for] - df[goals_against] print(f\n净胜球最高的球队: {df.loc[df[goal_difference].idxmax(), team]} ({df[goal_difference].max()} 球)) print(f净胜球最低的球队: {df.loc[df[goal_difference].idxmin(), team]} ({df[goal_difference].min()} 球)) return df # 返回添加了新列‘goal_difference‘的DataFrame def advanced_metrics(df): 计算高级指标 print(\n *50) print(高级指标计算) print(*50) # 1. 胜率、平率、负率 df[win_rate] (df[wins] / df[matches_played]).round(3) df[draw_rate] (df[draws] / df[matches_played]).round(3) df[loss_rate] (df[losses] / df[matches_played]).round(3) # 2. 场均进球、场均失球 df[goals_for_per_match] (df[goals_for] / df[matches_played]).round(2) df[goals_against_per_match] (df[goals_against] / df[matches_played]).round(2) # 3. 积分效率 (实际积分/最大可能积分)。最大可能积分 比赛场次 * 3 max_possible_points df[matches_played] * 3 df[points_efficiency] (df[points] / max_possible_points).round(3) print(已计算胜率、场均进球、积分效率等高级指标。) print(df[[team, win_rate, goals_for_per_match, points_efficiency]].head()) return df def correlation_analysis(df): 相关性分析哪些因素与积分最相关 print(\n *50) print(特征相关性分析) print(*50) # 选择数值型特征 numeric_cols [points, wins, draws, losses, goals_for, goals_against, goal_difference, win_rate, goals_for_per_match, points_efficiency] # 确保这些列都存在 numeric_cols [col for col in numeric_cols if col in df.columns] correlation_matrix df[numeric_cols].corr() # 找出与‘points‘相关性最高的特征 points_corr correlation_matrix[points].sort_values(ascendingFalse) print(特征与‘积分‘的相关性排序 (从高到低):) for feat, corr_val in points_corr.items(): print(f {feat:25s}: {corr_val:.3f}) # 可视化相关性热力图 plt.figure(figsize(10, 8)) sns.heatmap(correlation_matrix, annotTrue, fmt.2f, cmapcoolwarm, center0, squareTrue, linewidths.5, cbar_kws{shrink: .8}) plt.title(08-09赛季英超球队数据相关性热力图) plt.tight_layout() # 保存图表 output_dir output os.makedirs(output_dir, exist_okTrue) plt.savefig(os.path.join(output_dir, correlation_heatmap.png), dpi300) print(f\n相关性热力图已保存至: {output_dir}/correlation_heatmap.png) # plt.show() # 如果在Jupyter中运行可以显示 return points_corr if __name__ __main__: # 加载数据 df load_clean_data() # 执行基础分析 df basic_analysis(df) # 计算高级指标 df advanced_metrics(df) # 进行相关性分析 corr_result correlation_analysis(df) # (可选) 将增强后的分析数据保存下来 analysis_output_path os.path.join(data, processed, premier_league_0809_analyzed.csv) df.to_csv(analysis_output_path, indexFalse, encodingutf-8-sig) print(f\n完整的分析数据已保存至: {analysis_output_path})分析思路解读基础分析回答最直接的问题——谁赢了谁输了谁进攻强谁防守好这利用了Pandas的排序和索引功能。高级指标原始数据是总计但不同球队比赛场次相同我们可以将其“标准化”计算比率类指标如胜率、场均进球等。这能让我们进行更公平的跨球队比较。“积分效率”是一个有趣的指标它衡量了球队获取理论最大积分的比例反映了稳定性和抢分能力。相关性分析这是挖掘数据背后故事的关键。我们计算所有数值特征之间的相关系数皮尔逊相关系数。例如我们发现“胜场数”与“积分”的相关性必然极高因为赢球得3分。但更有趣的是“净胜球”与“积分”的相关性可能也非常高这说明了攻守平衡的重要性。通过热力图我们可以直观地看到所有变量之间的关系。运行这个分析脚本你不仅会在控制台看到各种文字结论还会在output/文件夹下生成一张精美的相关性热力图。6. 核心步骤四数据可视化与故事呈现数字是冰冷的图表却能讲故事。让我们用更多的图表来全方位展示08-09赛季英超的故事。我们在src/visualizer.py中创建可视化函数。# src/visualizer.py import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import os # 设置全局样式 plt.style.use(seaborn-v0_8-whitegrid) sns.set_palette(husl) def create_league_table_chart(df): 创建积分榜可视化条形图展示前6名球队的积分 top_n 6 df_sorted df.sort_values(points, ascendingFalse).head(top_n) fig, ax plt.subplots(figsize(10, 6)) bars ax.barh(df_sorted[team], df_sorted[points], colorsns.color_palette(Blues_r, top_n)) ax.set_xlabel(积分, fontsize12) ax.set_title(f08-09赛季英超积分榜 (前{top_n}名), fontsize14, fontweightbold) ax.invert_yaxis() # 让第一名在最上面 # 在条形末端显示积分值 for bar in bars: width bar.get_width() ax.text(width 0.5, bar.get_y() bar.get_height()/2, f{int(width)}, haleft, vacenter, fontsize10) plt.tight_layout() save_path os.path.join(output, league_table_top6.png) plt.savefig(save_path, dpi300) plt.close(fig) # 关闭图形避免在非交互环境下重叠 print(f积分榜条形图已保存: {save_path}) def create_goals_scatter(df): 创建进球-失球散点图观察球队攻防风格 fig, ax plt.subplots(figsize(11, 7)) scatter ax.scatter(df[goals_for], df[goals_against], sdf[points]*5, # 用点的大小表示积分多少 alpha0.7, cdf[goal_difference], # 用颜色表示净胜球 cmapRdYlGn) # 红-黄-绿色彩映射净胜球高为绿低为红 ax.set_xlabel(总进球数, fontsize12) ax.set_ylabel(总失球数, fontsize12) ax.set_title(08-09赛季英超球队攻防分布图 (点大小积分颜色净胜球), fontsize13, fontweightbold) # 添加球队标签避免重叠 for i, row in df.iterrows(): # 只为积分较高或特征明显的球队添加标签 if row[points] 50 or row[goals_for] 65 or row[goals_against] 65: ax.annotate(row[team], (row[goals_for], row[goals_against]), xytext(5, 5), textcoordsoffset points, fontsize9) # 添加参考线平均进球、平均失球 avg_goals_for df[goals_for].mean() avg_goals_against df[goals_against].mean() ax.axvline(avg_goals_for, colorgrey, linestyle--, alpha0.5, labelf平均进球 ({avg_goals_for:.1f})) ax.axhline(avg_goals_against, colorblack, linestyle--, alpha0.5, labelf平均失球 ({avg_goals_against:.1f})) ax.legend() plt.colorbar(scatter, label净胜球) plt.tight_layout() save_path os.path.join(output, goals_scatter.png) plt.savefig(save_path, dpi300) plt.close(fig) print(f攻防散点图已保存: {save_path}) def create_radar_chart_for_top_teams(df): 为前三名球队创建雷达图对比多项关键指标 # 选择要对比的指标 stats_columns [win_rate, goals_for_per_match, goals_against_per_match, points_per_match, points_efficiency] stats_labels [胜率, 场均进球, 场均失球, 场均积分, 积分效率] top_teams df.nlargest(3, points) team_names top_teams[team].tolist() # 准备数据每个指标需要归一化到 [0, 1] 区间以便在雷达图上比较 from math import pi plot_data [] for col in stats_columns: max_val top_teams[col].max() min_val top_teams[col].min() # 避免除零 if max_val min_val: normalized [0.5 for _ in range(len(top_teams))] else: normalized [(x - min_val) / (max_val - min_val) for x in top_teams[col]] plot_data.append(normalized) # 雷达图需要将第一个点重复一次以闭合图形 plot_data.append(plot_data[0]) stats_labels.append(stats_labels[0]) angles [n / float(len(stats_labels)-1) * 2 * pi for n in range(len(stats_labels))] angles angles[:1] # 闭合 fig, ax plt.subplots(figsize(9, 9), subplot_kwdict(projectionpolar)) colors [#1f77b4, #ff7f0e, #2ca02c] # 为三支球队指定颜色 for idx, team in enumerate(team_names): values [row[idx] for row in plot_data] ax.plot(angles, values, o-, linewidth2, labelteam, colorcolors[idx]) ax.fill(angles, values, alpha0.1, colorcolors[idx]) ax.set_xticks(angles[:-1]) ax.set_xticklabels(stats_labels[:-1], fontsize11) ax.set_ylim(0, 1) ax.set_title(08-09赛季英超前三名球队关键指标雷达图对比, size14, fontweightbold, pad20) ax.legend(locupper right, bbox_to_anchor(1.3, 1.0)) plt.tight_layout() save_path os.path.join(output, top_teams_radar.png) plt.savefig(save_path, dpi300, bbox_inchestight) plt.close(fig) print(f球队对比雷达图已保存: {save_path}) if __name__ __main__: # 确保输出目录存在 os.makedirs(output, exist_okTrue) # 加载分析后的数据 data_path os.path.join(data, processed, premier_league_0809_analyzed.csv) if not os.path.exists(data_path): print(f分析数据文件不存在请先运行 analyzer.py。) exit(1) df pd.read_csv(data_path, encodingutf-8-sig) print(f已加载数据准备生成可视化图表...) # 生成各类图表 create_league_table_chart(df) create_goals_scatter(df) create_radar_chart_for_top_teams(df) print(所有可视化图表已生成完毕请查看 ‘output‘ 文件夹。)可视化设计思路积分榜条形图采用横向条形图并让积分最高的球队排在最上方符合阅读习惯。用颜色深浅和直接标注数值来强化信息。攻防散点图这是分析球队风格的利器。X轴是进球Y轴是失球。右下区域进球多、失球少是攻守俱佳的冠军相球队。右上区域进球多、失球也多是激情四射但防守不稳的球队。左下区域进球少、失球少是防守稳健但进攻乏力的球队。左上区域进球少、失球多是表现不佳的球队。用点的大小表示积分颜色表示净胜球一张图里融合了四个维度的信息。雷达图用于多维度对比顶级球队的综合实力。我们将胜率、场均进球等不同量纲的指标归一化后放在同一个雷达图上可以清晰看出各队的优势与短板。例如冠军球队可能在所有维度都领先而亚军球队可能在“场均进球”上突出但“积分效率”稍弱。运行可视化脚本后打开output/文件夹你就能看到三张从不同角度讲述08-09赛季英超故事的图表。7. 常见问题与排查思路在实践这个项目时你可能会遇到一些典型问题。下面是一个快速排查指南。问题现象可能原因解决思路运行爬虫脚本时连接超时或拒绝1. 目标网站URL错误或失效。2. 网站有反爬机制如IP限制。3. 网络问题。1. 检查URL是否正确用浏览器手动访问确认。2. 添加更完整的请求头如Referer,Accept并显著增加请求间隔如time.sleep(5)。3. 检查本地网络尝试使用代理需合法合规。BeautifulSoup找不到表格 (table为None)1. 网页结构发生变化HTML标签或属性已更改。2. 表格数据是通过JavaScript动态加载的初始HTML中没有。1. 重新用开发者工具检查网页更新find函数中的选择器可能不是table或是class不同。2. 考虑使用Selenium或Playwright等工具来模拟浏览器渲染动态内容。pd.read_html解析出的数据是空的或错乱1. 表格结构复杂含有合并单元格等read_html无法完美解析。2. 表格可能嵌套在多个标签内。1. 放弃read_html改用BeautifulSoup手动遍历tr和td标签逐行提取数据。2. 将找到的table的HTML字符串保存到文件仔细检查其结构。数据分析时出现KeyError1. 代码中引用的列名在DataFrame中不存在。2. 列名在清洗前后不一致如大小写、空格。1. 使用df.columns打印所有列名确保引用正确。2. 在数据加载后和清洗后都打印列名确保清洗步骤没有意外修改或删除列。图表中文显示为方框系统中没有合适的中文字体或matplotlib未正确配置。1. 推荐在可视化代码中指定中文字体路径如plt.rcParams[‘font.sans-serif‘] [‘SimHei‘]。2. 或者避免在图表中使用中文改用英文标签。生成的图表图片是空白1. 在非交互式环境如脚本中未正确保存或显示图形。2. 图形被后续的绘图覆盖。1. 确保在plt.savefig()之后调用plt.close(fig)关闭当前图形对象。2. 检查保存路径的文件夹权限确保有写入权限。8. 最佳实践与项目扩展建议完成基础分析后你可以遵循以下最佳实践来完善项目并尝试更有挑战性的扩展。8.1 项目结构与代码优化模块化我们已经将爬虫、清洗、分析、可视化分离到不同文件这是良好的开端。可以进一步将通用函数如数据保存、日志记录提取到utils.py中。配置文件将URL、数据库连接字符串、API密钥等配置信息写入config.yaml或config.ini文件使代码更易维护和安全。日志记录使用Python的logging模块替代print语句可以更好地记录程序运行状态和错误信息方便调试。错误处理在关键步骤如网络请求、文件读写、数据库操作增加更细致的try-except块并提供有意义的错误提示和恢复逻辑。8.2 数据源扩展与深度分析多赛季对比爬取07-0809-10等相邻赛季的数据分析联赛格局的演变趋势。球员数据寻找球员级别的数据射手榜、助攻榜、传球成功率等分析金靴奖得主的特点或挖掘被低估的球员。比赛事件数据如果可能获取更细粒度的数据如每场比赛的射门位置、传球路线、控球时段。这可以用于计算“预期进球xG”等现代足球分析指标。集成第三方API使用像Football-Data.org、API-Football等提供的付费或免费API注意调用限制和条款获取更规范、稳定的数据。8.3 技术栈进阶数据库存储将SQLite升级为PostgreSQL或MySQL以处理更大规模的数据。使用SQLAlchemy的ORM可以平滑过渡。自动化与调度使用Apache Airflow或Prefect构建数据管道定期自动运行爬虫、清洗和分析任务。交互式可视化使用Plotly或Dash库创建交互式网页仪表盘让用户能够自主筛选赛季、球队动态查看图表。机器学习初探尝试使用scikit-learn构建简单的预测模型。例如利用球队上半赛季的数据预测其最终联赛排名。8.4 分析维度深化主场/客场表现将数据按主客场拆分分析“主场龙”和“客场虫”现象。赛季阶段分析将38轮联赛分为开局、中期、收官等阶段分析球队在不同阶段的抢分能力和状态稳定性。对阵关系网络使用NetworkX绘制球队之间的对阵关系图用边的粗细表示胜负关系或进球数可视化联赛内的“食物链”。通过这个项目你收获的不仅仅是对08-09赛季英超的数据解读更是一套完整的、可复用的数据分析工程能力。从数据获取到洞察呈现每一步都充满了挑战和乐趣。试着用这套方法去分析你喜爱的其他赛季、其他联赛或者完全不同的体育项目吧。数据的世界等你探索。
返回列表