Coordinates browser workers that call the Stratz GraphQL API to discover Dota 2 player accounts via the peers endpoint and aggregate hero performance. Each worker scrapes one player per task: it pulls the player's hero-performance breakdown, the latest match ID, and the full set of teammates / opponents from Stratz, then submits everything in one call to the backend. The backend runs a single-phase BFS — new accounts go into the queue at depth+1, and once every queued account is scraped the workers re-scrape the oldest entries on a LRU rotation.
Serves the worker dashboard and a small JSON API. Routes:
GET /: Operator dashboard. A localhost-only seeding form is shown when the request originates from a loopback address.POST /task: Returns the nextscrape_playertask. Picks the lowest-depth unscraped account first; if none exist, picks the oldest previously scraped account.POST /task/reset: Releases a task back to the queue (clears the assignment lease).POST /submit: Accepts ascrape_playerpayload: hero stats + discovered peer IDs + latest match ID + depth. Returns the next task in the same response.GET /progress: Reports{ players_total, scraped }.GET /progress/graph: Time-series chart of progress snapshots.GET /seed?start=N&end=M: Localhost-only bulk insert of seed accounts at depth 0.GET /leaderboards,GET /leaderboards/<hero_slug>,GET /best: Aggregated leaderboards sourced from the cachedhero_top100table.
PostgreSQL via psycopg. ensure_schema_exists() creates tables and indexes on startup. Connections are pooled per-thread for writers; reads open on-demand. A default connection string of postgresql://postgres:postgres@localhost:5432/stratz_scraper is used when DATABASE_URL isn't set.
Polls /task, fires a single combined GraphQL document against api.stratz.com/graphql (heroes + latest match + WITH/AGAINST peers), paginates per side if a page returns exactly 2000 rows, and submits to /submit. The Stratz token lives only in the browser's localStorage and is transmitted exclusively to Stratz. Multi-token rotation, JWT decoding, exponential backoff, and Retry-After handling are all in place.
| Table | Purpose | Key Columns |
|---|---|---|
players |
BFS queue with one bit of progress state. | steamAccountId, depth, assigned_to, assigned_at, scraped_at, latest_match_id |
hero_stats |
Per-(player, hero) match and win counts. | steamAccountId, heroId, matches, wins |
hero_top100 |
Cached leaderboard (100 players per hero). Rebuilt every 5 minutes by a background thread. | heroId, steamAccountId, matches, wins |
meta |
Scheduler key/value (e.g. last_assignment_cleanup). |
key, value |
progress_snapshots |
5-minute samples of (players_total, scraped). |
captured_at, players_total, scraped |
- Startup: Server ensures the schema exists and seeds the root account (
293053907). - Frontier scrape: While any account has
scraped_at IS NULL, workers receive itssteamAccountIdas ascrape_playertask. Lowest depth first, lowest steamAccountId tie-breaker. - Submission: Worker submits heroes + discovered peer IDs + latest match ID. Backend upserts
hero_stats, COPYs new peers intoplayersat depth+1, setsscraped_at = NOW(). - Re-scrape: Once the frontier is empty, workers re-scrape previously scraped accounts in LRU order (oldest
scraped_atfirst), discovering new peers as the player network evolves. - Leaderboard: A background thread rebuilds
hero_top100fromhero_statsevery 5 minutes.
- Create a PostgreSQL database (default:
stratz_scraperowned bypostgres). - Export
DATABASE_URLif credentials differ. pip install -r requirements.txt.python app.py— listens on0.0.0.0:80.
Behind a proxy, forward X-Forwarded-For so /seed stays localhost-only via is_local_request.
- Tokens stay in the browser. Workers transmit them only to Stratz.
- Stale leases: Background cleanup releases assignments older than 10 minutes every 60 seconds; up to 1000 rows per cycle.
- Partial scrapes: If the background processing fails after the foreground commit,
_unmark_scrape_tasknullsscraped_atso the player is immediately re-eligible. Successful re-scrapes are idempotent because hero_stats UPSERT is trust-newer and discovered_id INSERT-ON-CONFLICT preserves the minimum depth. - Backoff: Exponential backoff in the worker with Retry-After header honoured for 429s. UI surfaces the minimum active backoff across all running tokens.