Sofascore Season Stats Scraper
Pricing
from $1.50 / 1,000 player leaderboard rows
Sofascore Season Stats Scraper
Scrape season leaderboards and team statistics from Sofascore for football, basketball and ice hockey. Top scorers, assists, ratings and 30 more statistics per league, plus a complete team table and knockout brackets. No API key, no login.
Pricing
from $1.50 / 1,000 player leaderboard rows
Rating
0.0
(0)
Developer
Elena Vance
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
16 hours ago
Last modified
Categories
Share
Season leaderboards and team statistics from Sofascore, for any competition it covers. Top scorers, assist leaders and match ratings; a complete team table with every statistic the league publishes; and the knockout bracket where there is one.
No API key, no login, no browser. Calls Sofascore's own JSON API directly and only reaches for a proxy if a request is actually blocked.
What you get
Three row types in one dataset, told apart by the recordType column.
playerLeaderboard — one row per player per statistic, ranked. Football
publishes 34 leaderboards (goals, assists, rating, expected goals, key passes,
tackles, kilometres covered, top speed, saves…), basketball 24 (points,
rebounds, triple-doubles, true shooting, player efficiency…) and ice hockey 21
(points, save percentage, time on ice, power-play goals…). Each row carries
the rank, the value, the appearances behind it, the player and the club they
played for.
teamStats — one row per team with every team statistic merged into one
object: goals scored and conceded, expected goals, possession, passing
accuracy, corners, cards for football; offensive and defensive rating, pace,
true shooting for basketball. This is not a top-N cut — every team in the
competition is there, so a Premier League run is twenty complete rows.
cupBracket — one row per tie: round, both teams, the aggregate result and
who went through. Cups, tournament knockout stages and play-off leagues have
one; the NBA returns its fifteen-tie play-off bracket. Round-robin leagues
decided on points return nothing.
What makes this different
- Around thirty ranked tables merged into one row per team. Sofascore publishes team statistics as dozens of separate leaderboards; this Actor fetches them all and joins them, so a season arrives as one table you can open rather than thirty you have to reconcile yourself.
- Not a top-N cut. Every team in the competition gets a row — 20 for a Premier League season, 30 for the NBA, 32 for the NHL.
- 34 football leaderboards, 24 basketball, 21 ice hockey, each row carrying the rank, the value, the appearances behind it and the club the player played for.
- Knockout brackets where a competition has one — round, both teams, the aggregate result and who advanced. Round-robin leagues return nothing and are never charged for it.
- Failures are never billed, and unusable input returns an unbilled diagnostic row rather than failing the run.
Input
| Field | Type | Default | What it does |
|---|---|---|---|
tournaments | array | — | Sofascore tournament URLs or unique-tournament IDs. 17 = Premier League, 132 = NBA, 234 = NHL, 7 = Champions League. |
seasonId | integer | latest | Which season. A #id: fragment in a pasted URL wins over this. |
seasonFallback | boolean | true | Use the previous season when the newest one has been scheduled but not played. |
includePlayerLeaderboards | boolean | true | The ranked player tables. |
includeTeamStats | boolean | true | The complete team table. |
includeCupBracket | boolean | false | The knockout bracket. |
playerCategories | array | rating, goals, assists | Which leaderboards to keep, or ["all"]. This is the setting that decides the size of your bill. |
topN | integer | 50 | Keep the top N of each leaderboard. 50 is Sofascore's maximum. |
statisticsTypes | array | auto | Leave empty. Set it only to reach a table the automatic choice skips — see below. |
maxItems | integer | 10000 | Stop after this many rows. |
proxyConfiguration | object | off | Only needed if your IP is blocked. |
Minimal input:
{ "tournaments": ["https://www.sofascore.com/tournament/football/england/premier-league/17"] }
What a run costs
Requests are fixed and small — the whole 34-category table arrives in one call,
so a competition-season is two to five requests whatever you ask for. What
varies is the row count, and playerCategories is the dial:
| Input | Player rows | Team rows |
|---|---|---|
| Default three categories | 150 | 20 |
["goals", "assists"] | 100 | 20 |
["all"] | 1,560 | 20 |
Sport coverage
Verified against the live API for football, basketball and ice hockey, across the Premier League, the Champions League, the NBA, the NHL and the World Cup. Tennis and other individual sports have no team or squad concept and publish no leaderboards here. Baseball has none at all — Sofascore returns nothing for MLB, which the Actor reports rather than failing.
The two things that are not obvious
Statistics have a type, and it belongs to the competition. Football spells
it overall; the NBA and NHL answer 404 for that and want regularSeason, and
the NBA publishes playoffs as a separate table. Sofascore has no endpoint
that says which — so the Actor probes for it and remembers the answer per
competition, which is why a multi-league run doesn't pay for it repeatedly.
Set statisticsTypes yourself only to reach a table the probe skips: pinning
["regularSeason", "playoffs"] returns both NBA tables in one run.
A "latest" season is often empty. Sofascore lists a season as soon as it is
scheduled, but statistics appear only once matches have been played. Measured
in late August 2026, the NBA, NHL and Champions League all returned nothing for
their newest season while the previous one was complete. With seasonFallback
on (the default) the Actor walks back up to two seasons and records what it
used in seasonYear; seasonRequested is filled in only when a fallback
happened, so you can always tell.
Notes
- Player leaderboards are a top-50 cut, team tables are not. That is why the two have different row shapes: 381 Premier League players spread over 34 leaderboards fill 12% of the cells a pivoted table would need, so pivoting them would imply a striker made no tackles when he is simply outside the top 50. Team tables cover every team in every category, so they pivot losslessly.
- A category that doesn't exist for a sport (
goalsin basketball) is reported in the log along with the full list of the ones that do, rather than failing the run. - Bad input is recorded as an unbilled
INVALID_INPUTrow rather than crashing the run.
The Sofascore Actor suite
Six Actors from the same publisher, built on one shared HTTP layer and one ID space — so the output of one is the input of the next.
| Actor | What it returns |
|---|---|
| Live Scores & Fixtures | Fixtures and live results by date across 19 sports. Emits eventId. |
| Match Stats, xG & Lineups | Per-match shotmaps with pitch coordinates, lineups, ratings, momentum. Takes eventId. |
| League Standings & Tables | League tables, group stages, conferences and divisions. Takes uniqueTournamentId. |
| Team & Player Data | Squads, bios, market values, transfers, match logs. Takes team and player IDs. |
| Season Stats | This Actor. |
| Rankings & Search | FIFA, ATP/WTA, UEFA and UFC tables, plus name-to-ID search. |
How they chain. Live Scores emits the eventId that Match Stats takes.
Rankings' search turns a name into the team, player and tournament IDs the
other five accept. Standings and Season Stats share uniqueTournamentId, and
Team & Player emits that same ID as primaryTournamentId.
All six need no API key, no login and no headless browser; none requires a residential-proxy add-on, and all run on the Apify free plan.
Development
pip install -r requirements-dev.txtpytest # 131 tests, no networknpx apify-cli@1 validate-schemaapify run # needs storage/key_value_stores/default/INPUT.json
docs/architecture.md covers how the client, season resolution and billing fit
together; docs/ci-cd.md the GitHub Actions pipeline; docs/publishing.md the
Apify Store listing and pricing.