☰
用Python与机器学习分析LPL赛事,构建AL对阵LGD的胜率预测模型
2026/10/19 12:21:48 网站建设 项目流程

这次我们来看一个和 LPL 赛事相关,但本质是 Python 数据分析工程的东西:用公开的赛前数据,把 AL 对阵 LGD 这场比赛拆成可计算的特征,再用机器学习模型输出一个胜率参考值。重点不是直接告诉你“谁赢”——那样既不严谨也没意义,而是把“队伍状态好不好、节奏掌控强不强、辅助选手能带来多少增益”这类模糊判断,变成具体的数据指标。

这套分析工程有几个特点:不需要 NVIDIA 显卡,普通 CPU 就能跑;技术栈全是标准 Python 库;支持一次分析多场比赛;可以封装成 FastAPI 接口,后续接入复盘工具或者生成数据报告都很方便。

这篇文章会带你把完整流程过一遍:环境准备、数据清洗、特征工程、模型训练、AL vs LGD 这场比赛的赛前特征构造、批量预测、接口封装,以及常见问题排查。适合两类读者:一是想用 Python 做数据分析练手的开发者,二是平时看 LPL 但不满足于“谁状态好全靠感觉”的电竞观众。

先强调一点:这是一个技术学习项目,用于数据分析工程演示,不构成任何赛事结果判断,更不是投注建议。赛事数据要合法获取,分析结果也不能当作确定性结论。

1. 核心能力速览

能力项说明
项目类型LPL 赛事数据分析与赛果参考预测(Python 脚本项目)
数据来源官方赛后统计数据、公开可访问的数据平台;需要自行准备合法授权数据
主要功能队伍近期状态评分、辅助选手贡献特征、经济差/视野/资源控制统计、二分类赛果概率预测
运行环境Windows、macOS、Linux,Python 3.9 及以上
硬件门槛普通办公电脑即可,CPU 运行,无显存需求
启动方式命令行执行 Python 脚本,或 Jupyter Notebook 交互运行
接口能力可封装为 FastAPI/Flask 接口,支持 HTTP 调用
批量任务支持一次分析多场比赛,输出概率结果表
适合场景数据分析学习、赛事复盘、赛果概率参考

为什么这个项目不需要 GPU?因为输入特征是几十维的表格数据,不是图像、不是长序列,逻辑回归和 XGBoost 这类模型在 CPU 上就能完成训练和推理。真正影响预测效果的往往不是模型复杂度,而是特征构造和数据时效性。

2. 适用场景与使用边界

这套流程适合以下场景:

  • 想做数据分析项目的学生:数据量不大,但能完整覆盖“取数、清洗、特征、建模、评估、部署”全链路。
  • LPL 爱好者做赛后复盘:把队伍近期表现、资源控制率、选手发挥等维度量化,比纯文字评价更直观。
  • 需要批量生成赛事分析报表的团队:脚本化之后,每天新增比赛数据后可以自动化更新特征和概率输出。

不适合的场景也要说清楚:

  • 不适合作为确定性预测工具,电竞比赛受版本、BP、临场状态、心理因素影响非常大,模型只能给参考概率。
  • 不适合拿来做任何类型的投注决策参考,这是合规红线,本文所有示例不涉及投注推荐。
  • 不适合把模型结果直接对外发布,除非你对数据口径、特征时效性和版权都做过确认。

合规方面有三条底线:第一,比赛数据和选手信息必须通过官方或获得授权的方式获取;第二,涉及选手姓名、战队标识、比赛图像时,注意版权归属,个人学习可以,商用发布需另行确认;第三,如果要把项目部署成公开服务,接口要加访问控制,不能裸奔到公网。

3. 环境准备与前置条件

先确认 Python 环境。命令行执行:

python --version

如果版本低于 3.9,建议直接安装 Anaconda,同时解决 Python 和常用库的版本管理问题。接着安装依赖:

pip install pandas scikit-learn xgboost requests fastapi uvicorn matplotlib joblib

