diff options
| author | NW/RL <NWRL@dabikers.online> | 2024-04-02 14:31:33 -0500 |
|---|---|---|
| committer | NW/RL <NWRL@dabikers.online> | 2024-04-02 14:31:33 -0500 |
| commit | d6832a62fe0272ba725b3d5ceab64b5fb3a07276 (patch) | |
| tree | 7697835c9dd71b908c5167458f80e1a0df670a2d | |
| parent | 56bcf6615a51989dfbe4c7dbbb2d03b4af5f32fb (diff) | |
Implement request throttling
| -rw-r--r-- | steamrelationships/sr.py | 51 |
1 files changed, 24 insertions, 27 deletions
diff --git a/steamrelationships/sr.py b/steamrelationships/sr.py index 4bad716..1cf9c2e 100644 --- a/steamrelationships/sr.py +++ b/steamrelationships/sr.py | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | import requests | 1 | import requests |
| 2 | import json | 2 | import json |
| 3 | import time | 3 | import time |
| 4 | import random | ||
| 4 | 5 | ||
| 5 | from steamrelationships.constants import _Const | 6 | from steamrelationships.constants import _Const |
| 6 | CONST = _Const() | 7 | CONST = _Const() |
| @@ -8,33 +9,19 @@ CONST = _Const() | |||
| 8 | class SteamRelationships: | 9 | class SteamRelationships: |
| 9 | session: object = requests.Session() # Session object so repeated querries to steam's api use the same TCP connection | 10 | session: object = requests.Session() # Session object so repeated querries to steam's api use the same TCP connection |
| 10 | scanlist: dict = {} # To be populated by recursivescan() | 11 | scanlist: dict = {} # To be populated by recursivescan() |
| 11 | reqjson: str = "requests.json" # Path to the JSON file that handles requests | ||
| 12 | 12 | ||
| 13 | def __init__(self, webapikey: str, timeout: int = 30, timeout_retries: int = 5, reqdelay: float = 0.5, delayrand: float = 1.25, reqsafetybuffer: float = 0.9, reqjson: str = "requests.json") -> None: | 13 | def __init__(self, webapikey: str, timeout: int = 30, timeout_retries: int = 5, reqdelay: float = 0.5, delayrand: int = 25, reqsafetybuffer: float = 0.9, reqjson: str = "requests.json") -> None: |
| 14 | """ | 14 | """ |
| 15 | (str/int) webapikey - Steam dev api key required to use Steam's ISteamUser/GetFriendList interface | ||
| 16 | |||
| 17 | Request Options: | ||
| 18 | (int) timeout (Default: 30) - Seconds to wait before timing out a request. | ||
| 19 | (int) timeout_retries (Default: 5) Number of times to retry a request after timing out | ||
| 20 | (float) reqdelay (Default: 0.5) - Default amount of seconds to wait between sending each request to Steam | ||
| 21 | (float) delayrand (Default: 1.25) - The max percentage of extra delay to randomly add to each request (1 = no randomness, <1 = randomly shorter, >1 = randomly longer) | ||
| 22 | (float) reqsafetybuffer (Default: 0.9) - Highest percent of Steam's API request limit you are willing to run | ||
| 23 | > Steam has an API request limit of 100,000 requests per day. | ||
| 24 | reqsafetybuffer = 0.9 means SteamRequests will send a max of 90,000 requests per day. | ||
| 25 | Entering a number higher than 1 will not result in sending more than 100,000 requests per day | ||
| 26 | |||
| 27 | (str) reqjson (Default: "requests.json") - The name/filepath of the file to store request times in | ||
| 28 | """ # I can't make this a formatted string :sob: | 15 | """ # I can't make this a formatted string :sob: |
| 29 | 16 | ||
| 30 | self.webapikey = webapikey | 17 | # This used to be in a try-except block, but I decided that it should error out on a proper error |
| 31 | self.timeout = timeout | 18 | self.webapikey: str = str(webapikey) |
| 32 | self.timeout_retries = timeout_retries | 19 | self.timeout: int = int(abs(timeout)) |
| 33 | self.reqdelay = reqdelay | 20 | self.timeout_retries: int = int(abs(timeout_retries)) |
| 34 | self.delayrand = delayrand | 21 | self.reqdelay: float = float(abs(reqdelay)) |
| 35 | self.reqsafetybuffer = reqsafetybuffer | 22 | self.delayrand: int = int(abs(delayrand) % 100) |
| 36 | self.reqjson = reqjson | 23 | self.reqsafetybuffer: float = float(abs(reqsafetybuffer)) |
| 37 | 24 | self.reqjson: str = str(reqjson) | |
| 38 | 25 | ||
| 39 | 26 | ||
| 40 | def _readjsonfile(self, filepath: str = "") -> dict: | 27 | def _readjsonfile(self, filepath: str = "") -> dict: |
| @@ -80,7 +67,7 @@ class SteamRelationships: | |||
| 80 | jsoncontents = self._readjsonfile(filename) | 67 | jsoncontents = self._readjsonfile(filename) |
| 81 | 68 | ||
| 82 | except Exception as e: | 69 | except Exception as e: |
| 83 | print(f"[_checkrequests] Could not get the contents of file {filename} ({e})") | 70 | print(f"[_checkrequests] Could not get the contents of file \"{filename}\" ({e})") |
| 84 | return -1 | 71 | return -1 |
| 85 | 72 | ||
| 86 | # Check the current date. If over 1 day since last entry, add a new entry. Otherwise, edit the current day's entry [note, 1 day in nanoseconds = (8.64 * (10 ** 13)) ] | 73 | # Check the current date. If over 1 day since last entry, add a new entry. Otherwise, edit the current day's entry [note, 1 day in nanoseconds = (8.64 * (10 ** 13)) ] |
| @@ -96,7 +83,7 @@ class SteamRelationships: | |||
| 96 | jsoncontents[list(jsoncontents.keys())[-1]][1].append(checktime) | 83 | jsoncontents[list(jsoncontents.keys())[-1]][1].append(checktime) |
| 97 | 84 | ||
| 98 | else: | 85 | else: |
| 99 | print(f"[_checkrequests] Daily request limit reached ({jsoncontents[list(jsoncontents.keys())[-1]][0]}/{CONST.STEAMAPI_MAXREQ * self.reqsafetybuffer}). Please try again tomorrow, or increase \"reqsafetybuffer\" (currently: {self.reqsafetybuffer})") | 86 | print(f"[_checkrequests] Daily request limit reached ({currentreqs}/{CONST.STEAMAPI_MAXREQ * self.reqsafetybuffer}). Please try again tomorrow, or increase \"reqsafetybuffer\" (currently: {self.reqsafetybuffer})") |
| 100 | return 0 | 87 | return 0 |
| 101 | 88 | ||
| 102 | # Update the json file | 89 | # Update the json file |
| @@ -106,7 +93,7 @@ class SteamRelationships: | |||
| 106 | jsonfile.close() | 93 | jsonfile.close() |
| 107 | 94 | ||
| 108 | except Exception as e: | 95 | except Exception as e: |
| 109 | print("[_checkrequests] Could not update json file ({e})") | 96 | print(f"[_checkrequests] Could not update json file ({e})") |
| 110 | return -1 | 97 | return -1 |
| 111 | 98 | ||
| 112 | return jsoncontents[list(jsoncontents.keys())[-1]][0] | 99 | return jsoncontents[list(jsoncontents.keys())[-1]][0] |
| @@ -146,7 +133,11 @@ class SteamRelationships: | |||
| 146 | 133 | ||
| 147 | # Error out on request error | 134 | # Error out on request error |
| 148 | if result.status_code != requests.codes.ok: | 135 | if result.status_code != requests.codes.ok: |
| 149 | print(f"[_getFriendsList] Got bad status code (Requested id: {steamid64}, status: {result.status_code})") | 136 | print(f"[_getFriendsList] Got bad status code (Requested id: {steamid64}, status: {result.status_code})", end="") |
| 137 | if result.status_code == 401: # Steam returns a 401 on profiles with private friends lists | ||
| 138 | print(f". It is likely that the requested id has a private profile / private friends list", end="") | ||
| 139 | |||
| 140 | print("\n", end="") | ||
| 150 | return {} | 141 | return {} |
| 151 | 142 | ||
| 152 | # Get the json contents from the response | 143 | # Get the json contents from the response |
| @@ -222,6 +213,9 @@ class SteamRelationships: | |||
| 222 | nature of friendship relations, you will very quickly spam Steam with tens of thousands of requests. | 213 | nature of friendship relations, you will very quickly spam Steam with tens of thousands of requests. |
| 223 | If this concept is unfamiliar to you, please take a quick glance at the Wikipedia page for | 214 | If this concept is unfamiliar to you, please take a quick glance at the Wikipedia page for |
| 224 | "six degress of separation": https://en.wikipedia.org/wiki/Six_degrees_of_separation | 215 | "six degress of separation": https://en.wikipedia.org/wiki/Six_degrees_of_separation |
| 216 | |||
| 217 | |||
| 218 | TLDR: recursivescan is exponential and you will reach 100,000 requests very quickly if you recurse greater than 3 | ||
| 225 | ''' | 219 | ''' |
| 226 | 220 | ||
| 227 | testlist: dict = self.basicscan(steamid64) | 221 | testlist: dict = self.basicscan(steamid64) |
| @@ -234,6 +228,9 @@ class SteamRelationships: | |||
| 234 | if friend not in alreadyscanned: | 228 | if friend not in alreadyscanned: |
| 235 | tempdict.update(self.basicscan(friend)) | 229 | tempdict.update(self.basicscan(friend)) |
| 236 | alreadyscanned.append(friend) | 230 | alreadyscanned.append(friend) |
| 231 | |||
| 232 | sleepytime: float = abs(random.randrange(100 - self.delayrand, 100 + self.delayrand) / 100 * self.reqdelay) | ||
| 233 | time.sleep(sleepytime) | ||
| 237 | 234 | ||
| 238 | testlist.update(tempdict) | 235 | testlist.update(tempdict) |
| 239 | tempdict.clear() | 236 | tempdict.clear() |
