我目前正在使用 Steam-crawler (https://github.com/aesuli/steam-crawler) 脚本,该脚本基于带有游戏 ID 的数据集,能够抓取 Steam 评论数据(日期、评论文本、用户 ID 等) ……)。我不是 HTML 抓取方面的专家,但根据我从代码中的理解(即下面的内容),该脚本会为给定的游戏 ID 循环以收集所有评论,直到遇到这种特定情况 endre = re.compile(r'({"success":2})|(no_more_reviews)')
。< /p>
import argparse
import csv
import os
import re
import socket
import string
import urllib
import urllib.request
import urllib.parse
import json
from contextlib import closing
from time import sleep
def download_page(url, maxretries, timeout, pause):
tries = 0
htmlpage = None
while tries < maxretries and htmlpage is None:
try:
with closing(urllib.request.urlopen(url, timeout=timeout)) as f:
htmlpage = f.read()
sleep(pause)
except (urllib.error.URLError, socket.timeout, socket.error):
tries += 1
return htmlpage
def getgameids(filename):
ids = set()
with open(filename, encoding='utf8') as f:
reader = csv.reader(f)
for row in reader:
dir = row[0] #à la base c'est 0
id_ = row[1]
name = row[2]
ids.add((dir, id_, name))
return ids
def getgamereviews(ids, timeout, maxretries, pause, out):
urltemplate = string.Template(
'https://store.steampowered.com//appreviews/$id?cursor=$cursor&filter=recent&language=english')
endre = re.compile(r'({"success":2})|(no_more_reviews)')
for (dir, id_, name) in ids:
if dir == 'sub':
print('skipping sub %s %s' % (id_, name))
continue
gamedir = os.path.join(out, 'pages', 'reviews', '-'.join((dir, id_)))
donefilename = os.path.join(gamedir, 'reviews-done.txt') #When all reviews of a given have been extracted
if not os.path.exists(gamedir): #Create a folder if not existing
os.makedirs(gamedir)
elif os.path.exists(donefilename): #if folder exists, skip game
print('skipping app %s %s' % (id_, name))
continue
print(dir, id_, name)
cursor = '*'
offset = 0
page = 1
maxError = 10
errorCount = 0
i = 0
while True:
url = urltemplate.substitute({'id': id_, 'cursor': cursor})
print(offset, url)
htmlpage = download_page(url, maxretries, timeout, pause)
if htmlpage is None:
print('Error downloading the URL: ' + url)
sleep(pause * 3)
errorCount += 1
if errorCount >= maxError:
print('Max error!')
break
else:
with open(os.path.join(gamedir, 'reviews-%s.html' % page), 'w', encoding='utf-8') as f:
htmlpage = htmlpage.decode()
if endre.search(htmlpage):
break
f.write(htmlpage)
page = page + 1
parsed_json = (json.loads(htmlpage))
cursor = urllib.parse.quote(parsed_json['cursor'])
with open(donefilename, 'w', encoding='utf-8') as f:
pass
def main():
parser = argparse.ArgumentParser(description='Crawler of Steam reviews')
parser.add_argument('-f', '--force', help='Force download even if already successfully downloaded', required=False,
action='store_true')
parser.add_argument(
'-t', '--timeout', help='Timeout in seconds for http connections. Default: 180',
required=False, type=int, default=180)
parser.add_argument(
'-r', '--maxretries', help='Max retries to download a file. Default: 5',
required=False, type=int, default=3)
parser.add_argument(
'-p', '--pause', help='Seconds to wait between http requests. Default: 0.5', required=False, default=0.01,
type=float)
parser.add_argument(
'-m', '--maxreviews', help='Maximum number of reviews per item to download. Default:unlimited', required=False,
type=int, default=5000000)
parser.add_argument(
'-o', '--out', help='Output base path', required=False, default='data')
parser.add_argument(
'-i', '--ids', help='File with game ids', required=False, default='./data/games.csv')
args = parser.parse_args()
if not os.path.exists(args.out):
os.makedirs(args.out)
ids = getgameids(args.ids)
print('%s games' % len(ids))
getgamereviews(ids, args.timeout, args.maxretries, args.pause, args.out)
if __name__ == '__main__':
main()
我目前面临的问题是脚本没有正确提取评论:例如,对于像 Counter-Strike Global Offensive 这样大约有大约 1,000,000 条评论的游戏,脚本有时会返回 4000 页评论数(每个 html 页面包含 20 条评论),6000 或 500 条直到停止!
我想象的解决方案可能是保存脚本测试的每个 URL,并在每次 endre = re.compile(r'({"success":2})|(no_more_reviews)')
为 True 时重复循环 10 次,并跳过已经收集的 URL,但是我不确定它真的有效吗?
我也会在 GitHub 页面上提出这个问题,但作者似乎并不经常回复,我真的很想知道它为什么会发生,以及是否有可能解决这个问题。提前致谢。
编辑:所以我查看了 Steam API 文档 https://partner.steamgames.com/doc/store/getreviews,似乎每个页面都提供了一个光标,以便能够加载下一个页面。那么为什么它会随机变化呢?
答案 0 :(得分:0)
Rate Limiting 就像网络服务器的一种基本自我防御形式。 Steam 使用它来帮助防止其服务器被滥用(过多)。抓取 500 多个完整的网页是大量浪费的带宽——更不用说 4k+ 页面了——其中的许多数据都是无用的,因为机器没有运行 JS 或无论如何都没有显示任何内容。 >
您共享的最后一个链接 seems to be an official endpoint 来执行此操作,但您可能需要专门处理状态代码 429 "Too Many Requests"。 Related Q & A 提到此状态应该带有 Retry-After
标头和可能的限制说明。根据 MDN docs,Retry-After 的值可能是日期或等待的秒数。
cursor
值可能是在服务器端生成的,仅用于标识 20 条评论,因为这是默认的“页面大小”。文档说这意味着与以下请求一起发送,以从列表中获取下一组评论。