如果 xgboost 在你的系统上安装失败,可以先跳过,使用 scikit-learn 的 GradientBoostingClassifier 代替,逻辑完全一致,只是训练速度稍慢。

数据部分是重点。你需要准备至少三类数据:

  • 比赛主表:match_id、比赛日期、对阵双方、胜负。
  • 队伍统计表:按场次记录的击杀、死亡、助攻、经济、视野、大小龙、一血等数据。
  • 选手统计表:按选手维度记录的 KDA、视野分、参团率、助攻数等数据。

建议把文件按目录组织:

lpl-analysis/ ├── data/ │ ├── raw/ # 原始 CSV/JSON,不修改 │ ├── processed/ # 清洗后数据 │ └── features/ # 构造后的特征表 ├── outputs/ # 预测结果和图表 ├── models/ # 保存的模型文件 └── scripts/ ├── build_features.py └── train_model.py

磁盘空间需求很小,几百 MB 足够。如果你的数据源是接口,第一次抓取后最好存成 CSV 缓存,避免反复请求触发限流。

4. 数据获取与清洗

Riot 官方有开发者 API 可以获取比赛原始数据,具体接口路径和字段名以官方文档为准,这里不写死。更省事的做法是直接下载 CSV 版本的历史比赛数据,很多公开数据平台提供赛季汇总表。

先演示读取 CSV 和多表合并,注意字段名要根据你的真实数据调整:

import pandas as pd # 请根据真实数据路径修改 matches = pd.read_csv("./data/raw/matches.csv", encoding="utf-8") team_stats = pd.read_csv("./data/raw/team_stats.csv", encoding="utf-8") player_stats = pd.read_csv("./data/raw/player_stats.csv", encoding="utf-8") # 字段为示意,需要按实际数据调整 df = matches.merge(team_stats, on="match_id", suffixes=("_match", "_team")) df = df.merge(player_stats, on="match_id") # 比赛时长从秒转为分钟 df["game_duration_min"] = df["game_duration_sec"] / 60 # 基础清洗 df = df.dropna(subset=["win"]) df["win"] = df["win"].astype(int)

拿到数据后第一件事不是跑模型,而是检查数据质量:

print(df.shape) print(df["win"].value_counts()) print(df.isna().sum().sort_values(ascending=False).head(20)) print(df["game_date"].min(), df["game_date"].max())

常见问题有两个。一是合并后行数翻倍,说明关联键 match_id 在某一侧不唯一,需要先对重复维度做聚合,比如选手统计表里一名选手可能有多个位置记录,要先按 match_id 和 team 聚合。二是中文列名或选手名乱码,读取时统一指定 encoding,不要混用 UTF-8 和 GBK。

清洗原则就一条:保留原始数据在 raw 目录,processed 目录只放清洗后结果。后面所有特征工程都从 processed 开始,避免反复改原始文件。

5. 特征工程与选手状态分析

这是整个项目的核心。预测一场比赛,不能直接用“队伍全年胜率”这种静态特征,要用赛前滚动窗口特征。以 AL 和 LGD 为例,预测这场比赛时,AL 侧的每一个特征都只能由 AL 在这场比赛之前的 N 场比赛计算得出,LGD 同理。

先定义滚动特征函数:

def rolling_team_stats(df, group_col, feature_col, window=5): df = df.sort_values("game_date") grouped = ( df.groupby(group_col)[feature_col] .rolling(window, min_periods=1) .mean() .reset_index() ) grouped["feature_name"] = f"{feature_col}_win{window}" return grouped

然后按队伍维度汇总常见特征:

  • team_winrate_5:近 5 场胜率。
  • avg_kda:近 5 场场均 KDA。
  • gold_diff_at15:前 15 分钟经济差均值,反映前期节奏。
  • vision_diff:场均视野分差。
  • dragon_rate:小龙控制率。
  • first_blood_rate:一血率,体现前期设计能力。

辅助选手的个人特征可以单独做一组。像标题里说的“激情辅助松松”,如果松松是某队的首发辅助,那么他的场均助攻、视野分、参团率会直接影响队伍视野和中后期团战质量。用选手统计表按选手 ID 过滤即可:

