-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstrategus-scrape.py
More file actions
215 lines (180 loc) · 7.12 KB
/
Copy pathstrategus-scrape.py
File metadata and controls
215 lines (180 loc) · 7.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
from datetime import datetime
import json
import csv
import os
import time
import logging
# CSV headers
RANKING_CSV_HEADER = ['date', 'pos', 'name', 'flag', 'score', 'diff', 'wld', 'eff', 'opps', 'aor']
MAX_RETRIES = 3
PAGE_LOAD_TIMEOUT = 30
def _setup_logging():
"""Configure terminal-only logging in a Rose-style format"""
class _Fmt(logging.Formatter):
def format(self, record):
record._when = time.strftime("%H:%M:%S", time.localtime())
return super().format(record)
logger = logging.getLogger("scrape")
if not logger.handlers:
handler = logging.StreamHandler()
handler.setFormatter(_Fmt("%(_when)s | %(levelname)-7s | %(message)s"))
logger.addHandler(handler)
logger.setLevel(logging.INFO)
return logger
log = _setup_logging()
def log_success(message, tag="SCRAPE"):
"""Log a success line in Rose-style format"""
log.info(f"[{tag}] {message}")
def log_event(event, details=None, icon="", tag="SCRAPE"):
"""Log an event line, optionally followed by indented details"""
log.info(f"{icon} [{tag}] {event}" if icon else f"[{tag}] {event}")
if details:
for key, value in details.items():
log.info(f" | {key}: {value}")
def log_section(title, tag="SCRAPE"):
"""Log a section header"""
log.info(f"--- {title} ---")
def log_status(status, value, tag="SCRAPE"):
"""Log a status line"""
log.info(f"[{tag}] {status}: {value}")
def ensure_csv_header(filename, header):
"""Ensure CSV file exists with correct header"""
if not os.path.isfile(filename):
with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(header)
def date_from_timestamp(driver, json_url):
"""Fetch the JSON timestamp and return a deterministic UTC date YYYY-MM-DD"""
driver.get(json_url)
body = driver.execute_script("return document.body.innerText")
data = json.loads(body)
ts_ms = data["timestamp"]
return datetime.utcfromtimestamp(ts_ms / 1000.0).strftime('%Y-%m-%d')
def create_driver():
"""Create a shared headless Chrome driver"""
options = webdriver.ChromeOptions()
options.add_argument('--headless')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
options.add_argument('--disable-gpu')
driver = webdriver.Chrome(options=options)
driver.set_page_load_timeout(PAGE_LOAD_TIMEOUT)
return driver
def scrape_ranking_data(driver, url, scraped_date):
"""Scrape the ranking table data"""
data = []
driver.get(url)
# Click "Show all" button if exists
try:
show_all_button = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.ID, "divShowAll"))
)
show_all_button.click()
time.sleep(2)
log_success("Expanded 'Show all' list")
except TimeoutException:
log.warning("[SCRAPE] 'Show all' button not found; may get incomplete ranking")
except Exception as e:
log.warning(f"[SCRAPE] could not click 'Show all': {e}")
# Scrape table data
table_body = driver.find_element(By.ID, "listing")
rows = table_body.find_elements(By.TAG_NAME, "tr")
for row in rows:
cols = row.find_elements(By.TAG_NAME, "td")
if len(cols) >= 9:
pos = cols[0].text.strip()
name = cols[1].text.strip()
try:
flag_span = cols[2].find_element(By.TAG_NAME, "span")
flag = flag_span.get_attribute("class").replace("flag ", "").replace("flag-", "").upper()
except Exception:
flag = ""
data.append([
scraped_date,
pos,
name,
flag,
cols[3].text.strip(),
cols[4].text.strip().replace('(', '').replace(')', '').replace('d', '').strip(),
cols[5].text.strip(),
cols[6].text.strip(),
cols[7].text.strip(),
cols[8].text.strip()
])
return data
def scrape_with_retry(scrape_func, driver, url, scraped_date):
"""Run a scrape with retries on transient failures"""
last_error = None
for attempt in range(1, MAX_RETRIES + 1):
try:
return scrape_func(driver, url, scraped_date)
except Exception as e:
last_error = e
log.warning(f"[SCRAPE] attempt {attempt}/{MAX_RETRIES} failed: {e}")
if attempt < MAX_RETRIES:
# Recreate the driver in case the session is in a bad state
try:
driver.quit()
except Exception:
pass
driver = create_driver()
time.sleep(2)
raise last_error
def save_to_csv(data, filename, header):
"""Save data to CSV, checking for duplicates"""
if not data:
return
ensure_csv_header(filename, header)
# Get the date from the first row of new data
new_date = data[0][0] if data else None
# Check if this date already exists in the file
if new_date and is_date_already_saved(new_date, filename):
log.info(f"[SCRAPE] data for {new_date} already exists in {filename}. Skipping.")
return
# If not, append the new data
with open(filename, 'a', newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile)
writer.writerows(data)
def is_date_already_saved(date_to_check, filename):
"""Check if date exists in CSV"""
if not os.path.isfile(filename):
return False
with open(filename, 'r', encoding='utf-8') as csvfile:
reader = csv.reader(csvfile)
next(reader, None) # Skip header
for row in reader:
if row and row[0] == date_to_check:
return True
return False
if __name__ == "__main__":
# Initialize CSV
ensure_csv_header('ranking_history.csv', RANKING_CSV_HEADER)
ranking_url = "https://strategus.appspot.com/eloRanking.html"
ranking_json_url = "https://strategus.appspot.com/eloRanking"
ranking_csv = 'ranking_history.csv'
log_section("Strategus Archive Scrape")
driver = None
try:
driver = create_driver()
log_success("Launched headless browser")
# Scrape and save ranking data
log_section("Rankings")
ranking_date = date_from_timestamp(driver, ranking_json_url)
log_status("ranking date", ranking_date)
ranking_data = scrape_with_retry(scrape_ranking_data, driver, ranking_url, ranking_date)
if ranking_data:
log_event("ranking data", {"rows": len(ranking_data)})
save_to_csv(ranking_data, ranking_csv, RANKING_CSV_HEADER)
finally:
if driver is not None:
try:
driver.quit()
log.info("[SCRAPE] browser closed")
except Exception:
pass
log_success("Scraping complete")