1313import os
1414import time
1515from io import StringIO
16-
17- import pandas as pd
18- import requests
1916import random
2017
21-
22- USER_AGENTS = [
23- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" ,
24- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" ,
25- "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" ,
26- "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0" ,
27- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15" ,
28- "Mozilla/5.0 (X11; Linux x86_64; rv:89.0) Gecko/20100101 Firefox/89.0" ,
29- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36" ,
30- "Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Mobile/15E148 Safari/604.1" ,
31- ]
18+ import pandas as pd
19+ from curl_cffi import requests
3220
3321
3422def fetch_steamcharts (app_id : int , name : str ) -> pd .DataFrame | None :
3523 """Fetch monthly player count history for a single game"""
3624 url = f"https://steamcharts.com/app/{ app_id } "
37- headers = {
38- "User-Agent" : random .choice (USER_AGENTS ),
39- }
4025 try :
41- r = requests .get (url , headers = headers , timeout = 15 )
26+ r = requests .get (url , impersonate = "chrome136" , timeout = 15 )
4227 if r .status_code != 200 :
4328 print (f"{ name } : HTTP { r .status_code } " )
4429 return None
@@ -75,17 +60,25 @@ def scrape_to_disk(games: dict, output_path: str, delay: float = 4) -> int:
7560 Number of new rows written in this run.
7661 """
7762 os .makedirs (os .path .dirname (os .path .abspath (output_path )), exist_ok = True )
63+ failed_path = output_path .replace (".csv" , "_failed.csv" )
7864
7965 safely_done : set [int ] = set ()
66+ failed_ids : set [int ] = set ()
8067 header_written = False
8168
69+ # Load permanently failed IDs from previous runs
70+ if os .path .exists (failed_path ):
71+ failed_df = pd .read_csv (failed_path )
72+ failed_ids = set (failed_df ["app_id" ].dropna ().astype (int ).tolist ())
73+ print (f"[steamcharts] Skipping { len (failed_ids )} permanently failed app_ids" )
74+
8275 if os .path .exists (output_path ):
8376 try :
8477 existing = pd .read_csv (output_path )
8578 all_ids = existing ["app_id" ].dropna ().astype (int ).tolist ()
8679 if all_ids :
8780 last_id = all_ids [- 1 ]
88- # Drop the last game's rows because it they may be truncated
81+ # Drop the last game's rows because they may be truncated
8982 existing_clean = existing [existing ["app_id" ].astype (int ) != last_id ]
9083 existing_clean .to_csv (output_path , index = False )
9184 safely_done = set (
@@ -99,14 +92,18 @@ def scrape_to_disk(games: dict, output_path: str, delay: float = 4) -> int:
9992 except Exception :
10093 pass
10194
102- remaining = {k : v for k , v in games .items () if int (k ) not in safely_done }
95+ remaining = {
96+ k : v
97+ for k , v in sorted (games .items (), key = lambda x : x [1 ][1 ], reverse = True )
98+ if int (k ) not in safely_done and int (k ) not in failed_ids
99+ }
103100 print (f"[steamcharts] { len (remaining )} games to scrape: { output_path } " )
104101
105102 rows_written = 0
106103 fetched = 0
107104
108- for app_id , name in remaining .items ():
109- print (f" ({ len (safely_done ) + fetched + 1 } /{ len (games )} ) { name } ..." )
105+ for app_id , ( name , _ ) in remaining .items ():
106+ print (f" ({ len (safely_done ) + fetched + 1 } /{ len (games )} ) { name } { app_id } ..." )
110107 df_game = fetch_steamcharts (int (app_id ), name )
111108 if df_game is not None :
112109 df_game .to_csv (
@@ -118,7 +115,14 @@ def scrape_to_disk(games: dict, output_path: str, delay: float = 4) -> int:
118115 rows_written += len (df_game )
119116 header_written = True
120117 fetched += 1
121- time .sleep (delay )
118+ else :
119+ pd .DataFrame ({"app_id" : [int (app_id )], "name" : [name ]}).to_csv (
120+ failed_path ,
121+ mode = "a" ,
122+ index = False ,
123+ header = not os .path .exists (failed_path ),
124+ )
125+ time .sleep (delay + random .uniform (0 , 2 ))
122126
123127 print (
124128 f"[steamcharts] Done: { fetched } new games fetched, { rows_written } new rows written"
0 commit comments