# GRAPHS ARE AT THE BOTTOM!
# WARNING: The time numbers are from https://twitchtracker.com/quin69/games and the game sizes are
# a rough estimate based on the depot (all the files that are distributed via Steam's CDN for those games)
# file size for the game's files from https://steamdb.info!
#
# Some game size data might be innacurately scraped, but the vast majority should be correct.
import requests
import fake_useragent
session = requests.session()
session.headers["User-Agent"] = fake_useragent.FakeUserAgent().random
import bs4
response = session.get("https://twitchtracker.com/quin69/games")
parsed = bs4.BeautifulSoup(response.text, "lxml")
import pprint
import typing as T
# In minutes
game_to_airtime: T.Dict[str, int] = {}
for row in parsed.select("#games tbody tr"):
columns = row.select("td")
number, game, avg_viewers, max_viewers, followers, total_airtime, last_seen = columns
game_string = game.get_text(separator=" ", strip=True)
total_airtime_string = total_airtime.get_text(separator=" ", strip=True)
game_to_airtime[game_string] = int(total_airtime_string.split(None, 1)[0])
pprint.pprint(game_to_airtime)
{'A Chair in a Room: Greenwater': 245,
'AFFECTED: The Manor': 40,
'Accounting': 270,
'Amnesia: Rebirth': 255,
'Arizona Sunshine': 140,
'Ashes of Creation': 130,
"Baldur's Gate 3": 665,
'Beat Saber': 1253,
'Besiege': 435,
'Blade & Sorcery': 110,
'Bless Online': 614,
'Bloodborne': 2435,
'Bloons TD 6': 4850,
'Borderlands 3': 30,
'Cuphead': 885,
'Cyberpunk 2077': 970,
'DOOM Eternal': 2020,
'Dark Souls': 2903,
'Dark Souls II: Scholar of the First Sin': 3658,
'Dark Souls III': 5293,
'Darkest Dungeon': 7341,
"Demon's Souls": 2163,
'Destiny 2': 1679,
'Diablo': 710,
'Diablo II: Lord of Destruction': 1761,
'Diablo III': 56460,
'Diablo IV': 2074,
'Diablo Immortal': 200,
'Divinity: Original Sin II': 2993,
"Don't Knock Twice": 120,
'Dreadhalls': 130,
'Duck Season': 75,
'Exanima': 480,
'Fall Guys: Ultimate Knockout': 795,
'Fallout 4': 630,
'Final Fantasy VII Remake': 2887,
'GeoGuessr': 70,
'Getting Over It with Bennett Foddy': 1110,
'God of War': 459,
'Godfall': 215,
'Green Hell': 1580,
'Grim Dawn': 747,
'Grounded': 110,
'Hades': 3670,
'Half-Life: Alyx': 1451,
'Heroes of the Storm': 200,
'Hot Dogs, Horseshoes & Hand Grenades': 75,
'I Wanna Be The Boshy': 135,
'IRL': 395,
'In Your Face': 275,
'Jump King': 90,
'Just Chatting': 16362,
'LOST ARK': 14125,
'Last Epoch': 2055,
'Limbo': 295,
'Lineage 2: Revolution': 630,
'Little Misfortune': 250,
'Microsoft Flight Simulator': 120,
'Minecraft': 9239,
'Mortal Shell': 979,
'Mount & Blade II: Bannerlord': 295,
'New World': 1090,
'Oops!!! I Slept With Your Mom': 30,
'Papers, Please': 90,
'Paranormal Activity: The Lost Soul': 225,
'Path of Exile': 222300,
'Poly Bridge 2': 170,
'Portal': 230,
'Portal 2': 540,
'Risk of Rain 2': 105,
'SUPERHOT VR': 80,
'Sekiro: Shadows Die Twice': 3399,
'Slay the Spire': 1795,
'Special Events': 1521,
'Stilt Fella': 165,
'Subnautica': 2520,
'Superliminal': 175,
'Terraria': 13716,
'The Elder Scrolls V: Skyrim': 1748,
'The Exorcist: Legion VR': 295,
'The Stanley Parable Mod': 200,
'Totally Accurate Battle Simulator': 265,
'Transference': 60,
'Unknown': 90,
'VRChat': 1045,
'Valheim': 1486,
'Warcraft III: Reforged': 115,
'Warhammer: Chaosbane': 295,
'Welcome to the Game II': 2460,
'Who Wants To Be A Millionaire': 995,
'Who Wants to Be a Millionaire?': 70,
'Wolcen: Lords of Mayhem': 6236,
'World of Warcraft': 353067,
'World of Warcraft: Battle for Azeroth': 30}
# Have to use a webdriver because SteamDB isn't scraping friendly (CloudFlare checks)
import time
import urllib.parse
import selenium.webdriver
import selenium.common
SEARCH_URL_TEMPLATE = "https://steamdb.info/search/?a=app&q={}&type=1&category=0"
webdriver = selenium.webdriver.Chrome()
def find_steamdb_url(webdriver: selenium.webdriver.Remote, name: str) -> T.Optional[str]:
url = SEARCH_URL_TEMPLATE.format(urllib.parse.quote_plus(name))
webdriver.get(url)
try:
first_row = webdriver.find_element_by_css_selector("tr.app")
except selenium.common.exceptions.NoSuchElementException:
return None
anchor = first_row.find_element_by_tag_name("a")
return urllib.parse.urljoin(webdriver.current_url, anchor.get_attribute("href"))
def get_depot_url(steamdb_url: str) -> str:
return steamdb_url.rstrip("/") + "/depots/"
def get_total_depot_size(webdriver: selenium.webdriver.Remote, depot_url: str) -> int:
webdriver.get(depot_url)
# Wait for page to load in a naive fashion
time.sleep(3)
total_size = 0
for row in webdriver.find_elements_by_css_selector("#depots .table-responsive:first-of-type tbody tr"):
columns = row.find_elements_by_tag_name("td")
id_, name, max_size, os_, extra_info = columns
total_size += int(max_size.get_attribute("data-sort"))
return total_size
# Feel free to scrape it again, I don't want to
if False:
# In bytes
game_to_size: T.Dict[str, int] = {name: 0 for name in game_to_airtime}
for index, (name, hours_played) in enumerate(game_to_airtime.items(), start=1):
steamdb_url = find_steamdb_url(webdriver, name)
if not steamdb_url:
print(f"({index}/{len(game_to_airtime)})", name, "not found on SteamDB!")
continue
depot_url = get_depot_url(steamdb_url)
depot_size = get_total_depot_size(webdriver, depot_url)
print(f"({index}/{len(game_to_airtime)})", name, steamdb_url, depot_size)
game_to_size[name] = depot_size
else:
game_to_size = {
"Path of Exile": 29589377891,
"Diablo III": 5981923060,
"Just Chatting": 26848396,
"LOST ARK": 2654020720,
"Terraria": 1847376272,
"Minecraft": 14307586923,
"Darkest Dungeon": 12066824156,
"Wolcen: Lords of Mayhem": 109170195059,
"Dark Souls III": 27630425036,
"Bloons TD 6": 2123322021,
"Hades": 27011131823,
"Dark Souls II: Scholar of the First Sin": 38751409296,
"Sekiro: Shadows Die Twice": 16108432532,
"Divinity: Original Sin II": 97066890477,
"Dark Souls": 7813699112,
"Final Fantasy VII Remake": 31346367083,
"Subnautica": 20859143166,
"Welcome to the Game II": 5767667201,
"Demon's Souls": 79403560322,
"Diablo IV": 6881450057,
"Last Epoch": 49970534162,
"DOOM Eternal": 60026510027,
"Slay the Spire": 2360246842,
"Diablo II: Lord of Destruction": 10614589615,
"The Elder Scrolls V: Skyrim": 29864781871,
"Destiny 2": 76234637928,
"Green Hell": 9892099952,
"Half-Life: Alyx": 12131350186,
"Beat Saber": 752689827,
"Getting Over It with Bennett Foddy": 5231878019,
"New World": 35220626108,
"VRChat": 771268527,
"Who Wants To Be A Millionaire": 6755493072,
"Mortal Shell": 0,
"Cyberpunk 2077": 117759623745,
"Cuphead": 25009954846,
"Fall Guys: Ultimate Knockout": 5663789458,
"Grim Dawn": 10383299063,
"Diablo": 333962365,
"Baldur's Gate 3": 177857008731,
"Lineage 2: Revolution": 8262115020,
"Fallout 4": 53548454638,
"Bless Online": 44348434393,
"Portal 2": 357179213,
"Exanima": 4429205180,
"God of War": 1186924328,
"Besiege": 9883393946,
"Warhammer: Chaosbane": 58238753224,
"Limbo": 3206400933,
"Mount & Blade II: Bannerlord": 65992170893,
"The Exorcist: Legion VR": 4987498428,
"In Your Face": 329645151,
"Accounting": 2302197925,
"Totally Accurate Battle Simulator": 9794291046,
"Amnesia: Rebirth": 34914707637,
"Little Misfortune": 19764855934,
"A Chair in a Room: Greenwater": 4162988589,
"Portal": 357179213,
"Paranormal Activity: The Lost Soul": 6176413889,
"Godfall": 284533973,
"Heroes of the Storm": 2082985997,
"Diablo Immortal": 268337008,
"The Stanley Parable Mod": 925170085,
"Superliminal": 34755598440,
"Poly Bridge 2": 1812541029,
"Stilt Fella": 395362514,
"Arizona Sunshine": 28572819379,
"I Wanna Be The Boshy": 486910471,
"Ashes of Creation": 6777024339,
"Dreadhalls": 1013220341,
"Microsoft Flight Simulator": 1539007973,
"Don't Knock Twice": 2614746810,
"Warcraft III: Reforged": 5981923060,
"Blade & Sorcery": 8206475119,
"Grounded": 4591337959,
"Risk of Rain 2": 3307273090,
"Jump King": 1782741399,
"Unknown": 60974180387,
"Papers, Please": 204661900,
"SUPERHOT VR": 6628592425,
"Duck Season": 11868750058,
"Hot Dogs, Horseshoes & Hand Grenades": 15421675716,
"Who Wants to Be a Millionaire?": 6755493072,
"Transference": 11227835772,
"AFFECTED: The Manor": 3690441537,
"Oops!!! I Slept With Your Mom": 181920004,
"World of Warcraft: Battle for Azeroth": 8302308185,
"Borderlands 3": 128964722965,
"Valheim": 2843296311,
}
print()
pprint.pprint(game_to_size)
{'A Chair in a Room: Greenwater': 4162988589,
'AFFECTED: The Manor': 3690441537,
'Accounting': 2302197925,
'Amnesia: Rebirth': 34914707637,
'Arizona Sunshine': 28572819379,
'Ashes of Creation': 6777024339,
"Baldur's Gate 3": 177857008731,
'Beat Saber': 752689827,
'Besiege': 9883393946,
'Blade & Sorcery': 8206475119,
'Bless Online': 44348434393,
'Bloons TD 6': 2123322021,
'Borderlands 3': 128964722965,
'Cuphead': 25009954846,
'Cyberpunk 2077': 117759623745,
'DOOM Eternal': 60026510027,
'Dark Souls': 7813699112,
'Dark Souls II: Scholar of the First Sin': 38751409296,
'Dark Souls III': 27630425036,
'Darkest Dungeon': 12066824156,
"Demon's Souls": 79403560322,
'Destiny 2': 76234637928,
'Diablo': 333962365,
'Diablo II: Lord of Destruction': 10614589615,
'Diablo III': 5981923060,
'Diablo IV': 6881450057,
'Diablo Immortal': 268337008,
'Divinity: Original Sin II': 97066890477,
"Don't Knock Twice": 2614746810,
'Dreadhalls': 1013220341,
'Duck Season': 11868750058,
'Exanima': 4429205180,
'Fall Guys: Ultimate Knockout': 5663789458,
'Fallout 4': 53548454638,
'Final Fantasy VII Remake': 31346367083,
'Getting Over It with Bennett Foddy': 5231878019,
'God of War': 1186924328,
'Godfall': 284533973,
'Green Hell': 9892099952,
'Grim Dawn': 10383299063,
'Grounded': 4591337959,
'Hades': 27011131823,
'Half-Life: Alyx': 12131350186,
'Heroes of the Storm': 2082985997,
'Hot Dogs, Horseshoes & Hand Grenades': 15421675716,
'I Wanna Be The Boshy': 486910471,
'In Your Face': 329645151,
'Jump King': 1782741399,
'Just Chatting': 26848396,
'LOST ARK': 2654020720,
'Last Epoch': 49970534162,
'Limbo': 3206400933,
'Lineage 2: Revolution': 8262115020,
'Little Misfortune': 19764855934,
'Microsoft Flight Simulator': 1539007973,
'Minecraft': 14307586923,
'Mortal Shell': 0,
'Mount & Blade II: Bannerlord': 65992170893,
'New World': 35220626108,
'Oops!!! I Slept With Your Mom': 181920004,
'Papers, Please': 204661900,
'Paranormal Activity: The Lost Soul': 6176413889,
'Path of Exile': 29589377891,
'Poly Bridge 2': 1812541029,
'Portal': 357179213,
'Portal 2': 357179213,
'Risk of Rain 2': 3307273090,
'SUPERHOT VR': 6628592425,
'Sekiro: Shadows Die Twice': 16108432532,
'Slay the Spire': 2360246842,
'Stilt Fella': 395362514,
'Subnautica': 20859143166,
'Superliminal': 34755598440,
'Terraria': 1847376272,
'The Elder Scrolls V: Skyrim': 29864781871,
'The Exorcist: Legion VR': 4987498428,
'The Stanley Parable Mod': 925170085,
'Totally Accurate Battle Simulator': 9794291046,
'Transference': 11227835772,
'Unknown': 60974180387,
'VRChat': 771268527,
'Valheim': 2843296311,
'Warcraft III: Reforged': 5981923060,
'Warhammer: Chaosbane': 58238753224,
'Welcome to the Game II': 5767667201,
'Who Wants To Be A Millionaire': 6755493072,
'Who Wants to Be a Millionaire?': 6755493072,
'Wolcen: Lords of Mayhem': 109170195059,
'World of Warcraft: Battle for Azeroth': 8302308185}
import pprint
for name, size in game_to_size.items():
if size == 0:
print(name)
Mortal Shell
MEGABYTE_IN_BYTES = 1_000_000
GIGABYTE_IN_BYTES = MEGABYTE_IN_BYTES * 1000
GAMES_NOT_ON_STEAM = ["Diablo", "Diablo II", "Diablo III", "Diablo IV", "Diablo Immortal", "Lineage 2: Revolution", "Demon's Souls", "Diablo II: Lord of Destruction"]
# No depot info for Mortal Shell. Microsoft Flight Simulator's depot size is inaccurate, because it downloads the full game
# using a special downloader, which is not distributed over Steam.
PROBLEMATIC_GAMES = ["Special Events", "IRL", "Mortal Shell", "Microsoft Flight Simulator"]
# https://eu.battle.net/support/en/article/76459
game_to_size["World of Warcraft"] = 100 * GIGABYTE_IN_BYTES
# https://www.finder.com/complete-list-playstation-4-install-sizes-460-titles
# Take average of regular/GOTY since I have no idea what he played
game_to_size["Bloodborne"] = (27.19 + 32.75) / 2 * GIGABYTE_IN_BYTES
# Hard to estimate, since it uses external resources and is a browser game... Let's say 100mb
game_to_size["GeoGuessr"] = 100 * MEGABYTE_IN_BYTES
for non_game in PROBLEMATIC_GAMES + GAMES_NOT_ON_STEAM:
if non_game in game_to_airtime:
del game_to_airtime[non_game]
if non_game in game_to_size:
del game_to_size[non_game]
X = [size / GIGABYTE_IN_BYTES for name, size in sorted(game_to_size.items())]
Y = [airtime / 60 for name, airtime in sorted(game_to_airtime.items())]
# Do a sanity check, seems correct
assert len(X) == len(Y)
for game, size, airtime in zip(sorted(game_to_airtime), X, Y):
print(f"{game} ({size:.2f}Gb): Played for {airtime:.2f} hours")
A Chair in a Room: Greenwater (4.16Gb): Played for 4.08 hours AFFECTED: The Manor (3.69Gb): Played for 0.67 hours Accounting (2.30Gb): Played for 4.50 hours Amnesia: Rebirth (34.91Gb): Played for 4.25 hours Arizona Sunshine (28.57Gb): Played for 2.33 hours Ashes of Creation (6.78Gb): Played for 2.17 hours Baldur's Gate 3 (177.86Gb): Played for 11.08 hours Beat Saber (0.75Gb): Played for 20.88 hours Besiege (9.88Gb): Played for 7.25 hours Blade & Sorcery (8.21Gb): Played for 1.83 hours Bless Online (44.35Gb): Played for 10.23 hours Bloodborne (29.97Gb): Played for 40.58 hours Bloons TD 6 (2.12Gb): Played for 80.83 hours Borderlands 3 (128.96Gb): Played for 0.50 hours Cuphead (25.01Gb): Played for 14.75 hours Cyberpunk 2077 (117.76Gb): Played for 16.17 hours DOOM Eternal (60.03Gb): Played for 33.67 hours Dark Souls (7.81Gb): Played for 48.38 hours Dark Souls II: Scholar of the First Sin (38.75Gb): Played for 60.97 hours Dark Souls III (27.63Gb): Played for 88.22 hours Darkest Dungeon (12.07Gb): Played for 122.35 hours Destiny 2 (76.23Gb): Played for 27.98 hours Divinity: Original Sin II (97.07Gb): Played for 49.88 hours Don't Knock Twice (2.61Gb): Played for 2.00 hours Dreadhalls (1.01Gb): Played for 2.17 hours Duck Season (11.87Gb): Played for 1.25 hours Exanima (4.43Gb): Played for 8.00 hours Fall Guys: Ultimate Knockout (5.66Gb): Played for 13.25 hours Fallout 4 (53.55Gb): Played for 10.50 hours Final Fantasy VII Remake (31.35Gb): Played for 48.12 hours GeoGuessr (0.10Gb): Played for 1.17 hours Getting Over It with Bennett Foddy (5.23Gb): Played for 18.50 hours God of War (1.19Gb): Played for 7.65 hours Godfall (0.28Gb): Played for 3.58 hours Green Hell (9.89Gb): Played for 26.33 hours Grim Dawn (10.38Gb): Played for 12.45 hours Grounded (4.59Gb): Played for 1.83 hours Hades (27.01Gb): Played for 61.17 hours Half-Life: Alyx (12.13Gb): Played for 24.18 hours Heroes of the Storm (2.08Gb): Played for 3.33 hours Hot Dogs, Horseshoes & Hand Grenades (15.42Gb): Played for 1.25 hours I Wanna Be The Boshy (0.49Gb): Played for 2.25 hours In Your Face (0.33Gb): Played for 4.58 hours Jump King (1.78Gb): Played for 1.50 hours Just Chatting (0.03Gb): Played for 272.70 hours LOST ARK (2.65Gb): Played for 235.42 hours Last Epoch (49.97Gb): Played for 34.25 hours Limbo (3.21Gb): Played for 4.92 hours Little Misfortune (19.76Gb): Played for 4.17 hours Minecraft (14.31Gb): Played for 153.98 hours Mount & Blade II: Bannerlord (65.99Gb): Played for 4.92 hours New World (35.22Gb): Played for 18.17 hours Oops!!! I Slept With Your Mom (0.18Gb): Played for 0.50 hours Papers, Please (0.20Gb): Played for 1.50 hours Paranormal Activity: The Lost Soul (6.18Gb): Played for 3.75 hours Path of Exile (29.59Gb): Played for 3705.00 hours Poly Bridge 2 (1.81Gb): Played for 2.83 hours Portal (0.36Gb): Played for 3.83 hours Portal 2 (0.36Gb): Played for 9.00 hours Risk of Rain 2 (3.31Gb): Played for 1.75 hours SUPERHOT VR (6.63Gb): Played for 1.33 hours Sekiro: Shadows Die Twice (16.11Gb): Played for 56.65 hours Slay the Spire (2.36Gb): Played for 29.92 hours Stilt Fella (0.40Gb): Played for 2.75 hours Subnautica (20.86Gb): Played for 42.00 hours Superliminal (34.76Gb): Played for 2.92 hours Terraria (1.85Gb): Played for 228.60 hours The Elder Scrolls V: Skyrim (29.86Gb): Played for 29.13 hours The Exorcist: Legion VR (4.99Gb): Played for 4.92 hours The Stanley Parable Mod (0.93Gb): Played for 3.33 hours Totally Accurate Battle Simulator (9.79Gb): Played for 4.42 hours Transference (11.23Gb): Played for 1.00 hours Unknown (60.97Gb): Played for 1.50 hours VRChat (0.77Gb): Played for 17.42 hours Valheim (2.84Gb): Played for 24.77 hours Warcraft III: Reforged (5.98Gb): Played for 1.92 hours Warhammer: Chaosbane (58.24Gb): Played for 4.92 hours Welcome to the Game II (5.77Gb): Played for 41.00 hours Who Wants To Be A Millionaire (6.76Gb): Played for 16.58 hours Who Wants to Be a Millionaire? (6.76Gb): Played for 1.17 hours Wolcen: Lords of Mayhem (109.17Gb): Played for 103.93 hours World of Warcraft (100.00Gb): Played for 5884.45 hours World of Warcraft: Battle for Azeroth (8.30Gb): Played for 0.50 hours
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.set(title="Played/Size for Quin69's Stream", ylabel="Airtime in hours", xlabel="Size in Gigabytes")
ax.plot(X, Y, 'o')
plt.show()
fig, ax = plt.subplots()
ax.set(title="Played/Size for Quin69's Stream", ylabel="Airtime in hours (log)", xlabel="Size in Gigabytes")
ax.plot(X, Y, 'o')
plt.yscale("log")
plt.savefig("plot.png")
plt.show()