1.前言
本章旨在运用spark集群分析csgo时期职业选手数据集,通过spark各个模块分析职业选手的游戏数据并进行可视化。
2.系统设计思路
2.1系统框架图
2.2模块功能详解
数据清洗
删除不需要的列,对于队伍为空的数据,添加"NO",处理特殊字符。
rating排序
将数据转换成DataFrame根据rating排序,若rating相同则按照ADR排序,若ADR相同则按照先后顺序排序将排序结果写入Spark SQL将rating排序前三的热门选手数据输出为一个文件。
数据可视化
读取top3.csv文件分析3位选手的击杀数据生成击杀数据的扇形图
Spark SQL库的信息查询与分析
查询中国的所有玩家的基本信息,查询G2中杀敌数最多的玩家,计算所有玩家的K/D均值,查询使用狙击枪杀敌数最多的前五名玩家,查询在每回合中伤害最高的玩家的昵称和伤害数值。
数据流处理
使用Structured Streaming组件模拟动态输出将playerpositionanalysis原始数据输出对职位数量做词频统计并输出。
3.系统功能实现
3.1系统开发(运行)坏境
硬件环境:虚拟机模拟
软件环境:SecureCRT 9.4,PyCharm 2023.3.2,VMware Workstation Pro,ubuntu-18.04.5-live-server-amd64.iso,spark-2.4.0-bin-without-hadoop.tgz,hadoop-2.7.4.tar.gz,mysql-connector-j-8.3.0.jar,Anaconda3-2020.11-Linux-x86_64.sh。
3.2功能模块
3.2.1数据清洗
实现功能
使用RDD将将数据集不要的列删除,处理数据文件中的空值和特殊字符并生成一个新的csv文件。
核心代码
from pyspark import SparkContext, SparkConf
import os
# 设置JAVA_HOME环境变量
os.environ['JAVA_HOME'] = "/home/spark006/servers/jdk"
# 设置Spark Context
conf = SparkConf().setAppName("DataCleaningWithRDD")
sc = SparkContext(conf=conf)
# 读取数据
raw_data = sc.textFile("file:///home/spark006/data/cswork/csgo_players.csv")
# 获取header
header = raw_data.first()
data = raw_data.filter(lambda row: row != header)
# 切分数据
data_split = data.map(lambda line: line.split(","))
# 删除不需要的列
columns_to_keep = [0, 1, 2, 3, 4, 5] # 这些是需要保留的列索引
data_filtered = data_split.map(lambda row: [row[i] for i in columns_to_keep])
# 填充current_team为空的行
current_team_index = 5 # current_team在第6列
data_filled = data_filtered.map(lambda row: row[:current_team_index] + ["NO" if row[current_team_index] == "" else row[current_team_index]] + row[current_team_index + 1:])
# 清洗特殊符号
import re
def clean_special_chars(row):
row[0] = re.sub('[^a-zA-Z0-9\s]', '', row[0]) # 清洗real_name
row[1] = re.sub('[^a-zA-Z0-9\s]', '', row[1]) # 清洗teams
return row
data_cleaned = data_filled.map(clean_special_chars)
# 转换回CSV格式并保存
data_cleaned.map(lambda row: ",".join(row)).saveAsTextFile("file:///home/spark006/data/cswork/cleaned_dataset_rdd.csv")
# 关闭Spark Context
sc.stop()
结果展示
分析描述总结
成功的清洗了数据,方便后续对数据进行进一步的处理与分析
3.2.2rating排序
实现功能
将数据转化成dataframe,通过比较rating及ADR对文件中的玩家数据进行排名,并将新数据存进数据库当中,另外将排名在前三位的玩家数据输出一个新的csv文件
核心代码
from pyspark.sql import SparkSession
import os
# 设置JAVA_HOME环境变量
os.environ['JAVA_HOME'] = "/home/spark006/servers/jdk"
# 创建Spark Session,并配置MySQL Connector/J的驱动路径
spark = SparkSession.builder \
.appName("PlayerRanking") \
.config("spark.driver.extraClassPath", "/home/spark006/servers/spark-GL/jars/mysql-connector-j-8.3.0.jar") \
.getOrCreate()
# CSV文件路径
csv_file_path = "file:///home/spark006/data/cswork/cleaned_dataset/cleaned_dataset.csv"
# 读取数据并转换为DataFrame
data = spark.read.csv(csv_file_path, header=True, inferSchema=True)
# 确认列名
columns = data.columns
# 定义排序逻辑,'rating'是最后一列,'damage_per_round'是第十列
def sort_key(row):
return (-row[columns[-1]], -row[columns[9]])
# 排序DataFrame
sorted_df = data.orderBy(data[columns[-1]].desc(), data[columns[9]].desc())
#通过jdbc连接数据库
jdbcDF = (spark.read.format("jdbc")
.option("driver", "com.mysql.jdbc.Driver")
.option("url", "jdbc:mysql://localhost:3306/player_stats")
.option("dbtable", "player_stats").option("user","spark006")
.option("password","123456")
.load())
# 将DataFrame写入MySQL表
jdbc_url = "jdbc:mysql://localhost:3306/player_stats"
table_name = "player_stats"
connection_properties = {
"user": "spark006", # 数据库用户名
"password": "123456", # 数据库密码
"driver": "com.mysql.cj.jdbc.Driver", # MySQL JDBC驱动类
"useUnicode": "true" # 启用Unicode支持
}
# 将排序后的DataFrame写入MySQL数据库表player_stats
sorted_df.write.jdbc(url=jdbc_url, table=table_name, mode="overwrite", properties=connection_properties)
# 获取热门选手(rating排序前三个人)
top3_df = sorted_df.limit(3)
# 输出热门选手到top3.csv文件,使用mode="overwrite"确保覆盖旧文件
top3_df.write.csv("file:///home/spark006/data/cswork/top3.csv", header=True, mode="overwrite")
# 关闭Spark Session
spark.stop()
结果展示
数据库中查询结果
输出的csv文件
分析描述总结
将排序后的数据存入数据库中,更有利于数据的查看和分析。
3.2.3数据可视化
实现功能
将上一步输出的top3.csv文件进行可视化分析
核心代码
import pandas as pd
import matplotlib.pyplot as plt
import os
# 设置JAVA_HOME环境变量
os.environ['JAVA_HOME'] = "/home/spark006/servers/jdk"
# 读取 top3.csv 文件
csv_file = 'file:///home/spark006/data/cswork/top3/top3.csv'
df = pd.read_csv(csv_file)
# 提取三位选手的击杀数据
player1 = df.iloc[0]
player2 = df.iloc[1]
player3 = df.iloc[2]
# 准备数据
players = ['Player 1', 'Player 2', 'Player 3']
labels = ['Rifle Kills', 'Sniper Kills', 'SMG Kills', 'Pistol Kills', 'Grenade Kills', 'Other Kills']
colors = ['#ff9999','#66b3ff','#99ff99','#ffcc99','#c2c2f0','#ffb3e6']
# 构建每位选手的击杀数据列表
kills_player1 = [player1['rifle_kills'], player1['sniper_kills'], player1['smg_kills'],
player1['pistol_kills'], player1['grenade_kills'], player1['other_kills']]
kills_player2 = [player2['rifle_kills'], player2['sniper_kills'], player2['smg_kills'],
player2['pistol_kills'], player2['grenade_kills'], player2['other_kills']]
kills_player3 = [player3['rifle_kills'], player3['sniper_kills'], player3['smg_kills'],
player3['pistol_kills'], player3['grenade_kills'], player3['other_kills']]
# 绘制扇形图
fig, axs = plt.subplots(1, 3, figsize=(15, 5))
for i, (player, kills) in enumerate(zip(players, [kills_player1, kills_player2, kills_player3])):
axs[i].pie(kills, labels=labels, autopct='%1.1f%%', colors=colors, startangle=140)
axs[i].set_title(f'{player} Kills Distribution')
plt.tight_layout()
plt.show()
结果展示
分析描述总结
可视化前三位选手的击杀数据可以让我们更清晰的了解三位选手的共同之处以及个人特点。
3.2.4Spark SQL库的信息查询与分析
实现功能
查询中国的所有玩家的基本信息,查询G2中杀敌数最多的玩家,计算所有玩家的K/D均值,查询使用狙击枪杀敌数最多的前五名玩家,查询在每回合中伤害最高的玩家的昵称和伤害数值。
核心代码
1.SELECT nickname,real_name,age,country,current_team
FROM player_stats
WHERE country = 'China';
2.SELECT nickname, total_kills
FROM player_stats
WHERE current_team = 'G2'
ORDER BY total_kills DESCLIMIT 1;
3.SELECT AVG(kills_per_death)
AS average_kills_per_death
FROM player_stats;
4.SELECT nickname, sniper_kills
FROM player_stats
ORDER BY sniper_kills DESCLIMIT 5;
5.SELECT nickname, damage_per_round
FROM player_stats
ORDER BY damage_per_round DESCLIMIT 1;
结果展示
分析描述总结
通过sql查询语句对数据库中的数据进行查询分析,使得我们进一步了解职业玩家。
3.2.5数据流处理
实现功能
通过分析选手击杀使用的武器,根据击杀数来确定该玩家在队伍中担任的角色并将该数据输出一个csv文件,再通过structured streaming的file源模拟动态输出,另外再通过该组件对各个玩家在队伍中担任的角色进行词频统计。
核心代码
统计在队伍中担任的角色
from pyspark.sql import SparkSession
from pyspark.sql.functions import col, greatest, when
import os
# 设置JAVA_HOME环境变量
os.environ['JAVA_HOME'] = "/home/spark006/servers/jdk"
# 创建 SparkSession
spark = SparkSession.builder \
.appName("PlayerPositionAnalysis") \
.getOrCreate()
# 读取清洗后的 CSV 文件
df = spark.read.csv('file:///home/spark006/data/cswork/cleaned_dataset/cleaned_dataset.csv', header=True, inferSchema=True)
# 定义函数来确定职位
def determine_position(row):
max_column = greatest(*[col(column) for column in ['rifle_kills', 'sniper_kills', 'smg_kills', 'pistol_kills', 'grenade_kills', 'other_kills']])
position = when(max_column == col('rifle_kills'), '步枪手') \
.when(max_column == col('sniper_kills'), '狙击手') \
.when(max_column == col('smg_kills'), '突破手') \
.when(max_column == col('pistol_kills'), '手枪大师') \
.when(max_column == col('grenade_kills'), '道具手') \
.when(max_column == col('other_kills'), '无') \
.otherwise('无')
return position
# 添加新的职位列
df_with_position = df.withColumn('position', determine_position(df))
# 选择需要的列并保存到新的 CSV 文件
df_result = df_with_position.select('nickname', 'country', 'current_team', 'rating', 'position')
df_result.write.csv('file:///home/spark006/data/cswork/PlayerPositionAnalysis.csv', header=True, mode='overwrite')
# 关闭 SparkSession
spark.stop()
print("处理完成,结果已保存到 PlayerPositionAnalysis.csv 文件中。")
模拟动态输出选手信息
from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StringType, DoubleType, IntegerType
import os
# 设置JAVA_HOME环境变量
os.environ['JAVA_HOME'] = "/home/spark006/servers/jdk"
# 创建 SparkSession
spark = SparkSession.builder \
.appName("PlayerPositionAnalysisStreaming") \
.getOrCreate()
# 定义 CSV 文件的 schema
schema = StructType() \
.add("nickname", StringType(), True) \
.add("country", StringType(), True) \
.add("current_team", StringType(), True) \
.add("rating", DoubleType(), True) \
.add("position", StringType(), True)
# 从文件系统中读取 PlayerPositionAnalysis.csv 文件作为流数据源
input_path = "file:///home/spark006/data/cswork/PlayerPositionAnalysis/"
streaming_df = spark.readStream \
.option("header", "true") \
.schema(schema) \
.csv(input_path)
# 输出到控制台,模拟动态输出
query = streaming_df \
.writeStream \
.outputMode("append") \
.format("console") \
.option("truncate", "false") \
.start()
# 等待流查询结束
query.awaitTermination()
# 关闭 SparkSession
spark.stop()
词频统计
from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StringType
from pyspark.sql.functions import col, lower, explode, split
import os
# 设置JAVA_HOME环境变量
os.environ['JAVA_HOME'] = "/home/spark006/servers/jdk"
# 创建 SparkSession
spark = SparkSession.builder \
.appName("RoleFrequencyStreaming") \
.getOrCreate()
# 定义 CSV 文件路径
input_path = "file:///home/spark006/data/cswork/PlayerPositionAnalysis"
# 定义数据结构(schema)
schema = StructType() \
.add("nickname", StringType(), True) \
.add("country", StringType(), True) \
.add("current_team", StringType(), True) \
.add("rating", StringType(), True) \
.add("position", StringType(), True)
# 读取 CSV 文件作为流数据源
streaming_df = spark.readStream \
.option("header", "true") \
.schema(schema) \
.csv(input_path)
# 将职位数据转换为小写,并按空格拆分
roles_df = streaming_df.select(explode(split(lower(col("position")), " ")).alias("position"))
# 过滤有效职位并计数
role_counts = roles_df.filter((col("position") == "步枪手") |
(col("position") == "狙击手") |
(col("position") == "突破手") |
(col("position") == "手枪大师") |
(col("position") == "道具手")) \
.groupBy("position") \
.count()
# 输出到控制台,实时显示词频统计结果
query = role_counts \
.writeStream \
.outputMode("complete") \
.format("console") \
.start()
# 等待流查询结束
query.awaitTermination()
# 关闭 SparkSession
spark.stop()
结果展示
统计在队伍中担任的角色
模拟动态输出选手信息
词频统计
分析描述总结
通过分析玩家在队伍中担任的角色,并进行模拟动态输出及词频统计模拟动态输出,让我们更加清晰的了解了各个职业玩家的惯用武器以及在职业比赛当中大部分选手的惯用武器。
4.结论
综上所述,该系统已经完成使用RDD对数据的清洗与存储,并通过spark sql组件对数据进行查询分析,另外还通过structured streaming组件完成对file源数据的模拟动态输出。但是由于清洗后的数据列过多导致存进spark sql库后,表的显示存在列名排列两行的情况,因此后续可以根据数据的种类等将数据分为多个表存储以此达到美化表的作用。
5.参考文献
[1]林子雨,郑山海,赖永炫.Spark编程基础(Python版).北京:中国人民邮电出版社,2024.4(2022.5重印)
[2]db_zm_2057.Spark应用:CS:GO游戏数据分析与指标计算(2023-11-21)[2024-6-20]https://
blog.csdn.net/TSKxxx/article/details/134540665