用Python正则解析对局记录并生成统计报表
看到Breach 16-15-9 Split (Lose)时第一眼只会注意到 Lose。它看起来是一行非常短的对局记录但如果把这段文本当成需要分析的数据它其实包含至少四个字段角色 Breach、个人战绩 16-15-9、地图 Split、结算 Lose。按常见对局记录的写法可以解释为本局使用 Breach 角色击杀了 16 次、阵亡了 15 次、拿到了 9 个助攻比赛地图是 Split最终结果没有赢下这一局。这条文本的问题在于它把“角色、击杀、阵亡、助攻、地图、胜负”混在一个字符串里。只靠肉眼查看能得出的结论非常有限当记录规模达到几十场甚至上百场时人工复盘几乎不可能保持标准一致。更合理的做法是先把它解析成结构化数据再基于数据做统计、筛选和对比。下面从一个最小样例开始不讲授具体游戏操作而是写一套 Python 解析与统计工具。输入一行类似Breach 16-15-9 Split (Lose)的文本输出一个MatchRecord数据对象再批量汇总成 CSV 和统计表。最后会整理解析失败的常见原因以及如何避免因为样本太少而做出错误判断。1. 先拆字段这行文本到底记录了什么1.1 四种片段与常见混淆可以把Breach 16-15-9 Split (Lose)按空白分隔成四段各自含义如下。文本片段字段名示例值说明BreachagentBreach本局使用的角色或职业16-15-9kills-deaths-assists16-15-9击杀、阵亡、助攻三个数字Splitmap_nameSplit本局对局地图(Lose)resultLose本局结算结果通常为 Win 或 Lose其中最容易混淆的是16-15-9。在有的统计页里这类格式可能表示击杀-阵亡-助攻在另外一些工具里可能表示首杀、爆头、助攻等指标。本文使用的上下文是“角色 击杀-阵亡-助攻 地图 结果”因此在后续解析时会按击杀 16、阵亡 15、助攻 9 处理。实际对接外部数据源时需要先查看字段说明不能只凭格式猜测。之所以不直接使用文本进行统计是因为文本存在版本差异。下面三种写法在人类眼里基本等价但在程序里是完全不同的字符串Breach 16-15-9 Split (Lose) Breach 16-15-9 Split ( Lose ) breach:16-15-9:split:lose如果希望同一个统计脚本能处理多来源数据就必须先把这些文本统一成固定的字段结构。本文先以一版规范格式为准后面的排错章节会分析不统一的情况。1.2 为什么要先做结构化结构化之后能做的最小有价值操作是把 result 从文本变成二分类标签is_win 1 if result Win else 0有了这个标签就可以按角色、地图、时间段统计胜率有了击杀、阵亡、助攻三个整数就可以计算场均表现、中位数和异常值。这比在记事本里反复搜索Lose要可靠得多。从数据工程的角度看这一步属于“从半结构化文本到结构化记录”的清洗过程。解析器越早发现格式问题后面的统计就越不会受到脏数据影响。注意如果只是偶尔复盘一场手工记录完全够用。需要写解析器的前提是记录会持续产生并且你能从批量统计中获得新信息。2. 准备 Python 环境和复盘项目结构2.1 运行环境与依赖解析和统计使用 Python 3.10 以上版本主要用到标准库dataclasses、re、json以及第三方库pandas。可视化阶段如果要用可以再安装matplotlib。工具版本建议用途Python3.10使用 dataclass、类型注解、list 泛型pandas2.xDataFrame 汇总与透视matplotlib3.x按地图或角色绘制柱状图、趋势图检查本地环境python --version pip install pandas matplotlib如果 Python 版本较低例如 3.8需要把List[MatchRecord]写成typing.List[MatchRecord]否则会触发语法兼容问题。示例代码里的list[MatchRecord]依赖 3.9因此建议直接使用 3.10 以上的环境。2.2 目录结构与输入文件在本地创建一个独立目录例如breach_analysisbreach_analysis/ ├── parser.py ├── analyze.py ├── records.txt ├── records.csv └── output/ └── breach_by_map.csvparser.py负责把文本行解析成数据模型records.txt是原始样例输入records.csv是解析后的中间结果output目录保存统计数据。records.txt可以先放十几行样例用来跑通流程。下面是一些符合规则的记录这里用到了一个占用字段比较少的英文角色名实际使用中应根据自己的数据源替换Phoenix 18-14-7 Ascent (Win) Breach 16-15-9 Split (Lose) Breach 21-9-6 Split (Win) Breach 12-20-11 Icebox (Lose) Breach 15-11-7 Split (Lose) Breach 19-10-8 Split (Win) Breach 10-18-15 Haven (Lose) Breach 20-12-9 Split (Lose) Breach 17-13-10 Split (Win) Breach 16-15-9 Split (Lose)这些数据只用于说明统计流程不代表真实战绩结论。3. 实现解析器把战绩行变成结构化数据3.1 用 dataclass 固定记录结构数据模型最重要的作用是固定字段名和类型。如果后面代码里把kills写成kill类型系统或在数据访问层能尽早发现问题。在parser.py里定义MatchRecord# parser.py from dataclasses import dataclass from typing import List import re dataclass(frozenTrue) class MatchRecord: agent: str kills: int deaths: int assists: int map_name: str result: str property def kda(self) - float: if self.deaths 0: return float(self.kills self.assists) return round((self.kills self.assists) / self.deaths, 2)这里使用了frozenTrue让对象创建之后不可修改。对复盘场景来说单场记录一旦解析完成就不允许后续逻辑无意中覆盖字段这能减少统计阶段的副作用。kda属性不是单纯的“(击杀助攻)/阵亡”而需要处理阵亡为 0 的情况。按主流口径阵亡为 0 时通常记为该局击杀加助攻之和作为分母保护的兜底值。如果直接除以 0程序会抛ZeroDivisionError。3.2 用正则解析单行记录继续在parser.py中写解析函数。正则表达式的作用是描述文本结构角色名、三个数字、地图名、括号结果。MATCH_RE re.compile( r^(?Pagent[A-Za-z])\s r(?Pkills\d)-(?Pdeaths\d)-(?Passists\d)\s r(?Pmap_name[A-Za-z])\s* r\(\s*(?PresultWin|Lose)\s*\)\s*$ ) def parse_line(line: str) - MatchRecord: text line.strip() m MATCH_RE.match(text) if not m: raise ValueError(f无法解析: {text}) values m.groupdict() return MatchRecord( agentvalues[agent], killsint(values[kills]), deathsint(values[deaths]), assistsint(values[assists]), map_namevalues[map_name], resultvalues[result], ) def parse_text(raw: str) - List[MatchRecord]: records [] for line_no, line in enumerate(raw.splitlines(), start1): if not line.strip(): continue try: records.append(parse_line(line)) except ValueError as exc: print(f跳过第 {line_no} 行: {exc}) return records在parse_line中三个数字用split(-)也能实现但正则表达式能同时校验字段顺序和整体格式缺一个括号或字段解析就无法通过。parse_text遍历多行时不会因为单行错误而中断而是打印跳过的行号方便后续定位脏数据。验证一下单行解析python -c from parser import parse_line; r parse_line(Breach 16-15-9 Split (Lose)); print(r); print(r.kda)输出会显示MatchRecord(agentBreach, kills16, deaths15, assists9, map_nameSplit, resultLose) 1.67从这行就能看出16 击杀和 9 助攻合计 25 个参与击杀贡献分摊到 15 次阵亡后得到 KDA 1.67。该指标可以作为一个整体的参与度参考。3.3 批量解析并导出 CSV批量读取records.txt转成 JSON 和 CSVfrom dataclasses import asdict import json import pandas as pd with open(records.txt, r, encodingutf-8) as f: records parse_text(f.read()) records_json [asdict(record) for record in records] with open(records.json, w, encodingutf-8) as f: json.dump(records_json, f, ensure_asciiFalse, indent2) df pd.DataFrame(records_json) df.to_csv(records.csv, indexFalse, encodingutf-8)CSV 可以直接用 Excel 打开也可以在pandas里继续分析。records.csv的字段顺序是解析时写入的顺序agent,kills,deaths
上一篇/下一篇内容由系统自动关联
返回资讯列表 →