# 选手名请根据真实数据替换,这里只是示例 support = player_stats[player_stats["player_name"].str.contains("松松", na=False)] support_features = ( support.groupby("match_id") .agg( support_assists_mean=("assists", "mean"), support_vision_score_mean=("vision_score", "mean"), support_kill_participation_mean=("kill_participation", "mean"), ) .reset_index() ) df = df.merge(support_features, on="match_id", how="left")

这里有一个容易踩的坑:选手名可能在不同数据源里有不同写法,比如带战队前缀、带英文 ID。先用unique()查看选手名单,再用精确匹配,不要用模糊匹配一把梭。

特征表构造完成后,按时间切分训练集和测试集。注意,必须按时间顺序切分,不能随机切分,否则会造成时间穿越:

train_df = df[df["game_date"] < "2025-09-01"] test_df = df[df["game_date"] >= "2025-09-01"] print("训练集样本量:", len(train_df)) print("测试集样本量:", len(test_df))

这里的日期只是示例。你要根据目标比赛日做调整,比如目标是 9 月 4 日的 AL 对 LGD,那么切分点就应该设为 9 月 4 日之前。

6. 模型训练与胜率参考预测

特征准备好了,接下来选择模型。逻辑回归是首选,因为特征维度不高、样本量不大,逻辑回归稳定且可解释性强。如果想追求一点上限,再对比 XGBoost。

from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split features = [ "team_winrate_5", "avg_kda", "gold_diff_at15", "vision_diff", "dragon_rate", "first_blood_rate", "support_assists_mean", "support_vision_score_mean", ] X = train_df[features] y = train_df["win"] X_train, X_val, y_train, y_val = train_test_split( X, y, test_size=0.2, random_state=42 ) model = LogisticRegression(max_iter=1000) model.fit(X_train, y_train) print("验证集准确率:", model.score(X_val, y_val))

注意,这里用随机切分只是为了快速验证代码流程。正式评估应该结合时间序列切分,比如用前 80% 的比赛训练、后 20% 的比赛验证,这样更接近真实预测场景。

训练完成后,构造 AL 和 LGD 这场比赛的赛前特征。假设你已经从 processed 数据中计算好了两队各自的特征向量:

al_features = pd.DataFrame([[ 0.60, 3.20, 850, 6.5, 0.55, 0.62, 8.90, 42.5 ]], columns=features) lgd_features = pd.DataFrame([[ 0.40, 2.70, -500, -3.2, 0.38, 0.50, 9.10, 38.0 ]], columns=features) al_prob = model.predict_proba(al_features)[0][1] lgd_prob = model.predict_proba(lgd_features)[0][1] print(f"AL 胜率参考概率: {al_prob:.2%}") print(f"LGD 胜率参考概率: {lgd_prob:.2%}")

上面这几行数据只是占位演示,不是真实战绩。真实场景中,al_features和lgd_features的值来自第 5 步构建的赛前特征表,绝对不能手填。

模型输出的是参考概率,不是最终结论。判断模型是否可用,要看三个东西:验证集准确率、LogLoss、以及预测概率在强弱队对阵时的排序是否合理。如果模型把所有比赛都预测成五五开,说明特征没有区分度;如果准确率超过 90%,反而要警惕是不是混入了比赛后的数据,比如把已经打完的比赛统计塞进了特征列。

7. 批量分析与接口封装

单场比赛预测只是第一步。实际使用中,你可能要一次性分析第 9 周所有比赛,或者定期生成赛果复盘表。批量任务可以这样设计:

match_list = ["match_001", "match_002", "match_003"] results = [] for match_id in match_list: feat = build_features_for_match(match_id) prob = model.predict_proba(feat)[0][1] results.append({ "match_id": match_id, "win_probability": round(prob, 4), }) result_df = pd.DataFrame(results) result_df.to_csv("./outputs/predictions.csv", index=False) print(result_df)

