Transfermarkt Club Squad & Market Value Scraper
Pricing
from $1.20 / 1,000 squad player scrapeds
Transfermarkt Club Squad & Market Value Scraper
Scrape full football squads from Transfermarkt by club id, URL or name. One row per player: market value, shirt number, position, age, height, foot, nationality, joined date, signed-from club and the fee paid. Any season. No API key, no login, no proxy needed. JSON, CSV or Excel.
Pricing
from $1.20 / 1,000 squad player scrapeds
Rating
0.0
(0)
Developer
Elena Vance
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
Scrape a whole football squad from Transfermarkt in one call — by club ID, by URL, or by club name. One row per player, with market value, position, age, height, foot, nationality, joined date, the club they were signed from and the fee that was paid for them, plus the club's own totals repeated on every row.
Any season, back to whenever Transfermarkt's archive starts for that club.
No API key, no login, no residential proxy, no browser. It runs on the Apify free plan. Export to JSON, CSV, Excel, XML or pull it from the API.
What you get
One row per squad player. Real, unedited output from {"clubIds": ["281"]}:
| Field | Value |
|---|---|
clubId / clubName | 281 / Manchester City |
competitionCode / competitionName | GB1 / Premier League |
clubTotalMarketValue | 1310000000 |
clubSquadSize | 26 |
clubAverageAge | 24.9 |
clubLeagueLevel | First Tier |
clubTablePosition | 1 |
season / seasonStartYear | 26/27 / 2026 |
playerId | 315858 |
name | Gianluigi Donnarumma |
shirtNumber | 1 |
positionGroup / position | Goalkeeper / Goalkeeper |
dateOfBirth / age | 1999-02-25 / 27 |
nationality | ["Italy"] |
heightCm | 196 |
foot | right |
joinedClub | 2025-09-01 |
contractExpires | 2030-06-30 |
signedFrom / signedFromClubId | Paris Saint-Germain / 583 |
signedForFee | 30000000 |
marketValue | 45000000 |
playerUrl | https://www.transfermarkt.com/x/profil/spieler/315858 |
Every row repeats the club context, so a multi-club export is readable in a spreadsheet without a single join.
The signing fee, which is not on the page
signedForFee is pulled out of the crest tooltip on the "Signed from" column —
Transfermarkt renders it as Paris Saint-Germain: Ablöse €30.00m, in German
even on the English site. It is null for a free transfer, a loan, an
undisclosed fee, or a youth-team graduate, because none of those is zero.
Money and dates are parsed, not passed through
€45.00m becomes 45000000; 25/02/1999 becomes 1999-02-25; 1,96m becomes
196. The data sorts, sums and filters without a cleaning pass.
⚠️ One thing worth knowing about seasons
Transfermarkt shows different columns for the current season and a past one. Both tables have ten columns, so this is easy to miss:
| Current season | Past season | |
|---|---|---|
contractExpires | ✅ | — |
currentClub / isOnLoanElsewhere | — | ✅ |
| Rows returned | the registered squad (26) | everyone who appeared (44) |
So: ask for the current season if you want contract dates, and ask for a past season if you want to know where a club's loanees actually went. This Actor locates columns by their header text rather than by position, which is what keeps a contract date from being read as a club crest.
Input
{"clubIds": ["281", "https://www.transfermarkt.com/real-madrid/startseite/verein/418"],"searchQueries": ["Bayern Munich"],"maxClubsPerQuery": 1,"season": 2025,"includePlayersOnLoanElsewhere": true,"maxItems": 500}
| Input | What it does |
|---|---|
clubIds | Numeric IDs or any Transfermarkt club URL. The squad, profile and transfers URLs all work — the name in the URL is ignored. |
searchQueries | Club names, when you don't have IDs. |
maxClubsPerQuery | Clubs to take per name. Default 3. |
season | The year a season starts — 2025 is 2025/26. Leave empty for the current season. |
includePlayersOnLoanElsewhere | Past seasons list players who are now elsewhere. Keep them (flagged with isOnLoanElsewhere) or drop them. Default on. |
maxItems | Hard cap on charged rows. Default 500. |
proxyConfiguration | Leave it off — see below. |
Cost lever
One club is 25–45 charged rows, not one. That is the single thing to know
before setting maxItems. Two multipliers follow from it:
searchQueries×maxClubsPerQuery— three names at the default3is up to nine full squads, around 250 rows.- A past season returns roughly 70% more rows than the current one, because
it lists everyone who appeared rather than the registered squad. Turning
includePlayersOnLoanElsewhereoff brings that back down.
Why this is cheap to run
Transfermarkt is fronted by AWS WAF, and the usual answer — a residential proxy at $8/GB on 205 KB pages — is what makes scrapers of this site expensive. It turns out Apify's own egress is not challenged, so this Actor calls Transfermarkt directly and pays nothing for proxy. A residential IP is fetched only if a request is genuinely challenged, and most runs never touch one.
Measured on the platform: four full squads (109 players) in 8.7 s, 66 MB peak, $0.0007 of platform usage — about $0.007 per 1,000 players.
That is also why the Actor start fee is $0.00005 and the default memory is 256 MB. Apify charges the start fee once per gigabyte, so a 4 GB Actor with a $0.10 start fee bills $0.40 before returning a single row. Several Transfermarkt scrapers on the Store do exactly that.
Do not turn on Apify's datacenter proxy. Transfermarkt's WAF challenges Apify's datacenter pool while leaving its direct egress alone — verified, same URLs, same second — so switching it on takes a working run and breaks it.
What makes this different
- The fee each player was signed for, extracted from a German-language tooltip that is not part of the visible table.
- Club totals on every row — squad value, average age, league position — so one row answers "how does this player compare to his own squad" without a join.
- A missing club and an unpublished season are different answers. A club id
Transfermarkt doesn't have returns an unbilled
CLUB_NOT_FOUND; a season it hasn't published yet returns an unbilledSEASON_NOT_AVAILABLEnaming the club and season. Neither is silently dropped, and neither is charged. - The season returned is the season reported.
seasonis read back off the page, never echoed from your input, andrequestedSeasonsits beside it so a fallback is visible rather than inferred. - Columns are found by name, not position. Which is what makes the current/past-season column swap a non-event instead of a silent data corruption.
- You are never billed for a failure, and bad input returns an
INVALID_INPUTrow instead of failing the run. - A weekly canary runs the deployed build against live Transfermarkt and opens a GitHub issue if field coverage drops.
What this Actor does NOT return
- Per-player market value history or transfer history. Those are one
request per player rather than one per club — they belong to the
Transfermarkt Player & Market Value Scraper, which takes the
playerIdthis Actor returns. - Coaching and technical staff. Transfermarkt has them at
/mitarbeiter/verein/{id}; they are not in this Actor's scope. - Contract expiry for a past season, or loan destinations for the current one — Transfermarkt publishes each in only one of the two views. See the seasons note above.
- Anything requiring a Transfermarkt login.
Example runs
One club, current season:
{ "clubIds": ["281"] }
A whole league's worth of clubs, past season, no loanees:
{"clubIds": ["281", "11", "631", "985", "31"],"season": 2024,"includePlayersOnLoanElsewhere": false}
By name:
{ "searchQueries": ["Ajax", "Benfica"], "maxClubsPerQuery": 1 }
Related Actors
Part of a Transfermarkt suite:
- Transfermarkt Player & Market Value Scraper — one player in depth, with the full market-value and transfer histories.
- Transfermarkt Club Squad Scraper — this one.
- Transfermarkt Competition Scraper — league tables, participating clubs and top market values, for leagues and cups.
- Transfermarkt Transfers Scraper — transfer feeds by club, competition or the global latest-transfers stream.
The playerId this Actor returns is the input to the player scraper, and the
competition scraper's clubId is the input to this one.