公司动态

用Python拆解英雄联盟赛后数据:从BP分析到打野节奏复盘

📅 2026/8/30 7:26:52
用Python拆解英雄联盟赛后数据:从BP分析到打野节奏复盘
当一场 BO3 以 2:1 收尾打野选手在赛后采访里说出“本来以为会轻松拿下没想到微博的 BP 有备而来”时不同位置的观众会看到完全不同的信息有人看到选手心态有人看到阵容博弈有人看到野区节奏也有人只看到“主队输了心碎了”。这篇文章不打算替哪支战队喊话而是把赛后采访里的几个关键信息转成可量化的问题BP 到底哪里“有备而来”打野选手的前期节奏和视野参与度对比赛胜负影响有多大如果我们想用 Python 把一场英雄联盟比赛的赛后数据拆开来看从数据采集、字段清洗到可视化和复盘输出完整流程应该怎么做。本文适合喜欢电竞、又对数据分析感兴趣的开发者阅读。即使你不了解英雄联盟的版本细节只要有一点 Python 基础也可以照着代码跑通一套最简单的赛后分析流程。1. 背景与核心概念1.1 赛后采访背后的数据问题先还原标题里的信息NIP 2:1 WBG打野 guwon 接受赛后采访认为队伍原本预期能轻松取胜但 WBG 在 BP 阶段做了充分准备最终比赛打得并不轻松同时受访者也表达了对下一场与 iG 对决的期待。这段采访中最值得技术复盘的一句是“BP 有备而来”。BP 是什么在英雄联盟赛事中BP 是 Ban/Pick 的缩写也就是禁用英雄和选用英雄阶段。每一局比赛开始前双方都要先禁用掉若干个英雄再逐一选择自己的阵容。BP 阶段不仅决定双方阵容还会直接影响前期对线、中期野区规划、后期团战能力。“有备而来”听起来很主观但放在数据视角下它可以被拆成几个具体问题双方禁用了哪些英雄禁用名单里是否存在明显的针对关系蓝色方和红色方在选人顺序上做了哪些 Counter Pick双方打野英雄的对抗关系是否决定了前期的入侵和支援路线这些问题全部可以通过比赛后的公开数据做结构化分析。1.2 为什么用 Python 做赛后数据复盘英雄联盟比赛数据的原始形态通常是 JSON 或 CSV 格式里面是一堆选手、英雄、时间节点、经济、视野、击杀事件。直接用眼睛看会非常繁琐用 Python 处理则有明显优势。pandas可以把选手数据、团队数据转换成表格结构。matplotlib可以快速生成 BP 柱状图、选手数据对比图。requests可以对接公开数据源实现自动拉取。脚本本身可以复用这周分析 NIP 对阵 WBG下周分析 iG 二番战只需换掉数据文件逻辑不用重写。换句话说赛后采访只能给我们“观点”而 Python 能帮我们从数据里找到“证据”。1.3 从“心碎观赛”到“理性复盘”标题里的“收留 WBG 心碎女孩”是一句粉丝圈常见的玩笑话。但从技术文章的角度看这句话也提醒我们一个事实情绪化的观赛体验往往是短暂的而数据复盘是更长久的。把一场比赛交给脚本处理你可以得到几乎可复现的“比赛画像”。下次再遇到“为什么输”、“为什么赢”的争论时最好的回答不是互相争辩而是把 BP 数据、打野数据、团队 KDA 一起拉出来让数据说话。2. 环境准备与数据来源2.1 开发环境本文代码以 Python 3.10 及以上版本为例操作系统不限Windows、macOS、Linux 都可以运行。建议创建一个独立的虚拟环境避免依赖冲突。mkdir lol_post_match_analysis cd lol_post_match_analysis python -m venv venv source venv/bin/activate # Windows 下使用 venv\Scripts\activate再安装依赖库。pip install pandas matplotlib requests版本不需要刻意锁死pandas 2.x、matplotlib 3.x、requests 2.x 均可。本文示例代码中pandas主要用于表格分析matplotlib用于画图requests用于后续拉取真实数据时预留。2.2 公开数据来源目前英雄联盟赛事的公开数据来源主要有几类LeaguepediaFandom 平台提供 MediaWiki 风格的 API可查询比赛记录、选手名单、BP 和基础数据。Oracles Elixir 提供打包好的 CSV 下载字段丰富适合离线分析。部分非官方开源仓库会把职业比赛数据整理成 JSON。需要注意不同数据源的字段名和更新频率不一样。抓取前务必阅读对方网站的使用条款控制请求频率不要对公开服务器造成压力。涉及账号权限的接口必须在授权范围内使用。2.3 本文采用的数据方式为了让你能直接复现本文使用一个“模拟比赛数据”的 JSON 文件作为演示数据。它保留了真实比赛数据的常见结构但不是真实比赛数据。你可以在跑通流程后再把真实数据源的数据灌入同样的结构中。这种做法的好处是先把代码流程跑通再处理真实数据中的脏数据、缺失字段等问题学习压力更小。3. 赛后数据的核心字段设计3.1 比赛数据的分层结构一场比赛的数据通常可以分成多层。第一层是比赛元信息包括match_id比赛唯一标识。game_numberBO3 或 BO5 里的第几小局。game_version比赛使用的版本号。第二层是队伍数据包括队伍名称。禁用英雄列表。选用英雄列表。五个位置的选手数据。团队资源数据比如经济、防御塔、小龙、男爵。第三层是选手数据包括位置上单、打野、中单、下路、辅助。使用英雄。击杀、死亡、助攻。补刀数。经济。视野得分。在设计数据模型时尽量保持层次清晰不要把队伍数据和选手数据混在同一个扁平结构里。3.2 为什么打野数据要单独关注本文标题中的受访者是打野选手所以要单独关注打野位置。打野在英雄联盟里是一个特殊位置他不像线上选手那样固定对线而是游走在地图各个区域帮忙建立优势、争夺野区资源、控制地图视野。打野的核心指标包括参与击杀率打野对前期击杀事件的参与程度。经验与经济打野是否能跟上时间轴发育。视野得分打野对地图控制的影响。资源争夺小龙、男爵、虚空巢虫等中立资源的控制。在赛后采访中选手强调“BP 有备而来”如果从打野视角复盘还可以进一步看对方打野选出的英雄是否在入侵路线上克制我方打野双方的打野英雄在 15 分钟前的 Gank 路线和资源置换是否形成明显差异。3.3 字段命名建议在做数据分析时字段命名最好统一。建议使用英文小写加下划线避免中文字段名带来的编码和兼容问题。例如team_nameplayer_rolechampionkillsdeathsassiststotal_goldvision_scorecreep_score这样后续写 SQL、对接 Web 服务、做可视化时都能减少踩坑。4. 实战用 Python 拆解一场 BO3接下来我们进入完整实战。为了保证流程足够清晰我会先创建一个模拟数据文件再编写统计代码、BP 分析代码、打野分析代码和可视化代码。整个过程可以直接复制运行。4.1 创建项目结构建议按下面的目录组织项目lol_post_match_analysis/ ├── data/ │ └── match_demo.json ├── outputs/ │ └── ban_chart.png ├── main.pydata目录放比赛原始数据outputs目录放脚本生成的图表和报表main.py是主程序入口。如果后面接入真实数据可以再加入fetcher.py、parser.py等模块但第一阶段保持单文件足够。4.2 准备示例比赛数据在data/match_demo.json中写入以下内容。这份数据模拟了一场比赛的基本结构字段名称参考了常见比赛数据格式但数值属于演示用途。{ match_id: lpl_demo_bo3_game1, game_number: 1, blue_team: { team_name: NIP, bans: [永恩, 兰博, 布隆, 芮尔, 波比], picks: [格温, 千珏, 阿狸, 韦鲁斯, 蕾欧娜], players: [ {role: top, champion: 格温, kills: 4, deaths: 2, assists: 7, cs: 301, gold: 14600, vision: 28}, {role: jungle, champion: 千珏, kills: 5, deaths: 2, assists: 9, cs: 206, gold: 13500, vision: 42}, {role: mid, champion: 阿狸, kills: 7, deaths: 1, assists: 8, cs: 287, gold: 15800, vision: 30}, {role: bot, champion: 韦鲁斯, kills: 4, deaths: 3, assists: 6, cs: 310, gold: 14900, vision: 26}, {role: support, champion: 蕾欧娜, kills: 1, deaths: 4, assists: 14, cs: 32, gold: 8200, vision: 61} ] }, red_team: { team_name: WBG, bans: [贾克斯, 塞拉斯, 盲僧, 维鲁斯, 卡莉丝塔], picks: [乌迪尔, 雷克塞, 岩雀, 泽丽, 璐璐], players: [ {role: top, champion: 乌迪尔, kills: 3, deaths: 4, assists: 6, cs: 274, gold: 12800, vision: 31}, {role: jungle, champion: 雷克塞, kills: 4, deaths: 5, assists: 5, cs: 188, gold: 11600, vision: 38}, {role: mid, champion: 岩雀, kills: 5, deaths: 5, assists: 5, cs: 245, gold: 12100, vision: 29}, {role: bot, champion: 泽丽, kills: 6, deaths: 4, assists: 2, cs: 325, gold: 15200, vision: 24}, {role: support, champion: 璐璐, kills: 2, deaths: 6, assists: 11, cs: 30, gold: 7900, vision: 55} ] }, objectives: { blue: {towers: 9, dragons: 3, barons: 1, void_grubs: 4}, red: {towers: 5, dragons: 1, barons: 0, void_grubs: 2} } }这里每个位置都包含击杀、死亡、助攻、补刀、经济和视野得分。bans是禁用英雄列表picks是选用英雄列表。objectives保存团队资源情况方便后续做资源对比。4.3 编写赛后统计模块在项目根目录创建main.py。先把数据读取和基础统计写好。# 文件路径lol_post_match_analysis/main.py import json from collections import Counter import matplotlib.pyplot as plt def load_match(file_path: str) - dict: 读取 JSON 格式的比赛数据。 with open(file_path, r, encodingutf-8) as f: return json.load(f) def build_team_stats(match: dict, side: str) - dict: 计算某一方的团队汇总数据。 team match[f{side}_team] players team[players] total_kills sum(p[kills] for p in players) total_deaths sum(p[deaths] for p in players) total_assists sum(p[assists] for p in players) return { side: 蓝方 if side blue else 红方, team_name: team[team_name], kills: total_kills, deaths: total_deaths, assists: total_assists, kda: round((total_kills total_assists) / max(total_deaths, 1), 2), bans: team[bans], picks: team[picks], } def filter_role_player(match: dict, side: str, role: str) - dict | None: 按位置查找指定选手数据。 team match[f{side}_team] for player in team[players]: if player[role] role: return player return Nonebuild_team_stats遍历选手列表求出队伍总击杀、总死亡、总助攻并计算团队 KDA。这里用max(total_deaths, 1)是为了避免除零错误这是一种常见的防御性写法。filter_role_player的功能是按角色过滤选手数据比如我们只关心jungle后面会用它提取双方打野。4.4 BP 与打野专项分析接下来添加 BP 分析函数和打野报告函数。def show_bp_summary(match: dict) - None: 统计双方禁用英雄并输出 BP 汇总。 all_bans match[blue_team][bans] match[red_team][bans] counter Counter(all_bans) print( BP 禁用英雄汇总 ) for hero, cnt in counter.most_common(): print(f{hero}: {cnt} 次) def show_jungle_report(match: dict) - None: 单独分析双方打野的参与度和视野数据。 for side in (blue, red): team match[f{side}_team] jg filter_role_player(match, side, jungle) if jg is None: continue team_kills sum(p[kills] for p in team[players]) kp (jg[kills] jg[assists]) / max(team_kills, 1) * 100 jg_kda (jg[kills] jg[assists]) / max(jg[deaths], 1) print(f\n{team[team_name]} 打野 {jg[champion]}) print(f 参与击杀率{kp:.1f}%) print(f 个人 KDA{jg_kda:.2f}) print(f 视野得分{jg[vision]}补刀数{jg[cs]})BP 汇总用Counter统计所有禁用英雄的出现次数。如果某个英雄在一场比赛中被两边都禁了它的计数就是 2。这个数字可以直接反映“BP 针对”的强度。打野报告里kp表示参与击杀率公式是“打野击杀数与助攻数之和除以队伍总击杀数”。这个指标越高说明打野前中期参与比赛节奏的程度越高。4.5 可视化输出为了让分析结果更容易阅读我们再用matplotlib生成一张 BP 柱状图。这里要注意中文字体显示问题。def plot_bans(match: dict, output_path: str outputs/ban_chart.png) - None: 生成双方禁用英雄的柱状图。 all_bans match[blue_team][bans] match[red_team][bans] counter Counter(all_bans) names list(counter.keys()) counts list(counter.values()) plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, PingFang SC] plt.rcParams[axes.unicode_minus] False plt.figure(figsize(8, 5)) plt.bar(names, counts) plt.title(BP 阶段禁用英雄分布) plt.xlabel(英雄) plt.ylabel(被禁用次数) plt.tight_layout() plt.savefig(output_path, dpi150) plt.show()在 Windows 上SimHei和Microsoft YaHei通常可以正常显示中文在 macOS 上可以换成PingFang SCLinux 用户如果出现中文乱码需要先安装中文字体。axes.unicode_minus False是为了防止坐标轴上的负号显示成方块。4.6 主流程串联最后把上面这些函数串起来。def main() - None: match load_match(data/match_demo.json) print( 比赛团队数据 ) for side in (blue, red): stats build_team_stats(match, side) print(f{stats[side]} {stats[team_name]} 的团队数据) print(f总击杀: {stats[kills]}总死亡: {stats[deaths]}总助攻: {stats[assists]}团队 KDA: {stats[kda]}) print(f禁用英雄: {、.join(stats[bans])}) print(f选用英雄: {、.join(stats[picks])}) print() show_bp_summary(match) show_jungle_report(match) plot_bans(match) if __name__ __main__: main()执行下面的命令运行脚本。python main.py如果你的目录结构和 JSON 文件路径正确运行后会在控制台看到比赛数据和 BP 汇总在outputs目录中生成ban_chart.png图表。5. 运行结果与复盘解读5.1 控制台输出示例以示例数据运行后控制台输出大致如下。 比赛团队数据 蓝方 NIP 的团队数据 总击杀: 21总死亡: 12总助攻: 44团队 KDA: 5.42 禁用英雄: 永恩、兰博、布隆、芮尔、波比 选用英雄: 格温、千珏、阿狸、韦鲁斯、蕾欧娜 红方 WBG 的团队数据 总击杀: 20总死亡: 24总助攻: 29团队 KDA: 2.04 禁用英雄: 贾克斯、塞拉斯、盲僧、维鲁斯、卡莉丝塔 选用英雄: 乌迪尔、雷克塞、岩雀、泽丽、璐璐 BP 禁用英雄汇总 永恩: 1 次 兰博: 1 次 布隆: 1 次 芮尔: 1 次 波比: 1 次 贾克斯: 1 次 塞拉斯: 1 次 盲僧: 1 次 维鲁斯: 1 次 卡莉丝塔: 1 次 NIP 打野 千珏 参与击杀率66.7% 个人 KDA7.00 视野得分42补刀数206 WBG 打野 雷克塞 参与击杀率45.0% 个人 KDA1.80 视野得分38补刀数188这些输出说明什么只看团队 KDANIP 的整体击杀效率更高从打野参与率来看蓝色方打野的参与击杀率明显高于红色方打野说明前中期节奏更主动。视野得分也能看出蓝色方打野在视野布控上略有优势。5.2 怎么把分析结果带回赛后采访回到 guwon 的采访内容。“本来以为会轻松拿下”这句可以理解为队伍对自身实力有预期优势。“微博的 BP 有备而来”则需要在 BP 数据里寻找痕迹。比如对方在禁用阶段封锁了某些关键英雄或者选出了适合野区对抗、支援速度更快的阵容。如果你的数据里包含每个选手的详细时间轴事件还可以进一步统计双方打野第一次 Gank 的时间点。第一条小龙被谁控下。15 分钟时双方的经济差。关键团战发生前后野区视野控制比例。这些才是“BP 有备而来”背后的硬证据。赛后采访是信息来源数据分析则是验证手段。5.3 注意不要过度解读需要提醒的是本文示例数据只是模拟数据不能代表真实比赛。即使你接入了真实数据也需要结合阵容、版本、选手状态等多方面因素去分析。数据能提供相关性但不一定提供完整的因果链。比如打野参与击杀率高可能是因为阵容前期强势也可能是因为对方打野被中路视野压制原因需要结合比赛录像去确认。6. 常见问题与排查思路在跑通这套流程时你可能会遇到下面几类问题。问题现象常见原因解决思路Python 报json.decoder.JSONDecodeErrorJSON 文件格式有误缺少逗号或引号用编辑器检查 JSON 语法JSON 中不允许有注释尾逗号pandas读取中文列名报错编码不是 UTF-8打开文件时指定encodingutf-8plt.show()不显示图表非交互式环境或后端问题加plt.savefig保存图片或切换为plt.show(blockTrue)图里中文显示成方框缺少中文字体手动设置plt.rcParams[font.sans-serif]为系统已有中文字体访问公开 API 返回 403缺少 User-Agent 或请求频率过高请求头添加User-Agent并控制访问频率查看对方条款部分字段缺失导致KeyError数据结构不一致用dict.get()代替直接索引或先做字段校验真实数据字段名不一致不同数据源命名不同写一个字段映射层把原始字段名统一成本文所用字段下面给一个字段缺失时的防御示例。def safe_get(player: dict, key: str, default0): 防止字段缺失导致 KeyError。 return player.get(key, default)在真实数据分析中宁可返回默认值也不要让程序直接崩溃。但要注意默认值只能解决“程序能跑”不能解决“逻辑正确”。如果某个字段大量缺失你需要回到数据源确认是否解析错误还是比赛本身就没有该数据。7. 最佳实践与工程建议7.1 数据获取与缓存策略如果你要接真实比赛数据不建议每次分析都实时抓取。因为网络请求慢、接口压力大而且频繁请求容易被限制。更合理的做法是第一步把原始数据保存到本地 JSON 或 CSV。第二步在本地做解析和清洗。第三步将清洗后的结果写入 SQLite 或 PostgreSQL。这样做的好处是分析过程可复现。今天跑出的结果明天还能跑出同样的结果不会因为接口数据被覆盖而无法复盘。7.2 保持“数据源可追溯”做数据分析要有“溯源意识”。最好在最终报表中保留match_id、game_version、source字段。这样可以随时回答“这个数据是哪来的”、“是不是旧版本数据”等问题。7.3 代码分层虽然本文把代码放在一个main.py里方便学习但如果要长期维护建议拆成多个模块lol_post_match_analysis/ ├── src/ │ ├── fetcher.py # 抓取数据 │ ├── parser.py # 解析原始数据 │ ├── stats.py # 统计计算 │ └── visual.py # 可视化 ├── data/ # 原始数据缓存 ├── outputs/ # 报表和图表 └── tests/ # 单元测试清晰的模块划分能让你在比赛赛程密集时快速复用代码减少重复劳动。7.4 注意安全与规范不抓取需要登录授权才能访问的非公开数据。不绕过反爬机制不给对方服务器制造压力。在正式分析前先确认数据使用条款是否允许二次分发。这类分析更适合用于个人学习、内容创作、战队复盘而不是用来做投注预测或诱导不理性消费。如果文章或脚本涉及“预测胜负”一定注明“仅供娱乐和学习不具备决定性”。7.5 用表格沉淀结论分析完后把核心结论放到一张表里比写一大段话更有说服力。例如维度NIPWBG结论团队 KDA5.422.04蓝色方终结比赛能力更强打野击杀参与率66.7%45.0%蓝色方打野前期存在感更高视野得分4238蓝色方打野视野控制微弱领先禁用策略针对对方上中 AD针对对方打野 AD双方 BP 思路不同这类表格可以直接用于赛前分析文章、视频文案或团队内部复盘。8. 总结与学习路线这篇文章用一场“NIP 2:1 WBG”的赛后采访作为引子把“BP 有备而来”和“打野发挥”转成了可量化的数据分析问题并实现了一套最小可用的 Python 赛后分析流程。你现在应该已经掌握比赛数据的基本分层结构包括元数据、队伍数据、选手数据。如何用 Python 读取 JSON并计算团队 KDA、BP 统计、打野参与率。如何生成 BP 可视化柱状图并处理中文字体问题。如何从赛后采访里提炼数据问题再回到数据里找证据。下一步你可以继续做三件事。第一接真实数据源。把数据拉取模块加上替换掉模拟 JSON跑通一套“采集-清洗-分析-可视化”的完整流程。第二做多局对比。BO3 有三局每局 BP 不同节奏也不同。你可以把所有小局数据合并成一张总表再做交叉对比看看某一方在第几局开始调整策略。第三增加时间线指标。如果拿到包含关键事件时间线的数据可以画出“15 分钟经济差变化曲线”、“小龙控制顺序”等更专业的图表对战队复盘更有参考价值。等到 NIP 和 iG 的二番战开打你甚至可以提前用这套流程在赛前整理双方最近的 BP 习惯、打野路线和视野数据再在赛后对照分析看看赛前研究是否应验。技术复盘的魅力就在于此一次脚本写好后比赛会一场接一场地来而你的分析体系会越来越完整。如果这篇文章对你有帮助可以收藏备用下次比赛日直接打开用。