这也是为什么前面强调要把特征工程封装成函数:只要你写好了build_features_for_match,批量预测就只是一个 for 循环。要注意给每个匹配加上日志,方便排查哪条数据缺失:

import logging logging.basicConfig(level=logging.INFO) for match_id in match_list: try: feat = build_features_for_match(match_id) prob = model.predict_proba(feat)[0][1] results.append({"match_id": match_id, "win_probability": round(prob, 4)}) except Exception as e: logging.error(f"处理 {match_id} 失败: {e}")

批量任务必须加失败重试和日志,否则某个 CSV 编码问题可能导致整批任务静默失败。

接口方面,把训练好的模型保存到本地,再用 FastAPI 封装:

import joblib import pandas as pd from fastapi import FastAPI from pydantic import BaseModel # 训练完成后先保存模型 # joblib.dump(model, "./models/lpl_model.joblib") app = FastAPI() model = joblib.load("./models/lpl_model.joblib") class MatchFeatures(BaseModel): team_winrate_5: float avg_kda: float gold_diff_at15: float vision_diff: float dragon_rate: float first_blood_rate: float support_assists_mean: float support_vision_score_mean: float @app.post("/predict") def predict(features: MatchFeatures): X_input = pd.DataFrame([features.model_dump()]) prob = model.predict_proba(X_input)[0][1] return {"win_probability": float(prob)}

启动接口:

uvicorn api_server:app --host 127.0.0.1 --port 8000

用 curl 测试:

curl -X POST "http://127.0.0.1:8000/predict" \ -H "Content-Type: application/json" \ -d '{"team_winrate_5":0.6,"avg_kda":3.2,"gold_diff_at15":850,"vision_diff":6.5,"dragon_rate":0.55,"first_blood_rate":0.62,"support_assists_mean":8.9,"support_vision_score_mean":42.5}'

Python 调用也放出来:

import requests url = "http://127.0.0.1:8000/predict" payload = { "team_winrate_5": 0.6, "avg_kda": 3.2, "gold_diff_at15": 850, "vision_diff": 6.5, "dragon_rate": 0.55, "first_blood_rate": 0.62, "support_assists_mean": 8.9, "support_vision_score_mean": 42.5, } resp = requests.post(url, json=payload, timeout=10) print(resp.json())

接口部署时只监听 127.0.0.1,不要直接暴露到公网。如果需要给同事或小组用,加上简单的 Token 鉴权,或者放在内网环境里。

8. 结果可信度与性能观察

先说实话:电竞预测模型的准确率上限很有限。比赛数据样本量少,版本更新频繁,选手转会、伤病、临场发挥都会引入噪声。逻辑回归在几千行训练数据上达到 60%-70% 的验证集准确率已经是不错的结果,再往上需要非常多的特征工程和数据积累。

这套流程的性能压力很小:

  • 训练数据量通常在几千到几万行。
  • 特征维度控制在几十个以内。
  • 逻辑回归在 CPU 上通常几秒完成训练。
  • XGBoost 在 CPU 上一般也不会超过几十秒。

实际时间以你的机器为准。如果数据量到了十万行级别,再考虑用 LightGBM 加 GPU 加速,但那是另一个场景了。

观察训练耗时可以用最简单的time:

import time start = time.time() model.fit(X_train, y_train) print("训练耗时:", time.time() - start, "秒")

内存占用方面,如果数据只有几千行,通常不到 1GB。可以用 memory_profiler 观察,但没必要在这类小数据上过度优化。

性能观察更值得关注的是特征重要性和预测稳定性。XGBoost 版本可以直接看特征重要性:

from xgboost import XGBClassifier xgb_model = XGBClassifier(eval_metric="logloss") xgb_model.fit(X_train, y_train) importance = sorted( zip(features, xgb_model.feature_importances_), key=lambda x: x[1], reverse=True, ) for feat, imp in importance: print(f"{feat}: {imp:.4f}")

如果辅助选手相关特征权重很低,说明数据覆盖不足或者助攻/视野分与赛果相关性弱;如果近 5 场胜率权重过高,模型可能对连胜连败过于敏感,需要引入对手强度校准。

