Transfermarkt Club Squad & Market Value Scraper avatar

Transfermarkt Club Squad & Market Value Scraper

Pricing

from $1.20 / 1,000 squad player scrapeds

Go to Apify Store
Transfermarkt Club Squad & Market Value Scraper

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

Elena Vance

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

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"]}:

FieldValue
clubId / clubName281 / Manchester City
competitionCode / competitionNameGB1 / Premier League
clubTotalMarketValue1310000000
clubSquadSize26
clubAverageAge24.9
clubLeagueLevelFirst Tier
clubTablePosition1
season / seasonStartYear26/27 / 2026
playerId315858
nameGianluigi Donnarumma
shirtNumber1
positionGroup / positionGoalkeeper / Goalkeeper
dateOfBirth / age1999-02-25 / 27
nationality["Italy"]
heightCm196
footright
joinedClub2025-09-01
contractExpires2030-06-30
signedFrom / signedFromClubIdParis Saint-Germain / 583
signedForFee30000000
marketValue45000000
playerUrlhttps://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 seasonPast season
contractExpires
currentClub / isOnLoanElsewhere
Rows returnedthe 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
}
InputWhat it does
clubIdsNumeric IDs or any Transfermarkt club URL. The squad, profile and transfers URLs all work — the name in the URL is ignored.
searchQueriesClub names, when you don't have IDs.
maxClubsPerQueryClubs to take per name. Default 3.
seasonThe year a season starts2025 is 2025/26. Leave empty for the current season.
includePlayersOnLoanElsewherePast seasons list players who are now elsewhere. Keep them (flagged with isOnLoanElsewhere) or drop them. Default on.
maxItemsHard cap on charged rows. Default 500.
proxyConfigurationLeave 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 default 3 is 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 includePlayersOnLoanElsewhere off 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 unbilled SEASON_NOT_AVAILABLE naming the club and season. Neither is silently dropped, and neither is charged.
  • The season returned is the season reported. season is read back off the page, never echoed from your input, and requestedSeason sits 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_INPUT row 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 playerId this 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 }

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.