公司动态
用Python分析LPL赛后评分:从数据采集到可视化全流程
最近 LPL 赛场上 NIP 2-1 战胜 WBG赛后各平台的讨论区涌入大量观众评分。对于只看比赛的人来说评分只是一个数字但对于做赛后内容、电竞数据运营或者想练习 Python 数据分析的开发者来说这些评分本身就是一批值得处理的样本数据。然而大多数人在面对这样的数据时第一反应是手动翻页面、截图、复制到 Excel。缺点很明显耗时、容易漏、不方便对比选手之间的差异更没办法快速画出“各位置评分对比图”。这篇文章想给的是一套更工程化的解法用 Python 写一个小型数据分析项目把“看评分”变成“分析评分”。文章会以 NIP 对阵 WBG 的比赛为场景但不会讨论比赛细节也不会给出任何主观的选手评价。核心是讲清楚从数据采集、数据清洗、统计汇总到可视化的完整流程。你会得到一份可以复用的代码以后任何一场比赛只要有同样结构的评分数据跑一遍脚本就能生成对比图和数据表。这里必须先提醒一个边界真实网站的页面结构和数据字段会变化直接抓取也可能违反平台规则。本文为了完整演示流程会使用模拟的评分数据并给出替换成真实数据时的思路。你会学到的是通用方法而不是针对某一个网站的攻击手段。1. 这篇文章真正要解决的问题赛后评分数据分散在页面中人工处理低效。以 NIP 2-1 WBG 这场比赛为例如果只需要看几个关键选手手动翻页也许可以接受但如果想看所有上场选手的评分分布、队伍平均分、位置维度对比手动操作就很难做到。更别说后续还要生成图表、写战报、做多个场次的横向比较。这个问题本质上是典型的“非结构化页面转结构化数据”问题。评分区里的每条评分都包含多个字段比如选手ID、所属队伍、位置、评分值、参与评分的用户数量甚至还有一些标签或评论。页面展示是给人看的但不是给表格用的直接复制粘贴得到的数据往往带有换行、空格、多余文字无法直接进入分析流程。因此这篇文章要解决的不是“NIP 为什么能赢”这种竞技分析而是“如何用技术手段把赛后评分变成一份可复用、可量化、可可视化的数据报告”。读完这篇文章你应该能回答下面几个问题一个最小的数据分析项目需要哪些模块模拟数据怎么构造真实数据怎么替换数据清洗一般要处理哪些脏数据如何用 Python 完成统计和可视化我认为这件事值得做的原因有三个。第一门槛低只需要 Python 基础、pandas 和 matplotlib 的基本使用适合数据分析入门。第二场景真实电竞比赛评分是很多人熟悉的题材学习起来不会枯燥。第三方法论通用同样的流程可以迁移到商品评价、影评、直播弹幕等文本和评分混合的数据上。对于正在找数据分析练习项目的同学来说这比打印出来的“波士顿房价”更有代入感。这篇内容的读者定位也比较清晰一类是对数据分析感兴趣、但缺项目练手的开发者另一类是需要快速产出赛后可视化内容的编辑或运营同学。前者可以重点看代码结构和清洗逻辑后者可以直接复制脚本调整输出格式。无论你是哪一类都不需要额外安装复杂的数据库只要电脑上有 Python就能跑通全流程。2. 虎扑评分的数据特征与项目边界2.1 评分数据有哪些常见维度虎扑评分面向赛事和选手通常会把每个上场选手作为独立条目展示。从数据分析角度看常见维度包括几个部分。选手标识是选手ID或昵称这是数据关联的核心字段后续所有分组统计都要依赖它。队伍维度表示该选手属于哪一方例如 NIP 或 WBG这是比较两队评分的基础。场上位置一般分为上路、打野、中路、ADC、辅助是做位置分析的关键维度。接下来是评分值观众给出的分数通常是 1 分到 10 分之间的整数或小数但不同版本的页面也可能采用不同区间所以不能预先写死。参与人数反映了评分的样本量同样是 7.5 分一万个人评出来的 7.5 分和一百个人评出来的 7.5 分可信度完全不一样。最后还有附加内容比如热门评论、标签这些属于非结构化文本可以做情感分析但不适合直接进入统计表。这里我把字段写得比较通用没有绑定某个具体页面的准确实现。原因很简单真实页面的字段名、数据类型、评分区间随时可能调整。做项目时第一件事不是写代码而是打开页面确认数据结构。很多初学者跳过了这一步结果代码写了一百行才发现字段名完全对不上。2.2 为什么不能直接“爬”就完事很多初学者拿到一个评分页面后第一反应是写一段 requests 请求然后用正则表达式或 BeautifulSoup 去解析 HTML。这种方式对静态页面偶尔有效但存在三个问题。其一页面结构一变解析逻辑就失效其二很多数据是通过异步接口加载的只抓 HTML 拿不到完整内容其三频繁请求会给对方服务器造成压力也有合规风险。所以更稳妥的方式是“数据层与解析层分离”。先确认数据是否存在接口或者是否存在可导出的数据包如果没有合法接口再退回到只保存页面快照并做低频解析。本文的示例以模拟数据为核心不依赖任何真实页面目的就是让你先跑通流程再按合法方式接入真实数据。另外评分数据一定存在偏差例如刷分、粉丝滤镜、极端评分。数据分析只能展示现状不代表客观实力。在后续报告里如果用到真实数据也需要加样本量提示避免误读。许多人拿到数据后第一反应是“分高就是打得好”而忽略了评分人数和评论背景这是数据分析里最容易犯的错误。2.3 技术选型项目使用 Python 3.9依赖 pandas 做数据清洗和统计matplotlib 做可视化。选择这两个库是因为它们生态成熟、文档多、团队协作成本低。如果未来要部署成 Web 服务还可以把结果导出成 CSV 或 JSON交给前端展示。相比直接写一堆 for 循环pandas 的 groupby 和聚合函数可以大幅简化代码。为什么不选更重的方案因为项目范围很小。赛后评分数据量通常只有几十条到几千条完全不需要分布式计算、消息队列或数据库。用脚本的方式处理既符合最小可用原则也容易让初学者理解全链路。当你需要处理更多数据时再把数据源替换成数据库把脚本改造成定时任务演进路径非常清晰。3. 环境准备与项目初始化3.1 运行环境在开始之前建议准备一个独立的 Python 虚拟环境。虚拟环境可以避免不同项目的依赖互相冲突尤其是在机器上同时有多个 Python 项目时。操作系统方面Windows、macOS、Linux 都可以命令略有区别本文以 Linux/macOS 的 bash 为例Windows 用户把 source 换成 activate 即可。版本方面Python 3.9 以上即可pandas 和 matplotlib 的版本以当前最新稳定版为准本文不锁定具体小版本。如果你的机器上已经安装 Anaconda也可以直接用 conda 创建环境后面的代码不变。重点是环境干净不要出现 pandas 版本过低导致函数接口不一致的问题。3.2 创建虚拟环境并安装依赖# 创建虚拟环境 python -m venv venv # 激活虚拟环境macOS / Linux source venv/bin/activate # Windows 激活命令 # venv\Scripts\activate # 安装依赖 pip install pandas matplotlib这段命令执行后当前终端会进入独立的 Python 环境。注意Windows 下激活命令没有 source直接执行 venv\Scripts\activate 即可。安装依赖时如果网络较慢可以改用国内镜像源例如 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pandas matplotlib。这时可以用 python -c import pandas 验证是否安装成功。如果没有任何报错说明基础环境已经就绪。如果报 ModuleNotFoundError说明当前终端还在全局环境需要回到项目目录重新激活虚拟环境。3.3 工程目录结构为了让代码更清晰建议把项目拆成几个小文件每个文件只负责一个环节。下面是一个最简单的目录结构。lol_rating_analysis/ ├── data_generator.py # 模拟数据生成 ├── analysis.py # 数据清洗与统计 ├── visualize.py # 可视化 ├── main.py # 主流程 ├── data/ # 生成的 CSV 文件目录 └── output/ # 图片输出目录这个结构不复杂但它强调了关注点分离。以后如果要把真实爬虫接入只需要新增一个 fetcher.py返回与 data_generator.py 相同格式的 DataFrame后面的分析和可视化代码完全不用改。这也是初学者容易忽略的一点写代码时想着“以后可能会扩展”比真到扩展时重构要省力得多。4. 核心流程拆解整个项目可以拆成四个环节数据采集、数据标准化、统计分析、可视化输出。下面逐一拆解每个环节的目标和容易出错的地方。4.1 数据采集层数据采集层解决“数据从哪来”的问题。在本文的演示版本中这一层用 data_generator.py 生成一批结构完整的模拟评分数据。生成时指定两个队伍的选手列表、位置和评分范围并故意加入一些脏数据例如空值、重复行、格式不一致的字符串方便后续清洗演示。如果未来接入真实数据这一层就应该换成一个函数从合法的数据接口读取 JSON 或 CSV然后转换成 DataFrame。关键在于接口返回的字段名和数据生成器保持一致这样后面所有代码都不需要改动。这里需要特别强调不要跳过“数据结构确认”这一步。很多实际项目踩坑都是因为想当然地认为接口字段是某个名字结果返回的是另一个名字。4.2 数据标准化层数据标准化层处理原始数据里的脏数据。常见任务包括去除重复记录处理缺失值统一选手ID的大小写空格把评分字段从字符串转成浮点数给每条记录增加队伍和位置的标准映射。这个环节看起来琐碎却是数据分析中最重要的一步。如果跳过标准化统计结果可能被几行脏数据带偏。比如某个选手ID拼写不同分组时就会被拆成两个人评分字段里有中文符号转换成浮点数就会失败。因此主流程中数据清洗必须放在统计之前。有时候清洗类代码看起来很简单但正是这些“不起眼”的步骤决定了最终结论是否可信。4.3 统计与可视化层统计层负责计算基础指标包括每个选手的评分、每个队伍的平均分、每个位置的平均分。更细一点还可以统计评分人数作为样本量参考。可视化层则负责用图表把这些指标展示出来例如按队伍分组、按位置着色的横向条形图以及选手评分分布图。这一层的关键在于“维度对得上”。统计时按什么分组图表时也要按同样的字段分组。如果队伍名称在清洗后还有前后空格图表里会出现两个看起来一样的队伍柱状条这是最常见的问题。另一个容易忽略的是排序。建议在画图前先排序否则柱状图会显得很乱读者也很难一眼看出谁高谁低。4.4 报告输出层最后把统计结果保存成 CSV把图片保存成 PNG并在控制台打印关键指标。这样一个最小可用流程就算完成。后续如果需要做周报可以让脚本自动把多张图片拼接成一张长图或者把 CSV 导入 Excel 做二次加工。从工程角度看报告输出层还有一个作用让数据“可追溯”。当你把 CSV 和图片都存到本地后即使脚本之后调整了清洗逻辑也可以重新跑一份和旧版本对比。这对排查隐藏的数据问题非常有用。5. 完整示例代码实现下面开始写代码。先说明一点这里的数据全部是模拟数据不代表真实评分。即使随机数能跑出合理的结果也不要去解读成“某选手真实评分”。5.1 生成模拟评分数据创建 data_generator.py# 文件路径data_generator.py import random import pandas as pd def generate_mock_data(seed42): 生成模拟的赛后评分数据用于演示完整分析流程。 random.seed(seed) # 两个队伍的选手列表格式队伍, 位置, 选手ID players [ (NIP, 上路, NIP.Top), (NIP, 打野, NIP.Jug), (NIP, 中路, NIP.Mid), (NIP, ADC, NIP.ADC), (NIP, 辅助, NIP.Sup), (WBG, 上路, WBG.Top), (WBG, 打野, WBG.Jug), (WBG, 中路, WBG.Mid), (WBG, ADC, WBG.ADC), (WBG, 辅助, WBG.Sup), ] rows [] for team, pos, player in players: score round(random.uniform(4.0, 9.5), 1) count random.randint(1000, 10000) rows.append([team, pos, player, score, count]) # 故意加入一条脏数据重复行和空评分方便后续清洗演示 rows.append([NIP, ADC, NIP.ADC, 9.9, 8000]) rows.append([WBG, 中路, WBG.Mid , None, 5000]) df pd.DataFrame(rows, columns[team, position, player, score, score_count]) return df if __name__ __main__: df generate_mock_data() print(df)这段代码生成了 10 个上场选手的模拟评分并额外加了一条重复行和一条带空值的记录。score 的取值被限制在 4.0 到 9.5score_count 在 1000 到 10000 之间。这样的设计是为了让后续的清洗和统计有得做也更能反映真实数据里常见的“同一选手出现两次”和“字段缺失”的情况。这里解释一下为什么要用 random.seed(seed)。数据分析项目讲究可复现性固定随机种子后无论在哪台机器上运行生成的数据都完全一致。这样你的结果可以被同事复现排错时也不用担心数据每次都不一样。如果你去掉 seed每次运行生成的评分都会不同虽然也能演示但很难定位是代码问题还是数据变化导致的问题。5.2 数据清洗与统计创建 analysis.py# 文件路径analysis.py import pandas as pd def clean_data(df: pd.DataFrame) - pd.DataFrame: 对评分数据做基础清洗。 df df.copy() # 去掉完全重复的行 df df.drop_duplicates() # 去掉评分缺失的行 df df.dropna(subset[score]) # 去掉选手ID首尾空格 df[player] df[player].str.strip() # 队伍名统一去掉空格 df[team] df[team].str.strip() # 评分转成浮点数异常值会被转为 NaN再次删除 df[score] pd.to_numeric(df[score], errorscoerce) df df.dropna(subset[score]) return df def summarize(df: pd.DataFrame): 统计每名选手、每支队伍、每个位置的基础指标。 player_summary df.groupby([team, player], as_indexFalse).agg( avg_score(score, mean), score_count(score_count, sum) ) team_summary df.groupby(team, as_indexFalse).agg( avg_score(score, mean), total_count(score_count, sum) ) position_summary df.groupby(position, as_indexFalse).agg( avg_score(score, mean) ) return player_summary, team_summary, position_summary清洗函数做了三件关键事删除重复记录、删除缺失评分的记录、清洗ID和队伍名的空格。最后用 pd.to_numeric 转评分类型并处理无法转换的值。这个函数可以应对绝大部分基础脏数据但不处理业务规则类问题比如评分超出合理区间。如果你遇到满分10分但出现100分的情况还要再加一层范围过滤。如果只看表面很多人会以为 drop_duplicates 只是去掉“完全一样”的行。但在实际页面里重复数据往往不会一行不差选手ID前后的空格就能让一模一样的数据被当成两条。所以先 strip 再 drop_duplicates才会更可靠。同样的道理也适用于队伍名如果一个叫“WBG”另一个叫“WBG ”分组后就会变成两支队伍图表里也会出现重复阴影。5.3 数据可视化创建 visualize.py# 文件路径visualize.py import matplotlib.pyplot as plt import pandas as pd def plot_player_scores(player_summary: pd.DataFrame, output_path: str): 绘制选手平均评分横向条形图。 plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, PingFang SC] plt.rcParams[axes.unicode_minus] False df player_summary.sort_values(avg_score) colors [#d62728 if team NIP else #1f77b4 for team in df[team]] plt.figure(figsize(10, 6)) plt.barh(df[player], df[avg_score], colorcolors) plt.xlabel(平均评分) plt.title(NIP vs WBG 选手评分对比) for idx, value in enumerate(df[avg_score]): plt.text(value 0.02, idx, f{value:.1f}, vacenter) plt.tight_layout() plt.savefig(output_path, dpi150) plt.close() print(f图片已保存到{output_path})matplotlib 默认字体不包含中文字形所以第一行设置了常见中文字体。请根据你的系统环境调整字体名称Windows 可以用 SimHei 或 Microsoft YaHeimacOS 可以用 PingFang SC。如果绘制的图里出现方块说明字体设置没有生效。这里的 colors 列表根据队伍把 NIP 标成红色、WBG 标成蓝色便于快速区分。用横向条形图而不是纵向柱状图原因是选手ID一般较长横向排列可以避免文字重叠。如果你更习惯纵向柱状图也可以把 plt.barh 换成 plt.bar但要注意 x 轴文字旋转角度。图表里同时显示数值是为了让读者不用盯着坐标轴估计长度直接看标签就能知道分数。5.4 主流程串联创建 main.py# 文件路径main.py from data_generator import generate_mock_data from analysis import clean_data, summarize from visualize import plot_player_scores def main(): raw_df generate_mock_data() print(原始数据条数, len(raw_df)) clean_df clean_data(raw_df) print(清洗后数据条数, len(clean_df)) player_summary, team_summary, position_summary summarize(clean_df) print(\n队伍平均评分) print(team_summary) print(\n位置平均评分) print(position_summary) clean_df.to_csv(data/clean_scores.csv, indexFalse) team_summary.to_csv(data/team_summary.csv, indexFalse) plot_player_scores(player_summary, output/player_scores.png) if __name__ __main__: main()主流程把前面三个模块串起来生成数据、清洗、统计、导出 CSV、画图。如果你用的是模拟数据运行后会在 data 目录生成两个 CSV 文件在 output 目录生成一张选手评分对比图。整个过程不超过几秒钟。从工程上看这个主流程的优点是“替换成本低”。如果以后想接入真实数据只需要把 generate_mock_data 替换成真实数据读取函数只要返回的 DataFrame 列名一致clean_data 和后面的所有代码都不需要改。这也是我在设计代码时刻意保持接口一致的原因小项目也要留出扩展空间。6. 运行结果与效果验证6.1 运行方式在项目目录下执行mkdir -p data output python main.py如果你在 Windows PowerShell 中执行mkdir -p 不支持可以先用 New-Item -ItemType Directory -Force data, output 创建目录或者直接使用 PyCharm 等 IDE 的目录创建功能。这里使用 mkdir -p 只是为了在 Linux/macOS 下更省事。目录创建好后再运行 python main.py。6.2 预期输出由于使用的是模拟数据每次运行只要 seed 固定输出也会固定。下面展示一个可能的输出结构数值具体是多少不重要。原始数据条数 12 清洗后数据条数 10 队伍平均评分 team avg_score total_count 0 NIP 7.123456 12345 1 WBG 6.987654 23456 位置平均评分 position avg_score 0 中路 7.500 1 打野 7.100 ... 图片已保存到output/player_scores.png这段输出是我根据代码逻辑构造的演示效果不代表真实评分。实际运行时的数字取决于随机数据和清洗结果。验证是否成功的标准有三个控制台显示清洗后条数比原始条数少data 目录下出现 clean_scores.csv 和 team_summary.csvoutput 目录下出现 player_scores.png。如果你看到“原始数据条数 12”说明模拟数据生成正确如果清洗后仍然是 12说明重复行和缺失值可能没有被正确去掉。这时优先检查 clean_data 里的 drop_duplicates 和 dropna 是否执行。建议在 clean_data 函数里临时加 print 语句逐步查看每一步的行数变化这是最直接的调试方法。6.3 结果验证标准拿到 CSV 后建议打开看一下。clean_scores.csv 里每行应该是一个有效选手score 列全部是数字team_summary.csv 里每个队伍只有一行avg_score 是数值。图片上应该能看到 NIP 和 WBG 两种颜色的横向柱状条柱状条长度对应平均评分。若图片中文变成方块按第 5.3 节的字体设置调整即可。如果你用的是 Excel 打开 CSV遇到中文乱码很可能是编码问题。pandas 默认导出 UTF-8 编码Excel 打开时可能识别不了。解决方式有两种一是在 to_csv 时指定 encodingutf-8-sig二是在 Excel 里手动导入并选择 UTF-8 编码。对于写代码的读者更推荐第一种因为它是稳定的程序化方案。6.4 真实数据替换思路真实项目的接入流程可以这样理解先找到合法的数据源确认字段然后用 requests 或其它 HTTP 客户端获取 JSON最后把 JSON 解析成 DataFrame。解析时注意字段名可能叫 score 也可能叫 rating队伍名可能有简称和全称这些都需要在标准化层处理。替换后整个统计和可视化链路不变。不过这里不展开真实请求代码因为网站接口和规则变化频繁。建议读者先跑通模拟数据再用开发者工具观察页面结构。如果页面数据不是静态 HTML而是异步加载就需要找到真正的数据接口必要时还要考虑身份校验、限流等复杂因素。请务必在平台允许的范围内操作。7. 常见问题与排查思路问题现象可能原因排查方式解决方案图表中文显示为方块matplotlib 默认字体不支持中文查看控制台是否提示字体缺失在代码中设置中文字体如 SimHei、Microsoft YaHei清洗后数据条数没有减少重复行并不完全一致或缺失值集中在未检查的列打印原始数据检查 player 字段前后空格先 strip 再 drop_duplicates检查 None 和空字符串score 转换时报错字段里有中文符号或单位打印该字段的 Unique 值使用 pd.to_numeric(errorscoerce) 并删除 NaN图片保存失败output 目录不存在查看报错信息中的路径先创建输出目录模拟数据无法反映真实情况只是演示数据检查生成逻辑中的随机范围接入真实数据源后重新清洗和统计这张表覆盖了本项目最常见的几个问题。真正调试时第一步永远不是改代码而是看报错信息。Python 的数据分析项目错误通常会直接指出是哪一行再结合这一行的输入数据去判断效率会高很多。如果你使用的是 Jupyter Notebook还可以把每个中间步骤都变成单元格单独运行这样更容易发现哪一步出了问题。此外还有一个很容易踩坑的地方如果你在 Jupyter Notebook 里运行plt.close() 会关闭当前画布导致后续单元格看不到图。建议在 Notebook 环境中先用 plt.show() 查看确认效果后再保存图片在脚本化项目里则保留 plt.close()避免内存堆积。很多初学者在 Notebook 里画不出图以为是代码问题实际上就是画布已经被关闭了。8. 最佳实践与工程建议8.1 数据规范先行任何数据分析项目都应该先定义好字段规范再写逻辑。比如队伍统一用 NIP 和 WBG选手ID统一不带空格评分区间统一为 0-10。定义清楚后清洗代码才不会越写越乱。建议把字段规范写在一个 config.py 里后续校验逻辑直接引用。这样做的好处是当项目从模拟数据切换到真实数据时你不需要在清洗函数里到处找硬编码的字段名。只要 config 里的规则不变代码就可以复用。对于入门项目来说这可能看起来有点过度设计但当你想把脚本扩展成一个定时任务时会发现这种“小设计”非常值得。8.2 异常处理和日志如果以后接入真实请求必须把“网络异常”和“数据格式异常”分开处理。网络异常可以重试数据格式异常说明页面结构变了需要人工介入。可以把结果打印成结构化日志例如记录生成时间、数据条数、清洗比例。这样即使脚本定时跑出问题时也能快速定位。简单做法是用 Python 自带的 logging 模块而不是大量 print。print 适合刚开始调试logging 更适合长期运行。你可以把统计结果写入日志文件保留每次运行的历史。相比只看控制台输出日志文件能让你知道上一次跑脚本时发生了什么对追踪问题非常有帮助。8.3 合规和安全边界这一块很重要。不要对任何网站做高频抓取不要尝试绕过身份校验或访问限制不要用抓取来的数据做二次售卖。本文提供的模拟数据方法正是为了避免你在没有合法授权的情况下直接抓取。如果你确实需要真实数据请先阅读目标平台的用户协议确认数据使用边界必要时咨询运营方。对于权限和访问控制遵循最小权限原则只需要公开的页面数据就不去碰需要登录的接口只需要统计结果就不要保存用户的隐私字段。比赛选手评分是公开讨论内容但仍然需要尊重平台规则。技术能力越强越要懂得数据使用的边界。8.4 扩展建议这个项目可以做很多扩展。一是加入多个比赛场次的纵向比较分析队伍评分趋势。二是加入评论情感分析把评分和文本评论结合起来。三是把 CSV 结果接入 BI 工具生成自动更新的数据看板。四是对评分分布做直方图观察是否存在刷分或两极分化。每一条都能成为独立的学习项目。虽然这些扩展需要更多技术栈但核心流程不会变采集、清洗、统计、可视化。你在本项目里积累的代码结构可以直接复用。比如从单场评分变成多场评分只需要给 DataFrame 增加一个“比赛ID”字段groupby 的维度就会自然多一层不需要重写统计逻辑。9. 总结与后续学习方向这篇文章从一个“NIP 2-1 WBG”赛后评分场景出发梳理了一个小型数据分析项目的完整流程。核心不在于具体某场比赛谁高谁低而在于你具备了一套处理评分数据的工程方法先定义字段生成或获取数据清洗脏数据统计分组指标再输出可视化和表格。这套方法可以迁移到比赛评分、商品评价、影视评分等多个领域。接下来如果你想继续深入可以按三条线走。第一条线是数据采集学习 HTTP 请求、JSON 解析、反爬应对的合法边界了解如何把模拟数据源替换成真实数据源。第二条线是数据分析学习更丰富的 pandas 操作比如透视表、多表关联、时间序列分析。第三条线是可视化学习用 pyecharts、Plotly 或 ECharts 生成交互式图表让赛后数据报告更好看。最后提醒一句项目里的模拟数据不代表任何选手的真实评分也不要拿着结果去评价选手。技术工具的意义是帮助我们更高效地观察信息而不是替代判断。建议收藏这份代码下次比赛结束后替换成合法、可靠的数据源几分钟就能生成自己的赛后数据报告。