再提醒一次,模型概率只能作为客观化参考。看比赛还是需要看版本理解、BP、队员当天状态,模型只是把历史数据里的模式提炼出来,不能代替人的临场判断。

9. 常见问题与排查方法

问题现象可能原因排查方式解决方案
CSV 读取中文乱码文件编码不是 UTF-8用记事本打开看原始编码指定 encoding="gbk" 或统一转为 utf-8
merge 后行数翻倍关联键 match_id 不唯一检查 value_counts()先对选手表/队伍表按 match_id 聚合再 merge
训练集日期范围不对切分条件写反打印 train_df 和 test_df 日期范围严格按目标比赛日时间切分
预测概率全部约等于 0.5特征无区分度检查特征的 describe 和分布增加滚动窗口特征、对手强度特征
准确率异常高混入了比赛结束后才产生的数据检查特征中是否出现“该场赛后统计”每个特征都验算时间,只保留赛前信息
FastAPI 请求超时模型加载慢或服务未启动查看 uvicorn 日志重启服务,检查端口占用
xgboost 安装失败系统缺少编译环境pip show xgboost 看错误换用 sklearn 的 GradientBoostingClassifier
接口可以被外部访问监听地址写了 0.0.0.0检查启动命令改为 127.0.0.1 或加 Token 鉴权
批量任务卡住单个比赛数据处理异常未捕获检查日志输出加 try/except 和失败重试

最容易踩的坑就一个:时间穿越。任何特征只要混入了该场比赛结束之后的信息,模型验证集表现都会虚高,一旦到真实预测就立刻失真。最稳妥的办法是单独写一个函数,传入比赛日,特征只从该日之前的样本中计算。

10. 最佳实践与使用建议

工程上给你几条建议。

第一,数据管线要分层。raw 目录保持原始数据不动,processed 目录放清洗后的宽表,features 目录放特征表,outputs 目录放预测结果。每一步都在本地落盘,方便回溯。

第二,模型训练完立刻保存。每次接口启动直接加载模型,不用重复训练:

import joblib joblib.dump(model, "./models/lpl_model.joblib")
model = joblib.load("./models/lpl_model.joblib")

第三,训练集和验证集按时间切分,不要随机切分。随机切分会把未来的信息放进训练集,导致评估结果过度乐观。

第四,批量任务要记录每次预测的特征快照。这样比赛结束后回头复盘,能清楚看到当时模型用的是哪些数据,而不是只留下一个概率数字。

第五,接口服务限制访问范围。内网部署、本机演示都可以,不要直接暴露公网。如果确实需要远程访问,至少加一层 API Key。

第六,涉及选手姓名、战队名称、比赛数据的版权合规问题要提前确认。个人学习用没关系,公开博客输出或商业发布需要确认数据源是否允许再分发。

第七,如果你准备把结果发布到博客或社交媒体,必须标注这是数据分析工程的参考概率,不构成赛事推荐或预测承诺。

11. 总结与下一步

这个项目最值得尝试的地方,不是模型多高级,而是你能用一套完全标准的数据分析流程,把一项基于直觉和评论判断的“状态好坏”变成可计算的指标。对于 AL 对 LGD 这场比赛,你真正验证的是:队伍近 5 场胜率、前期经济差、视野分、小龙控制率、辅助选手助攻贡献这些特征,在历史数据里到底有多大解释力。

最先要验证的功能是特征工程里的时间切分。先把切分逻辑写对,后面的模型准确率、接口对接才有意义。最容易踩的坑也是这里:特征里混入赛后数据。

后续可以继续扩展的方向很多:加入 BP 阶段英雄优先级、选手历史对阵记录、版本补丁改动时间点;把逻辑回归换成 LightGBM 并做超参搜索;用 Streamlit 做一个交互式复盘报告;把本项目扩展到其他电竞项目或体育赛事,思路完全一致。

建议收藏备用,先从一个小数据集跑通全流程,再逐步加特征、加比赛。数据不会说谎,但前提是你的时间切分没有作弊。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询