A step-by-step guide to exporting full match details, team formations, per-player stats and ratings, goal/card/substitution timelines, and head-to-head xG comparisons for any football match — by match ID or URL, with no scraping code required.
The problem: Football data is rich, but trapped in mobile apps and scattered web pages
FotMob is one of the cleanest, fastest, and most comprehensive football tracking platforms in the world. Whether it's the Premier League, Champions League, La Liga, MLS, or lower-tier domestic competitions, FotMob packs an enormous amount of deep data into every single fixture: confirmed tactical formations, live match ratings, expected goals (xG) shot breakdowns, player of the match honors, and granular event timelines.
The catch: you can only explore it one match at a time in a browser or mobile app. If you are:
- a football data analyst or sports scientist looking to model team performance and player tendencies,
- a predictive modeler or sports bettor looking for quantitative edge through historical xG, shot volumes, and duel success rates,
- a fantasy manager or scouting tool builder tracking player form, minutes, and statistical outputs across fixtures,
- a content creator, newsletter writer, or sports journalist looking for instant post-match stat cards and tactical timelines, or
- a developer building a Discord bot, mobile app, or personal dashboard —
…then manually copying numbers off the screen is impossible at scale. Pulling 500 weekend matches by hand at 2 minutes per match takes over 16 hours of clicking. Meanwhile, enterprise sports data feeds (Opta, StatsBomb, Sportradar) charge hundreds to thousands of dollars per month and strictly gatekeep xG and player ratings behind expensive tiers.
Scraping FotMob directly yourself isn't much easier either: data is fragmented across dynamic client-side bundles, internal endpoints, and request signature headers (x-mas) that frequently break custom scrapers.
This tutorial shows a faster, painless alternative: input a list of match IDs or URLs and get back one clean, normalized JSON (or CSV/Excel) record per match — complete with scorelines, formations, per-player stats, ordered event timelines, and team comparisons — in a single automated run that finishes in minutes and costs pennies (with a free monthly tier that covers ~500 matches).
We’ll use the FotMob Match Details Scraper on Apify. No coding is required for the standard workflow, and we've included quick Python and API snippets at the end if you want to pipe the data directly into your analytical stack.
What you'll get (per match)
Each match returns as one structured record containing:
-
Match header & status — home/away teams, final score, kickoff time in UTC (ISO 8601), competition, matchday/round, country, status (
started,finished), venue, referee, attendance, and official Player of the Match. -
lineups— both teams' tactical formations (e.g.4-3-3,4-2-3-1), head coach, starters, and bench with shirt numbers and designated positions. -
playerStats— flattened, analysis-ready stats for every player who featured:rating_title, minutes played, goals, assists, expected goals (expected_goals), expected assists (expected_assists), total shots, accurate passes ("34/40 (85.0%)"), chances created, touches, duels won, clearances, and goalkeeper stats (saves,goals_conceded). -
events— an ordered, chronological timeline of every key match event: goals (withgoalTypeandassist), cards (yellow/red), and substitutions (with incoming/outgoing players and exact minute labels). -
teamStats— the full head-to-head comparison table categorized into clear groups:Top stats,Shots,Expected goals (xG),Passes,Defence,Duels, andDiscipline. -
h2h(opt-in) — historical head-to-head meetings between the two clubs. -
rawData(opt-in) — the complete, unparsed FotMob JSON payload for advanced users needing internal metadata.
Export everything into JSON, CSV, Excel, XML, or fetch via API.
What you'll need
- A free Apify account — the free plan includes $5/month in platform credit (covering roughly 500 full match breakdowns every month), no credit card required.
- The Actor: FotMob Match Details Scraper.
- A list of FotMob match IDs or URLs (we'll cover how to find them or generate them in bulk).
Step 1 — Open the Actor
Go to the FotMob Match Details Scraper on Apify and click Try for free. If you're not signed in, create your free account, and you'll immediately land on the Actor console with the input screen ready.
Step 2 — Add your match IDs or URLs
In the Match ids or URLs field, add one entry per line. The Actor accepts numeric IDs, direct match URLs, or anchor URLs interchangeably:
4506324
https://www.fotmob.com/match/4506324
https://www.fotmob.com/matches/aston-villa-vs-manchester-united/2pj9x9#4506324
How to find match IDs: Open any match on FotMob in your web browser. The numeric string in the URL (e.g.
fotmob.com/match/4506324) is the match ID.Need match IDs in bulk? You don't have to look them up manually. Run the FotMob Matches Scraper (to pull all match IDs by calendar date) or the FotMob League Scraper (to pull an entire season's fixture schedule). Both actors emit
matchIds that pipe directly into this Actor.
Step 3 — Choose your options (defaults are fine)
The Actor provides independent toggles so you can control payload size, speed, and cost:
| Option | What it does | Recommended |
|---|---|---|
| Include player stats | Extracts per-player ratings, minutes, passes, duels, shots & xG | Leave On (turn off only if you strictly need team totals) |
| Include events timeline | Adds chronological goals, assists, cards, and substitutions | Leave On |
| Include lineups | Adds formations, coaches, starting XI, and bench players | Leave On |
| Include team stats | Head-to-head stat table (possession, xG, shots, passes, fouls) | Leave On |
| Include head-to-head history | Adds previous meetings between the two sides | Off unless doing historical matchup previews |
| Include raw data | Appends FotMob's entire unparsed JSON response | Leave Off (significantly balloons dataset size) |
| Proxy configuration | Network routing through Apify Proxy | Leave Automatic / Datacenter (default is fast and reliable) |
| Max concurrency / Delay | Controls scraping speed and politeness | Defaults (5 concurrency / 1s delay) work smoothly |
| Max items | Stops run after N matches |
0 = unlimited |
If you prefer using the JSON editor tab on Apify, here is the equivalent payload:
{
"matchIds": [
"4506324",
"https://www.fotmob.com/match/4506324"
],
"includePlayerStats": true,
"includeEvents": true,
"includeLineups": true,
"includeTeamStats": true,
"includeH2H": false,
"includeRawData": false,
"proxyConfiguration": { "useApifyProxy": true }
}
Step 4 — Run it
Click Save & Start.
The Actor launches and begins streaming parsed match records into your default dataset. A single match finishes in 2–3 seconds; batches of 50 to 100 matches complete in 1 to 2 minutes.
One critical detail: Failed lookups are 100% free. If you accidentally submit an invalid match ID, a postponed match that won't load, or a typo in the URL, that entry is not written to your dataset and you are never billed for it. Any problematic inputs are transparently captured in the SUMMARY record inside your Key-Value Store so you can inspect them without paying a dime.
Step 5 — Read the output (the important part)
Head over to the Dataset tab. You can view the tabular view or switch to raw JSON.
Here is a trimmed real-world output from the Premier League clash between Aston Villa and Manchester United (4506324):
{
"matchId": 4506324,
"matchName": "Aston Villa-vs-Manchester United_Sun, Oct 6, 2024, 13:00 UTC",
"matchUrl": "https://www.fotmob.com/match/4506324",
"leagueId": 47,
"leagueName": "Premier League",
"round": "7",
"countryCode": "ENG",
"matchTimeUtc": "2024-10-06T13:00:00.000Z",
"started": true,
"finished": true,
"homeTeam": { "id": 10252, "name": "Aston Villa", "score": 0 },
"awayTeam": { "id": 10260, "name": "Manchester United", "score": 0 },
"venue": "Villa Park",
"referee": "Robert Jones",
"attendance": 42682,
"playerOfTheMatch": {
"playerId": 268375,
"name": "Emiliano Martinez",
"teamName": "Aston Villa"
},
"teamStats": [
{ "group": "Top stats", "title": "Ball possession", "key": "BallPossesion", "home": 54, "away": 46 },
{ "group": "Top stats", "title": "Expected goals (xG)", "key": "expected_goals", "home": "0.50", "away": "0.56" },
{ "group": "Top stats", "title": "Total shots", "key": "total_shots", "home": 11, "away": 10 },
{ "group": "Passes", "title": "Accurate passes", "key": "accurate_passes", "home": "345 (83%)", "away": "302 (82%)" }
// … grouped by Shots, Passes, Defence, Duels, Discipline
],
"events": [
{
"type": "Card",
"minute": 3,
"minuteLabel": 3,
"isHome": false,
"playerId": 157723,
"playerName": "Christian Eriksen",
"card": "Yellow"
},
{
"type": "Substitution",
"minute": 12,
"isHome": true,
"playersInvolved": ["Diego Carlos", "Ezri Konsa"]
}
// … goals include goalType, scorer, and assist
],
"lineups": {
"home": {
"teamId": 10252,
"teamName": "Aston Villa",
"formation": "4-4-1-1",
"coach": "Unai Emery",
"starters": [
{ "playerId": 268375, "name": "Emiliano Martinez", "shirtNumber": "23", "position": "Keeper" }
// … all 11 starters
],
"subs": [ /* bench players */ ]
},
"away": {
"teamId": 10260,
"teamName": "Manchester United",
"formation": "4-2-3-1",
"coach": "Erik ten Hag",
"starters": [ /* … */ ],
"subs": [ /* … */ ]
}
},
"playerStats": [
{
"playerId": 268375,
"name": "Emiliano Martinez",
"teamId": 10252,
"teamName": "Aston Villa",
"shirtNumber": "23",
"position": "Keeper",
"isGoalkeeper": true,
"rating_title": 8.36,
"minutes_played": 90,
"saves": 4,
"goals_conceded": 0,
"accurate_passes": "24/32 (75.0%)",
"touches": 40
}
// … one complete flattened stats object per player who stepped on the pitch
],
"source": "fotmob-match-details-scraper",
"scrapedAt": "2026-06-18T11:11:32+00:00"
}
Three key things to note about this schema:
-
Player statistics are flattened for analysis. Many scrapers dump deeply nested UI trees for player cards. This Actor flattens them into clean, standardized key-value pairs (
rating_title,minutes_played,expected_goals,touches,chances_created), making it trivial to load into a Pandas DataFrame or SQL table without data wrangling. -
Events are chronologically unified. Substitutions, cards, and goals live together in the
eventsarray in exact game order. You don't have to query players to find out who was subbed off in the 12th minute. -
Status-aware data. For scheduled upcoming matches, the Actor returns confirmed lineups and formations as soon as FotMob publishes them, while omitting
playerStatsuntil the match kicks off.
Step 6 — Export and integrate
No-code: Spreadsheets & Live Google Sheets
From the Apify Console Dataset tab, click Export to download your records as CSV, Excel (XLSX), JSON, or XML.
To pull live match data into Google Sheets without exporting files manually, use the dataset's direct CSV endpoint with =IMPORTDATA():
=IMPORTDATA("https://api.apify.com/v2/datasets/<DATASET_ID>/items?format=csv&clean=true&token=<YOUR_APIFY_TOKEN>")
(Keep your Apify API token private or create a read-only token under your Apify Integrations settings).
Python: Automate workflows with apify-client
Install the official Python client:
pip install apify-client
Trigger the Actor and iterate over player stats and xG values directly in Python:
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run_input = {
"matchIds": [
"4506324",
"https://www.fotmob.com/match/4506324",
],
"includePlayerStats": True,
"includeEvents": True,
"includeLineups": True,
"includeTeamStats": True,
}
run = client.actor("incognito_mode/fotmob-match-details-scraper").call(run_input=run_input)
# Stream parsed dataset items
for match in client.dataset(run["defaultDatasetId"]).iterate_items():
print(f"Match: {match['homeTeam']['name']} {match['homeTeam']['score']} - {match['awayTeam']['score']} {match['awayTeam']['name']}")
print(f"Player of the Match: {match.get('playerOfTheMatch', {}).get('name')}")
# Extract top ratings
for player in match.get("playerStats", []):
if player.get("rating_title", 0) >= 8.0:
print(f" ⭐ {player['name']} ({player['teamName']}): Rating {player['rating_title']} | Minutes: {player['minutes_played']}")
cURL: One-shot synchronous API run
If you want to trigger a run from an external backend or serverless function and immediately receive the dataset items in response:
curl -X POST \
"https://api.apify.com/v2/acts/incognito_mode~fotmob-match-details-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"matchIds": ["4506324"],
"includePlayerStats": true,
"includeTeamStats": true
}'
Automating with Schedules & Webhooks
- Daily / Weekly Schedules: Set an Apify Schedule to run every Sunday evening or Monday morning to automatically collect all weekend match stats.
- Webhooks: Configure a webhook to POST the completed dataset to your webhook endpoint, Zapier, Make, or Discord bot whenever a run finishes.
What it costs
The Actor uses transparent, predictable pay-per-event pricing with an automatic volume discount — no recurring subscription fees, no locked contracts, and zero charges for failed requests:
- $0.01 per match ($10.00 per 1,000 matches) for the first 10,000 matches in a run.
- $0.006 per match ($6.00 per 1,000 matches) for every match beyond 10,000 in a single run (40% volume discount).
- A minimal $0.02 Actor start fee per run.
- Failed lookups cost $0.00 — you never pay for non-existent matches, broken links, or cancelled fixtures.
Real-world cost examples:
- Analyzing a 10-match Premier League matchday ≈ $0.12
- Processing a 50-match weekend slate across top European leagues ≈ $0.52
- Scraping a full 380-match season with all lineups, xG, and player ratings ≈ $3.82
- Scraping 15,000 global fixtures across a season ≈ $130.02
The $5.00/month free platform credit on Apify covers ~500 full match breakdowns completely free every month. Compared to commercial sports data feeds charging $300–$1,500/month for xG and event data, this represents unmatched flexibility.
You can also set a Maximum charge per run in the Apify Console. If your run reaches your specified budget, it terminates cleanly and preserves every record already collected.
Tips, limits & responsible use
-
Combine with the discovery actors: The cleanest automated pipeline is a 2-step chain:
- Run FotMob Matches Scraper (by date) or FotMob League Scraper (by competition).
- Map the output
matchIds straight into the FotMob Match Details Scraper for deep extraction.
-
Payload sizing: If you're building high-volume databases and don't need historical head-to-head records or raw JSON, leave
includeH2HandincludeRawDataoff. This keeps records lightweight and transfers fast. -
Stay respectful: Keep concurrency moderate (
5–10) and maintain the default 1-second delay. - Disclaimer: This Actor is intended for personal analysis, research, and data integration. Ensure your usage complies with applicable terms of service and law. This project is not affiliated with, endorsed by, or sponsored by FotMob.
Wrap-up
That’s the complete pipeline: input match IDs or URLs → run → get structured JSON/CSV with verified scorelines, starting formations, xG stats, player ratings, and minute-by-minute match events.
Whether you're developing predictive models, tracking fantasy players, or building a live football dashboard, it replaces fragile DIY scrapers and four-figure API subscriptions with a dependable, pay-as-you-go workflow.
Try it out on this weekend's matches: FotMob Match Details Scraper on Apify.
Have a specific field you'd like added or feedback on the schema? Open an issue from the Actor's Issues tab on Apify!

Top comments (0)