
前阵子给一个做独立游戏的朋友帮忙,他想搞清楚 Steam 上到底什么类型的游戏卖得动、玩家到底在骂什么夸什么。市面上能搜到的分析报告要么太宏观,要么就是靠感觉拍脑袋写的。我决定自己动手,把数据爬下来看个究竟。
先说踩了哪些坑
一开始我打算直接从 Steam 商店页面爬,心想就解析个 HTML 的事。结果 Steam 的反爬比我想的硬。
Steam 的商店页面有好几层防护。首先是请求频率限制,连续请求太快会直接返回空数据,没有任何提示。其次,某些页面需要带着 Steam 的 cookie 才能拿到完整数据,否则返回的是一个精简版,里面的评论数量、标签权重都是缺的。最关键的是 Steam 有"愿望单游戏数"和"评论语言偏好"两个接口走的是内嵌 JS 渲染,直接拿 HTML 根本看不到。
这里说一下我的实际抓取逻辑,花了大概两天把整个流程跑通。
正式开始之前,先解决一个问题:怎么保证请求不被 Steam 封 IP。毕竟我要连续跑上千次请求,单 IP 在这么高频的访问下很容易被拉黑。这次我用了亿牛云的动态转发代理,每次请求自动切换出口 IP,比手动维护代理池省心太多了。
先配好代理入口,后面的请求全部走这个通道:
# 亿牛云动态转发代理配置
# 使用动态转发模式,每次请求自动从代理池中分配一个可用 IP
# 代理格式为「订单号:密码@代理域名:端口」,由亿牛云后台提供
PROXY_HOST = "<填入亿牛云动态转发代理的订单地址>"
PROXIES = {
"http": PROXY_HOST,
"https": PROXY_HOST,
}
这里的关键是用了动态转发而不是固定 IP 代理。固定 IP 的问题是请求量一上来 Steam 照样能识别,毕竟几千次请求全从同一个出口出去,很难不被注意。动态转发每请求一次换一个 IP,四小时跑下来 Steam 那边看到的是几千个不同 IP 各访问了一两次,风控阈值压根没触发。
第一步:获取畅销榜列表
Steam 热门榜单有一个返回 JSON 的接口,不需要登录。请求时带上 cc=CN 参数可以切换国区数据,默认是全球(美元区)。我测了一下,国区前 50 和全球前 50 的重合度不到 60%,国产独立游戏在国区明显更靠前。请求方式就是一个普通的 GET,带上上面配好的 proxies=PROXIES 即可,代码不复杂就不单独贴了。
这里顺带说一句:Steam 能走 API 就尽量走 API,JSON 比解析 HTML 干净得多。榜单和评论都有现成接口,只有需要登录才能看的页面才考虑上浏览器自动化。
第二步:获取每个游戏的评论
评论数据走了 Steam 的社区 API,接口形式是「评论接口地址加上游戏 ID」。这里有两个容易被忽略的细节。
第一,这个接口默认只返回英文评论,要拿中文评论需要在参数里指定 language=schinese。我就是吃了这个亏,第一轮全跑完发现拿回来的全是英文数据,白白浪费了两个小时。中英文数据一定要分开抓、分开分析,否则拿到的结论大概率不准。反过来,如果你做的是全球化分析,中英文评论的差异本身就是一个可以深挖的维度。
第二,Steam 评论接口的 cursor 分页实现很不稳定。我翻到第 8 页就突然中断过好几次,返回空列表但 success 还是 1。后来把每页请求数从默认 20 调到 100,同时加了重试逻辑,如果某次返回的 review 数量小于预期就等 3 秒再试。
请求间隔我设的 1.5 秒,配合亿牛云代理的自动 IP 轮换,74 款游戏连续跑了 4 个小时没有触发任何风控。同时跑多个游戏时千万不要并行请求同一个接口,宁可慢一点。核心代码逻辑如下:
def get_reviews(appid, num_reviews=100):
"""抓取指定游戏的玩家评论,支持 cursor 分页"""
reviews = []
cursor = "*"
while len(reviews) < num_reviews:
# 评论接口地址需拼接游戏 ID,此处用占位符表示
url = f"<填入 Steam 评论接口地址,需拼接游戏 ID>"
params = {
"json": 1,
"filter": "all",
"language": "schinese", # 指定中文评论,避免默认返回英文
"day_range": 365, # 只拉取近一年的评论
"review_type": "all",
"purchase_type": "all",
"num_per_page": 100, # 每页 100 条,减少分页请求次数
"cursor": cursor,
}
# 所有请求走亿牛云代理,防止高频抓取被 Steam 封 IP
resp = requests.get(url, params=params, proxies=PROXIES, timeout=15)
data = resp.json()
if data["success"] != 1:
break
batch = data.get("reviews", [])
reviews.extend(batch)
# 如果本页返回的数据量明显异常,等待后重试
if len(batch) < 20 and cursor != "*":
time.sleep(3)
continue
cursor = data.get("cursor", "")
if not cursor:
break
time.sleep(1.5) # 请求间隔,配合代理 IP 轮换避免触发限流
return reviews
第三步:拿到足够多的数据
最终我爬了 Steam 全球畅销榜前 50 款游戏和国区畅销榜前 50 款游戏(去重后共 74 款),每款游戏抓了中文评论和英文评论各 50 到 100 条,总计接近 7400 条评论。另外还拉取了每款游戏的标签数据、好评率和近期在线人数。单次请求间隔 1.5 秒,整批数据跑了大概 4 个小时,没有触发封禁。
数据跑完以后,几个让我意外的发现
数据出来以后,有几个结论和我的直觉完全相反。
一、模拟经营类正在吃掉 RPG 的份额
在畅销榜前 50 里,RPG 和动作游戏依然数量最多,这不意外。但对比近两年的评论情绪,模拟经营和生存建造类的玩家满意度明显高于传统 RPG。拿两款知名沙盒游戏来说,近期好评率稳定在 96% 以上,而几款大作 RPG 普遍在 78% 到 84% 之间晃。
更关键的是评论内容本身。我把近 3000 条中文评论做了一遍关键词频次分析,发现"自由度""创造""规划"这几个词的出现频率在过去一年涨了将近 40%,而"剧情""角色""养成"在下降。玩家嘴上说着要剧情,钱包投给了自由沙盒。
二、中国玩家对价格比想象中更敏感
对比中英文评论,一个很明显的差异是"价格""打折""值不值"这些词在中文评论里的出现频率是英文评论的 2.6 倍。国区玩家一条评论里同时夸游戏好玩但又说"建议等打折"的占比非常高。
还有一个有趣的点:国区玩家很少因为"游戏太简单"给差评,但会因为"游戏太难"给差评的比例是英文区的 1.8 倍。很难说是玩家群体的差异还是什么别的原因,但做游戏出海的公司可能需要把这个因素考虑进去。
三、"社群"正在变成购买决策的核心因素
这是我没想到的。在"你为什么买这款游戏"的评论里,"朋友推荐""看主播玩的""群里安利"之类的社交渠道占比超过了 20%。这在 2023 年大概是不到 10% 的水平。Steam 的评测区本身也变成了一个内容社区,不少玩家写千字长评,写得比游戏媒体还认真。
从爬虫的角度来说,这意味着单纯抓销量和评分已经不够了。评论的文本内容、回复的互动量、评测者的社区活跃度,这些才是理解玩家真实偏好的关键维度。
以上就是这次 Steam 数据爬取和分析的完整记录。最后想说一句:爬虫只是敲门砖,这个项目最花时间的不是写代码抓数据,是数据清洗、关键词提取和分析,真正有价值的部分在于你能从数据里看到别人没看到的东西。不是什么方法论式的大作,就是一个爬虫工程师用周末做的小项目。如果你也在做游戏相关的数据调研,希望里面的爬取方案和分析结论能帮你省一些时间。