拒绝无脑刷好评!用Python爬取Steam真实数据,手搓一套“防踩雷”游戏推荐系统
摘要:还在被“好评如潮”的标签骗?本文带你从零搭建一套基于Steam真实数据的自动化游戏推荐工具。不讲虚的,直接上代码:从热销榜单爬取、API详情补全、加权评分算法设计到最终推荐列表生成,全流程可复现。适合有一定Python基础、想练手爬虫+数据分析的新手开发者。
一、为什么我要自己写这套推荐逻辑?
作为一个打了十几年游戏的“数据党”,我发现一个很尴尬的现实:Steam商店页的好评率越来越“失真”。
- 某3A大作首发差评轰炸,后续修好了但历史评价拖后腿;
- 小众独立游戏好评率98%,但评测数只有200条,样本量根本不够;
- “好评如潮”里混杂了大量跑题差评(比如因为锁区、涨价、没中文),跟游戏质量本身无关。
市面上的推荐要么是纯算法黑盒,要么就是简单的按热度排序。我想做的很简单:把“评价数量”、“近期口碑趋势”、“跑题过滤”这些因子揉进一个透明的加权公式里,生成一份我自己信得过的推荐清单。
这篇文章不是教你当“爬虫黑客”,而是分享如何用工程化思维解决一个具体的信息筛选问题。所有技术手段均基于Steam公开接口与页面结构,遵守robots协议,仅用于个人学习研究。
二、整体架构:数据从哪来,到哪去?
在动手写代码前,先理清数据流。很多新手上来就requests.get,结果爬到一半发现字段缺失、频率被封,就是因为缺了顶层设计。
核心思路是“两步走”:
- 第一步(轻量):爬取热销/新品榜单页,只拿AppID和基础排名。这个页面结构简单,反爬较弱。
- 第二步(精准):用AppID调用Steam Store API (
appdetails) 获取结构化JSON数据。相比硬爬商店详情页,API返回的数据干净、稳定,且包含total_positive/total_negative等关键字段。
⚠️关键认知:不要试图用BeautifulSoup去解析Steam商店详情页的评价区域!那是动态渲染+AJAX加载的泥潭。能用API解决的,绝不碰DOM解析。
三、环境准备与依赖安装
本项目对新手友好,不需要Scrapy那种重型框架。确保你的Python版本 ≥ 3.8,然后装这几个库就够了:
pipinstallrequests beautifulsoup4 pandas tqdm fake-useragent| 库 | 用途 | 备注 |
|---|---|---|
requests | HTTP请求主力 | 支持Session复用 |
beautifulsoup4 | 解析榜单页HTML | 仅用于第一步 |
pandas | 数据处理与导出 | 推荐列表生成神器 |
tqdm | 进度条显示 | 爬几百个AppID时心态不崩 |
fake-useragent | 随机UA头 | 基础反反爬手段 |
四、核心代码实现(可直接运行)
4.1 第一步:从热销榜获取AppID列表
我们选择全球热销榜作为入口,URL格式为:https://store.steampowered.com/search/?filter=globaltopsellers&page={page}
importrequestsfrombs4importBeautifulSoupfromfake_useragentimportUserAgentimporttimeimportrandomdefget_top_seller_appids(pages=3):""" 爬取Steam全球热销榜前N页的AppID 返回去重后的AppID列表 """appids=[]ua=UserAgent()headers={'Accept-Language':'zh-CN,zh;q=0.9,en;q=0.8',# Steam对中文请求更友好,减少触发验证概率}session=requests.Session()forpageinrange(1,pages+1):url=f"https://store.steampowered.com/search/?filter=globaltopsellers&page={page}"headers['User-Agent']=ua.randomtry:resp=session.get(url,headers=headers,timeout=15)resp.raise_for_status()soup=BeautifulSoup(resp.text,'html.parser')# 热销榜每个游戏条目都有>=soup.select('a.search_result_row[data-ds-appid]')forrowinrows:appid=row.get('data-ds-appid')ifappidandappid.isdigit():appids.append(int(appid))print(f"[✓] 第{page}页获取{len(rows)}个AppID")# 礼貌延迟:2~4秒随机间隔time.sleep(random.uniform(2,4))exceptExceptionase:print(f"[✗] 第{page}页请求失败:{e}")continuereturnlist(dict.fromkeys(appids))# 保序去重细节说明:
- 使用
Session()复用TCP连接,比单次get快30%以上; data-ds-appid是Steam前端组件绑定的属性,比解析href链接更稳定;- 随机延迟不是摆设,Steam对短时间高频请求会返回429或验证码页面。
4.2 第二步:通过Store API补全游戏详情
拿到AppID后,调用官方API获取结构化数据。这是整个项目最关键的环节。
importjsondeffetch_game_details(appids,batch_size=20):""" 批量获取游戏详情 API文档: https://wiki.teamfortress.com/wiki/WebAPI/GetAppDetails """results=[]base_url="https://store.steampowered.com/api/appdetails"# 分批处理,避免单次请求过大foriinrange(0,len(appids),batch_size):batch=appids[i:i+batch_size]forappidinbatch:params={'appids':appid,'l':'schinese',# 强制中文返回'cc':'cn',# 中国区定价'filters':'basic,categories,genres,reviews'# 只取需要的字段}try:resp=requests.get(base_url,params=params,timeout=10)data=resp.json()# API返回格式: {"appid": {"success": bool, "data": {...}}}key=str(appid)ifkeyindataanddata[key].get('success'):info=data[key]['data']results.append({'appid':appid,'name':info.get('name',''),'price':info.get('price_overview',{}).get('final',0)/100,'positive':info.get('positive',0),# 好评数'negative':info.get('negative',0),# 差评数'review_score':info.get('review_score',0),'review_desc':info.get('review_desc',''),'tags':[g['description']forgininfo.get('genres',[])],'release_date':info.get('release_date',{}).get('date','')})else:print(f" [!] AppID{appid}无数据或被下架")exceptExceptionase:print(f" [✗] AppID{appid}请求异常:{e}")# API限流较严格,每次请求间隔1~2秒time.sleep(random.uniform(1,2))print(f"[✓] 已完成{min(i+batch_size,len(appids))}/{len(appids)}")returnresults⚠️ 踩坑预警:
appdetails接口虽然不需要API Key,但有隐性频率限制。务必加延迟,否则IP会被临时封禁15~30分钟;filters参数一定要加!不加的话会返回几MB的完整数据(含截图、视频、DLC等),既慢又容易超时;- 价格字段单位是“分”,需要除以100才是元;
- 部分免费游戏或测试版没有
price_overview字段,要用.get()兜底。
4.3 第三步:加权推荐算法(核心灵魂)
数据拿到了,怎么排序?直接用好评率?那200条评价的98%好评会把10万条评价的90%神作挤下去。
我设计了一个贝叶斯平滑 + 时间衰减的混合评分公式:
S c o r e = p o s i t i v e + m × p 0 t o t a l + m × log 10 ( t o t a l + 1 ) × r e c e n c y _ f a c t o r Score = \frac{positive + m \times p_0}{total + m} \times \log_{10}(total + 1) \times recency\_factorScore=total+mpositive+m×p0×log10(total+1)×recency_factor
其中:
- m mm是最小样本量阈值(设为500),低于此权重的游戏会被拉向全局平均好评率p 0 p_0p0;
- log ( t o t a l + 1 ) \log(total+1)log(total+1)是对评价数量的对数奖励,避免线性膨胀;
- r e c e n c y _ f a c t o r recency\_factorrecency_factor是近期口碑修正系数(可通过评论API的recent filter单独计算,简化版可先置1)。
importpandasaspdimportnumpyasnpdefbuild_recommendation(df,min_reviews=100,m=500):""" 构建推荐评分并排序 """# 过滤掉评价数过少的噪音数据df=df[df['positive']+df['negative']>=min_reviews].copy()total=df['positive']+df['negative']global_avg=df['positive'].sum()/total.sum()# 全局基准好评率# 贝叶斯平滑好评率smoothed_rate=(df['positive']+m*global_avg)/(total+m)# 对数数量奖励volume_bonus=np.log10(total+1)# 综合得分df['recommend_score']=smoothed_rate*volume_bonus# 降序排列df=df.sort_values('recommend_score',ascending=False).reset_index(drop=True)# 添加可读性列df['actual_rate']=(df['positive']/total*100).round(1)df['rank']=range(1,len(df)+1)returndf这个公式解决了什么?
- 新游戏评价少 → 被m mm拉向均值,不会虚高;
- 老游戏评价多但口碑下滑 → 平滑率下降,对数奖励无法弥补;
- 真正优质且有一定体量的游戏 → 两项乘积最大化,自然排前面。
你可以在此基础上加入价格权重(比如性价比因子)、标签偏好过滤等个性化维度。
4.4 第四步:输出推荐列表
defexport_results(df,top_n=30):"""导出Top N推荐列表"""output=df.head(top_n)[['rank','name','actual_rate','positive','negative','price','tags','recommend_score']]# 控制台预览print("\n"+"="*80)print(f"🎮 Steam 智能推荐 Top{top_n}(生成时间:{pd.Timestamp.now():%Y-%m-%d%H:%M})")print("="*80)for_,rowinoutput.iterrows():tags_str=', '.join(row['tags'][:3])print(f"{row['rank']:>2}.{row['name']:<30}"f"好评率:{row['actual_rate']}% | "f"¥{row['price']:.0f}| [{tags_str}]")print("="*80)# 保存CSV供后续分析output.to_csv('steam_recommendations.csv',index=False,encoding='utf-8-sig')print("✅ 已保存至 steam_recommendations.csv")五、运行效果与数据解读
将上述模块串联起来:
if__name__=='__main__':print("📡 Step 1: 获取热销榜AppID...")appids=get_top_seller_appids(pages=5)print(f"共获取{len(appids)}个有效AppID\n")print("📡 Step 2: 拉取游戏详情...")games=fetch_game_details(appids)df=pd.DataFrame(games)print(f"成功获取{len(df)}款游戏数据\n")print("🧮 Step 3: 计算推荐评分...")result=build_recommendation(df)print("📋 Step 4: 生成推荐列表...")export_results(result,top_n=20)实际运行后你会看到类似这样的输出(示例数据):
1. Baldur's Gate 3 好评率:96.2% | ¥298 | [角色扮演,策略,奇幻] 2. Hades II 好评率:94.8% | ¥108 | [动作,roguelike,神话] 3. Dave the Diver 好评率:97.1% | ¥88 | [模拟,像素,海洋] ...注意观察:某些好评率98%但评价数仅300条的游戏被压到了20名开外,而一些好评率92%但拥有5万+评价的长青作品稳居前列。这正是算法在起作用。
六、进阶优化方向与合规提醒
可以继续深挖的点
- 接入评论时序API:用
GetReviews接口的recent过滤器计算近30天好评率,与历史好评率做差值,识别“口碑回暖”或“暴雷”趋势; - 标签协同过滤:对用户打标签的行为做矩阵分解,实现“喜欢A的人也喜欢B”;
- 定时任务+推送:配合APScheduler每日更新,结果推送到微信/钉钉;
- 可视化看板:用Streamlit或Gradio搭个Web界面,交互式筛选。
⚖️ 合规与道德底线
这一点必须强调:
- 遵守robots.txt:Steam允许爬取商店页面用于非商业用途,但禁止高频抓取;
- 不使用用户隐私数据:我们只抓公开的商店元数据和聚合评价统计,不碰个人评论文本、账号信息;
- 控制请求频率:单IP建议不超过每秒1次API调用,榜单页间隔≥2秒;
- 仅供学习研究:不得将爬取数据用于商业竞品分析、倒卖或训练对外服务的模型;
- 尊重平台ToS:如果Steam未来关闭某个接口或明确禁止某种访问方式,应立即停止。
七、写在最后
这个项目麻雀虽小,五脏俱全:网页解析、API对接、数据清洗、算法设计、结果输出,覆盖了数据工程的核心链路。比起跟着教程抄一个“豆瓣电影Top250”爬虫,它多了真实业务场景下的权衡取舍——什么时候用API代替爬虫、怎么处理脏数据、如何让排序结果“符合直觉又不违背数学”。
代码已经过实测可用,但Steam页面结构和API行为可能随时调整。建议你把它当作一个起点,而不是终点。遇到报错时打开浏览器F12看看网络请求,读一读API文档的变更日志,这种debug能力比代码本身更有价值。
如果你跑通了或者有改进想法,欢迎在评论区交流。下次我们可以聊聊怎么用这套数据做一个“史低价格预测器”。
免责声明:本文所述技术方案仅用于编程学习与数据分析研究,不构成任何商业建议。请严格遵守Steam平台服务条款及相关法律法规,合理控制访问频率,尊重数据所有权与用户隐私。因不当使用造成的任何后果由使用者自行承担。