DEV Community

Devil Scrapes
Devil Scrapes

Posted on

Flashscore Live Scores Scraper: the scores feed is not JSON, it is and

Quick answer

The Flashscore Live Scores Scraper turns Flashscore's fixtures, live scores and final results for football, tennis, basketball and hockey into clean JSON, CSV or Excel rows: teams, scores, status, kickoff time in UTC, league, country and a link to the event page. Pick a sport and the days you want (from a week back to a week ahead), optionally narrow by league, and run it. Flashscore has no bulk export and no public API, and the data behind the page is not JSON at all. It is a proprietary delimited feed, and we parse it for you. Pricing is pay-per-result, $0.20 per run plus $0.002 per event, $2.20 per 1,000 events.

Does Flashscore have an API?

Not a public one. The scores you see on flashscore.com are painted by the site's own JavaScript from a feed host it calls in the background. That feed only answers requests that carry a header value the site's front-end bundle supplies, and the value can change when the bundle does. So "just call the API" means reverse-engineering a moving target and keeping up with it. That part is our job: when the feed host starts rejecting requests, the Actor reports it as a rotated header rather than as a vague network error, and we ship the update.

What does the feed actually look like?

Not JSON. One day of football is a single text body where records are split by ¬~, fields by ¬, and keys from values by ÷. A trimmed slice of a real feed:

¬~ZA÷AFRICA: Olympic Games Women - Qualification¬ZY÷Africa¬ZL÷/football/world/olympic-games-women/
¬~AA÷GvGtelQO¬AD÷1791385200¬AB÷3¬AE÷Guinea W¬AF÷Cameroon W¬WM÷GUI¬WN÷CAM
Enter fullscreen mode Exit fullscreen mode

The keys are two- and three-letter codes. AA is the event id, AD the kickoff as a Unix epoch, AB the status (1 scheduled, 2 live, 3 finished), AE and AF the home and away sides, and AG and AH the scores once a match has started.

Flashscore's live scores travel as a delimited text feed with two-letter keys, and the league is a header record, not a field on the match.

What goes wrong if you parse it naively?

Three things, each covered by a test against real captured feeds.

  • The league is a header, not a column. A ZA record names the league, then every match below it belongs to that league until the next ZA. Split the body into records and treat each one alone, and every match loses its league. We carry the header forward and stamp league, country, league_url and tournament_id onto each event.
  • Keys repeat inside a record. The first value is the right one. Last-write-wins quietly swaps fields.
  • A finished match can carry no score keys. The Guinea vs Cameroon event above is status 3 with no AG or AH. We return null rather than inventing 0-0, because a fake score is worse than a missing one in any model you feed.

Across several days the same event can appear in more than one feed, so we dedupe on event_id and you never pay twice for one match.

What the Actor gives you

One row per event, 19 fields: event_id, sport, day_offset, league, country, league_url, tournament_id, home_team, away_team, home_team_short, away_team_short, home_score, away_score, status, stage_code, kickoff_utc, kickoff_epoch, event_url and scraped_at.

{
  "event_id": "GvGtelQO",
  "sport": "football",
  "day_offset": 0,
  "league": "AFRICA: Olympic Games Women - Qualification",
  "home_team": "Guinea W",
  "away_team": "Cameroon W",
  "home_score": null,
  "away_score": null,
  "status": "finished",
  "kickoff_utc": "2026-10-05T13:00:00+00:00",
  "event_url": "https://www.flashscore.com/match/GvGtelQO/"
}
Enter fullscreen mode Exit fullscreen mode

Input: sport (1 football, 2 tennis, 3 basketball, 4 hockey), dayOffsets (for example [0, 1] for today and tomorrow, -7 to 7, up to 15 days), an optional leagueFilter substring such as premier league, and maxEvents up to 5,000.

Honest limitations 🚧

One sport per run. Fixtures, scores and status only: no odds, lineups or match statistics. Each run is a snapshot, not a stream, so schedule it to follow live matches. Tennis days are large, so fewer days means a cheaper run. A league filter that matches nothing is a successful run that costs only the start fee.

FAQ

Do I need a Flashscore account?
No. The Actor reads the same public feed the website uses.

What about blocks and rate limits?
That's our job. Every request carries a real-browser TLS fingerprint through curl-cffi, goes through Apify Proxy with a fresh session on every retry, and backs off exponentially on rate limits and server errors, honouring Retry-After. One day that still fails is logged and skipped. The run fails only if every requested day fails.

Can I track live scores?
Yes, with Apify Schedules: run it every few minutes on dayOffsets: [0] and react to status and score changes.

How does pricing work?
$0.20 per run plus $0.002 per event row: $2.20 per 1,000 events. No data, no charge beyond the start fee.

→ Flashscore Live Scores Scraper on Apify


Built by Devil Scrapes. We handle the feed format, the fingerprints, the proxies and the retries, so your scores table stays clean. 😈

Top comments (0)