ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

2021-2025 Steam游戏数据集CSV:10特征清洗分析与推荐实践

2021-2025 Steam游戏数据集CSV:10特征清洗分析与推荐实践 简介面向2021至2025年Steam游戏市场的公开数据资源包含65,521款游戏条目适合游戏行业分析人员、市场研究者及对数字发行趋势感兴趣的学习者。数据来自官方Steam网页API覆盖appid、名称、发行日期、美元价格、类型、类别、开发者、出版商及用户推荐数共10个字段可用于分析市场趋势、类型热度、定价策略与独立游戏表现。资源包共2个文件以7z压缩包形式提供内含一个Python采集脚本与一个CSV数据文件整体大小约1.93MB。其中CSV为核心数据表Python脚本可用于数据更新或采集流程复现。已有349人浏览学习便于快速获取结构化数据进行二次分析或可视化。该数据集时间跨度完整兼顾已发售作品与计划于2025年发布的未来作品能为行业观察与量化分析提供基础支撑。1. 2021-2025 Steam游戏数据集10特征65k个独立条目CSV想研究游戏市场从这张表开始拿到这份 2021-2025 Steam游戏数据集10特征65k个独立条目CSV最值得做的不是急着画图而是先想清楚一个问题这 65k 条记录能帮你验证什么假设Steam 游戏列表本身并不稀缺稀缺的是把时间跨度、定价、评价、类型、标签压缩到一张表里让你能快速回答“疫情后独立游戏是不是更多了”“免费游戏的评分是不是真的更分化”“2024 年哪些品类发行量在涨”这类问题。它适合四类人想入门数据分析的新手、做推荐系统的算法工程师、写 Steam 相关爬虫或应用的后端开发以及做游戏市场研究的从业者。CSV 格式意味着你不需要任何数据库pandas 就能直接读但在开始之前先别把“特征数”当成“列数”理解下面我会从一个最常用的 10 列结构展开讲清楚。2. 先看清楚 10 个特征再动手字段语义、读取参数和第一眼检查2.1 一份 Steam 游戏 CSV 常见的 10 个特征是什么我接触过不少 Steam 游戏类 CSV列名很少完全一致但核心信息通常是同一批游戏唯一标识、名称、发布日期、价格、开发者/发行商、类型、标签、好评数、差评数、好评率。把这些列凑齐正好是 10 个特征特征名类型说明app_idint/strSteam 应用唯一 ID去重和关联外部数据的钥匙namestr游戏名注意可能有重名和空格release_datestr/datetime发行日期常见格式为 YYYY-MM-DDpricefloat当前价格美元免费游戏常为 0developersstr开发者多个用逗号或竖线分隔genresstr主类型如 Action、Indie、Strategytagsstr社区标签通常用竖线分隔数量较多positive_reviewsint好评数量negative_reviewsint差评数量positive_ratiofloat好评率取值 0~100注意单位是百分比还是 0~1前面几列一眼就能看懂真正影响后续分析的是positive_ratio的单位和tags的分隔符。如果你直接拿positive_ratio去乘 100或者把tags当成单个字符串去匹配“多人”或“单机”后面所有统计都会翻车。拿到 CSV 的第一件事不是训练模型而是用一个标准流程确认字段类型和取值分布。2.2 用 pandas 读取 CSVencoding 和 dtype 是两个省内存开关读取一张 65k 行、10 列的表文件本身可能只有几 MB 到几十 MB但如果你不做任何处理pandas 会默认把每一列都读成对象或 64 位整数内存翻两三倍很常见。我一般会在读取时就指定dtype避免事后才发现app_id被读成了 float。import pandas as pd df pd.read_csv( steam_games_2021_2025.csv, encodingutf-8, dtype{ app_id: int32, price: float32, positive_reviews: int32, negative_reviews: int32, positive_ratio: float32, }, parse_dates[release_date], low_memoryFalse, ) print(df.shape) print(df.head()) print(df.info(memory_usagedeep))这段代码里最值得注意的两个参数是dtype和parse_dates。dtype让数字列用更小的整型和浮点型存储65k 行看不出明显差异但后面一旦要合并 Steam 爬虫或外部评分数据这个习惯能省下几十 MB。parse_dates会把发行日期在读取阶段就转成datetime64避免你之后用pd.to_datetime再去遍历一遍。low_memoryFalse是为了防止 pandas 在分块读取时因为列类型推断不一致而给出潜在类型警告。读取完成后不要急着 head()先跑一下df.isna().sum()看空值列在哪些特征上。很多由爬虫生成的 CSV 会在tags、developers上留下空值这些空值不是简单的“没有”可能是爬虫没有抓取到也可能是独立游戏确实没有填写标签处理方式完全不同。2.3 从特征到业务问题哪些列可以组合使用10 个特征不是 10 个独立变量组合起来才能回答更具体的问题。比如release_date加genres可以看类型发行量随年份的变化price加positive_ratio可以看定价区间与口碑的关系tags加positive_reviews可以找出“EA 测试但评价不错”的小众产品。我习惯在清洗之前先做一遍“假设映射”也就是把业务问题拆成特征组合否则很可能会多洗掉不少有价值的数据。比如positive_ratio如果缺失不要立刻删行而是看positive_reviews和negative_reviews是否存在存在的话完全可以用后者手工计算好评率。3. 数据清洗把 10 个特征变成可供模型使用的干净宽表3.1 日期、价格、空值三个最先翻车的字段几乎所有 Steam 类 CSV 都逃不过这三个坑日期格式不统一、价格字段混入 “Free to Play” 或 “免费” 等文本、空值分布在多个列。直接dropna()会把 65k 删到 30k属于最粗暴的解法。我更倾向于按列清洗先让每一列都能被正确解释再决定是否删除行。import pandas as pd # 日期不强制格式让 pandas 自动解析解析失败的置为 NaT df[release_date] pd.to_datetime(df[release_date], errorscoerce) # 价格先统一转成字符串处理再转数值 df[price] df[price].astype(str).str.strip().str.lower() df.loc[df[price].str.contains(free, naFalse), price] 0 df[price] pd.to_numeric(df[price], errorscoerce) # 空值只对关键列删除缺失 df df.dropna(subset[app_id, name, release_date]) print(df.shape) print(df[df[price].isna()][[name, price]].head())这段代码的逻辑分三步走。第一步把release_date统一成 datetimeerrorscoerce会把类似 “2025-01-15” 和 “2025/1/15” 都解析掉但纯年份或 “Coming Soon” 会变成NaT后续再单独处理。第二步处理价格这里容易踩的细节是astype(str)之后整列变成字符串str.contains(free, naFalse)才能安全过滤空值最后再转数值。第三步只对主键列删除缺失价格缺失可以先保留后面用 0 或中位数填充。我在实际处理中会把解析失败的日期单独列出来看而不是直接删掉。很多时候失败信息里能看到 “TBA” 或 “2025” 这样的短格式它们可以单独归为一类“即将发行”或“年份未知”不需要完全丢弃。3.2 好评率、发行年份、标签拆分的特征工程原始 10 个特征对机器学习来说太原始了至少要构造出年份、归一化好评率、标签列表三个新变量。这里有一个经验positive_ratio即使已经给定我仍然会重算一遍因为少数行的positive_ratio可能是爬虫算错的用positive_reviews / (positive_reviews negative_reviews)校验一下更稳妥。# 发行年份 df[year] df[release_date].dt.year # 好评率用好评数重新计算避免原字段单位不统一 review_total df[positive_reviews] df[negative_reviews] df[positive_ratio_calc] df[positive_reviews] / review_total.replace(0, pd.NA) df.loc[review_total 0, positive_ratio_calc] pd.NA # 标签拆分为列表方便后续做多标签分析 df[tags_list] df[tags].fillna().str.split(|) # 把拆分后的标签展开成长表 tags_expanded df[[app_id, tags_list]].explode(tags_list) tags_expanded tags_expanded.dropna(subset[tags_list]) print(df[[name, year, positive_ratio, positive_ratio_calc]].head()) print(tags_expanded.head())这里最值得关注的是explode这一步。它把一行包含多个标签的游戏拆成多行好处是后续做“哪个标签平均好评率最高”的聚合变得非常直接坏处是如果直接让训练集爆炸会引入大量重复样本。所以我在实际工程里通常会保留两份数据一份是原始宽表df用于建模一份是长表tags_expanded用于统计和标签特征。两个表用app_id来做映射避免在长表上反复展开和合并。review_total.replace(0, pd.NA)也是一种常见防御写法没有评论的游戏不应被当成 0% 好评而应视为未知。后面填充时我会单独把positive_ratio_calc填为一个中心值比如 0.6而不是 0。这个细节直接影响推荐系统里冷启动策略。3.3 清洗后的质量校验为什么别直接用 groupby 出结论清洗后最常犯的错是直接df.groupby(year).size()然后开始画图结果发现某一年数量异常。原因很可能是在release_date解析阶段把很多无效值归到了同一年或者存在重复下载导致的重复行。# 1. 检查重复主键 dup_count df[app_id].duplicated().sum() print(fduplicated app_id: {dup_count}) # 2. 检查年份分布是否合理 year_dist df[year].value_counts().sort_index() print(year_dist) # 3. 检查价格是否有极端值 print(df[price].describe()) # 4. 清洗完成校验保存前确认主键唯一 df df.drop_duplicates(subset[app_id], keepfirst) df.to_csv(steam_clean.csv, indexFalse)我一般在剔重前会先问自己一句这个数据集是从单一 API 拿的还是多个爬虫拼的如果是多源合并重复不一定是同一款游戏可能是某个 DLC 或测试版占了多个条目。此时app_id唯一不一定合理需要先看name是否完全相同。如果构建推荐系统我会保留最全的那条记录如果只做市场统计则应该把重复项单独标记而不是默默删掉。4. 用 10 个特征回答三个业务问题趋势、定价与口碑4.1 2021-2025 发行数量与类型迁移清洗完成后第一个值得验证的问题是2021 到 2025 年Steam 游戏的发行量到底在涨还是跌如果把release_date拆成年份再叠加上genres分组看会发现“类型迁移”比总量更有意思某些年份动作射击类数量下滑模拟经营和生存类上升。这背后可能是市场偏好变化也可能是爬虫采集范围变化所以在得出结论前要给构图留一个交叉验证步骤。import pandas as pd df pd.read_csv(steam_clean.csv, parse_dates[release_date]) df[year] df[release_date].dt.year # 按年份统计发行量 trend df.groupby(year).size().reset_index(namegame_count) # 按年份类型统计 genre_trend df.dropna(subset[genres]).copy() genre_trend[main_genre] genre_trend[genres].str.split(,).str[0].str.strip() genre_trend genre_trend.groupby([year, main_genre]).size().reset_index(namecount) pivot genre_trend.pivot(indexyear, columnsmain_genre, valuescount).fillna(0) print(pivot.head(10))这段代码里有一个手动选主类型的动作str.split(,).str[0]会把多重类型里的第一个当作主类型因为它最接近游戏的核心分类。这样处理可以让pivot后的表列数不会爆炸到几十个。如果某些行genres是空值dropna(subset[genres])会丢弃这部分但注意这会减少统计总量单独画个饼图看缺失占比更稳妥。4.2 定价策略免费游戏、付费区间与好评率的关系价格和口碑的关系在游戏行业里一直是热门话题。免费游戏因为基础用户量大好评率往往高于预期低价独立游戏则容易陷入“好评但没销量”的困境。要验证这个先把价格分成几个区间再看每个区间的好评率中位数效果比散点图直观得多。bins [-0.01, 0, 9.99, 19.99, 29.99, 59.99, float(inf)] labels [free, under_10, 10_20, 20_30, 30_60, above_60] df[price_range] pd.cut(df[price], binsbins, labelslabels, rightFalse) # 好评率中位数和游戏数量 price_group df.dropna(subset[positive_ratio_calc]) \ .groupby(price_range, observedFalse)[positive_ratio_calc] \ .agg([median, count]) print(price_group)pd.cut的rightFalse很关键它保证 9.99 被归到 “under_10” 而不是 “10_20”。另外observedFalse是我们处理 category 类型时防止 pandas 3.0 警告的标准写法。如果你用过老版本 pandas这里可能会踩到 category 的坑下面一节会专门讲。4.3 把结论沉淀成一张可以直接用的宽表分析完之后我一般会把聚合结果导出成一个更小的宽表用于后续可视化或前端展示而不是让其他人直接操作 65k 行的原始表。summary df.groupby(year, as_indexFalse).agg( game_count(app_id, count), mean_price(price, mean), median_ratio(positive_ratio_calc, median), total_positive(positive_reviews, sum), ) summary.to_csv(steam_yearly_summary.csv, indexFalse) print(summary)as_indexFalse让year保留为列而不是变成索引这样导出 CSV 后其他人可直接导入 Excel 或做 BI 图表不用再 reset_index。这张表已经足够回答经常被问的“这几年 Steam 游戏整体是不是变贵了”这类问题。5. 常见问题与踩坑处理 Steam 游戏 CSV 时最常翻车的 5 个瞬间5.1 现象读取 CSV 后 app_id 变成 float后面合并全部错位原因CSV 里有空行或爬虫把 app_id 写成了科学计数法pandas 自动推断列类型时把它当成浮点型。此时df[app_id]里的值从amp55变成5.5e05最直接的后果就是 join 外部数据时能匹配上的一般不足 10%。解决读取时强制指定dtype{app_id: str}并在读取后检查是否存在形如730.0的字符串。如果已发生用df[app_id] df[app_id].astype(str).str.replace(r\.0$, , regexTrue)清理。5.2 现象release_date 中混入 “2025.1.5” 和 “2025-01-05” 后pd.to_datetime 解析出现不同年月原因Steam 数据源存在多套格式部分抓取工具会把点号或下划线当成日期分隔符自动解析会误以为 “5” 是月份。解决不要信任自动解析。我把release_date先转成字符串用正则统一替换分隔符为-再指定format或使用errorscoerce配合后续手工校验。更稳妥的是拿到数据时就先做一轮strftime标准化而不是等建模前再处理。5.3 现象CSV 里某列出现换行符read_csv 后行数比预期多几百行原因游戏简介或 tags 字段里含有\n如果生成 CSV 时没有套用 CSV 引号规则pandas 默认按换行切分导致单行断成多行。解决读取时使用quotingcsv.QUOTE_ALL或至少enginepython。如果文件已经损坏可以用csv模块逐行恢复但更建议从源头爬虫端修好写入时用csv.writer(row)并用quotechar不要手工用逗号拼接字段。5.4 现象清洗后只剩 3 万行数据量腰斩原因一次性dropna()把所有有缺失的列全部过滤开发者、标签、好评率任一缺失都会删掉整行。解决改变清洗顺序先对主键列去重再对参与建模的核心列release_date、price做局部dropna其余缺失列用中位数、众数或专门标记填充。比如developers缺失可以填unknownpositive_ratio_calc缺失可以填 50 分位而不是丢数据。5.5 现象用外部 Steam 数据去补 CSV 时按名称匹配成功率不到一半原因同一游戏在不同来源中的名称可能差一个空格、年份或副标题比如 “Dota 2” 和 “Dota2” 表现不一致名称匹配天然不可靠。解决不要直接用name作 key统一通过app_id关联。如果外部接口没有返回 ID先做一个归一化函数转小写、去空格、统一全半角再生成一个slug列用于模糊匹配。这么做之后匹配率会从 40% 提到 90% 以上。补充数据时还容易触发 Steam 侧连接失败或限流提示解决办法是加随机延时、按app_id升序遍历、失败自动跳过而不是一次性并发请求。6. 继续往前走用清洗后的 CSV 跑一个最小推荐并建立更新习惯6.1 基于类型和标签做内容推荐的最小实现当字段清洗到genres和tags_list都能干净使用时最值得做的进阶尝试是内容推荐。对 65k 条游戏做近邻搜索并不需要复杂的图模型TF-IDF 加余弦相似度已经能给出不错的效果而且代码很短from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity df[_text] ( df[genres].fillna() df[tags].fillna().astype(str).str.replace(|, , regexFalse) ) vec TfidfVectorizer(min_df2, max_features5000) mat vec.fit_transform(df[_text]) sim cosine_similarity(mat) # 找一个具体游戏做演示 demo_idx df.index[df[name] Dota 2] if len(demo_idx) 0: top5 sim[demo_idx[0]].argsort()[::-1][1:6] print(df.iloc[top5][[name, genres, positive_ratio_calc]])min_df2表示出现次数低于 2 的标签直接忽略避免稀碎标签影响相似度max_features5000是控制词表规模防止 65k 行 几十万标签导致矩阵过大。如果你发现结果里全是类型相似的换皮游戏可以手动调大min_df或给tags更大的权重。我在实际项目里还会把流行度和差评率作为后过滤条件避免推荐“极其相似但评价很差”的作品。6.2 新数据进来之后怎么维护一年后再看这份 2021-2025 数据集里面可能已经少了新上线的游戏。常见做法是写一个定时拉取脚本通过 Steam 官方开发者接口增量抓新增 app_id再把新数据concat到原表上进行清洗和去重。new_df pd.read_csv(steam_latest_2026.csv) merged pd.concat([df, new_df], ignore_indexTrue) merged merged.drop_duplicates(subset[app_id], keeplast) merged.to_parquet(steam_games.parquet, indexFalse)这里我会刻意把清洗后的结果存成 Parquet 而不是继续存 CSV因为 Parquet 支持类型、压缩比高随后的查询速度更快。如果后续要接入上层的 Web 服务再多一步to_sql写入 SQLite 或 PostgreSQL 即可。数据库导入 CSV 文件这个操作只有在数据量小、一次性分析时才建议直接使用一旦进入持续更新阶段还是让程序写库更可控。以前我拿到一个新数据集总急着跑模型后来发现特征里的时间、价格、空值全在骗人。现在我给自己定了一条规矩先画分布、后写 schema、再进模型。这份 2021-2025 Steam 游戏数据集正好是练手的好目标希望这些经验能帮到你。本文还有配套的精品资源点击获取
返回列表