Skip to content

About

Scrape Transfermarkt football data as clean JSON/CSV - player profiles, market value and transfer history, game logs, squads, league tables, season transfers with fees and the most valuable players - via the ScrapeUnblocker API. CLI + Python library.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Transfermarkt Scraper

CI Python 3.9+ License: MIT Powered by ScrapeUnblocker

Scrape Transfermarkt (transfermarkt.com) football data into clean JSON or CSV. It covers player profiles (market value, contract, agent, citizenship, positions, foot, height, international caps) and the full market value history and transfer history with fees. It also reads a player's game-by-game log (minutes, goals, assists, cards, substitutions, shots, passes) with per-season totals, club squads, league clubs with squad market values and league tables. Add the season's transfers of a whole league with fees, loan flags and each club's net spend, the most valuable players (with position, age, nationality and continent filters) and search to find any id. It's built for football analytics, scouting databases, fantasy football tools, transfer-market research and data journalism, and ships as a CLI and a Python library.

Powered by ScrapeUnblocker. Every page is fetched through the ScrapeUnblocker getPageSource API, so you don't have to handle blocks, rate limits or proxies yourself.

transfermarkt-scraper search haaland -f table
transfermarkt-scraper player 418560 --history --transfers
transfermarkt-scraper games 418560 --season 2025 --summary competition -f table
transfermarkt-scraper squad 281 -f csv -o man-city.csv
transfermarkt-scraper table ES1 --season 2024 -f table
transfermarkt-scraper market-values --age-group u21 --position-group forwards -f table
transfermarkt-scraper transfers GB1 --season 2025 --balances -f table

Features

  • Player profiles (player): name, full name, shirt number, date of birth and age, place and country of birth, every citizenship, height in cm, foot, main and other positions, current club and league, joined date, contract end, contract option and last extension, loan details (parent club and its contract), agent, outfitter, current market value and its update date, national team with caps and goals, honours (titles with counts), social media links and portrait URL. Retired players get their last club and retirement date.
  • Market value history (market-value-history, or player --history): every value Transfermarkt has published, with date, club and age.
  • Transfer history (transfer-history, or player --transfers): date, season, from and to club (with ids), market value at the time and the fee as a number plus its kind: fee, free, loan, loan_fee, end_of_loan or unknown.
  • Game log (games): every game of a player's career, club and national team, newest first. Each game has the date and kickoff time, competition, matchday, home/away, opponent, score and result (W/D/L, extra time or penalty shoot-out), whether the player started, was a sub, was unused, injured or not in the squad, minutes, goals, assists, penalty and own goals, yellow and red card minutes, substitution minutes, captaincy, shirt number, position played, and where Transfermarkt has them shots, shots on target, passes, tackles, fouls and offsides. Filter by season and competition, or --summary competition|season for appearances, starts, minutes, goals, assists, cards and minutes per goal per season.
  • Squads (squad): shirt number, position, date of birth, age, nationalities, height, foot, joined date, previous club and the fee paid, contract end, market value, captain, injury status and recent loan returns, for the current or any past season (past seasons show each player's current club).
  • Leagues (competition): league level, reigning and record champion, UEFA coefficient, number of teams and players, foreigners, average age, average and total market value, most valuable player, and every club with squad size, average age, foreigners and squad market value. Cups such as the Champions League list their participating clubs.
  • League tables (table): position, played, won, drawn, lost, goals for and against, goal difference, points and last week's position, for any season. Leagues split into groups (MLS conferences) get a group name.
  • Most valuable players (market-values): worldwide with Transfermarkt's filters (position or position group, age group, year of birth, nationality, continent), or within one league, 25 per page up to 500 players.
  • League transfers (transfers): every arrival and departure of every club in a league season: player, age, nationalities, position, market value, the other club and its country, fee and fee kind, transfer id. Filter by window (summer/winter), loans, internal moves, club and direction. --balances gives each club's spending, income and net balance.
  • Search (search): players, clubs or competitions by name, with their ids.
  • Robust: retries with backoff on timeouts, transient errors and error pages, and raises NotFoundError for unknown ids.
  • Output as JSON, JSON Lines, CSV or a terminal table; typed dataclasses in Python. All amounts are integers in EUR.

How it works

Transfermarkt pages are server-rendered HTML tables. The scraper reads them with BeautifulSoup and finds each column by its header label rather than by position. This matters because the same table has different columns on different pages: a current squad has a contract column, while a past season has a "current club" column instead. Amounts such as €1.50bn, dates such as 25/02/1999 (27) and heights such as 1,96m are turned into numbers and ISO dates.

Three datasets come from the JSON endpoints Transfermarkt's own pages load:

  • the market value chart (/ceapi/marketValueDevelopment/graph/<id>)
  • the transfer history (/ceapi/transferHistory/list/<id>)
  • the game log behind the stats pages (/ceapi/performance-game/<id>)

The game log only holds club and competition ids, so their names are looked up in batches of 100 and cached.

Each page or JSON response is one request to ScrapeUnblocker's getPageSource endpoint; no browser rendering is needed.

Install

git clone https://github.com/ScrapeUnblocker/transfermarkt-scraper.git
cd transfermarkt-scraper
python -m pip install .

Get an API key at scrapeunblocker.com and expose it as SCRAPEUNBLOCKER_KEY (see .env.example):

export SCRAPEUNBLOCKER_KEY=your_key_here

Requires Python 3.9+. Runtime dependencies are the official scrapeunblocker SDK and beautifulsoup4.

CLI usage

transfermarkt-scraper [--proxy-country CC] [--retries 3] [-v] COMMAND ...

  search                QUERY [--type players|clubs|competitions] [--pages N] [--max N]
  player                PLAYER ... [--history] [--transfers]
  market-value-history  PLAYER
  transfer-history      PLAYER
  games                 PLAYER [--season S] [--competition ID] [--played-only]
                        [--summary competition|season] [--no-names]
  squad                 CLUB [--season S]
  competition           COMPETITION [--season S]
  table                 COMPETITION [--season S]
  market-values         [--competition ID] [--position ID|NAME] [--position-group GROUP]
                        [--age-group AGE] [--born YEAR] [--country-id ID]
                        [--continent NAME] [--pages N] [--max N]
  transfers             COMPETITION [--season S] [--window summer|winter]
                        [--loans all|only|no-returns|exclude] [--internal]
                        [--direction in|out] [--club CLUB] [--balances]

  every command: [-f json|jsonl|csv|table] [-o FILE]

Ids. PLAYER, CLUB and COMPETITION are Transfermarkt ids or URLs. The id is in every Transfermarkt URL (.../profil/spieler/418560, .../startseite/verein/281, .../startseite/wettbewerb/GB1), and search finds it for you. Some common competition ids:

Id Competition Id Competition
GB1 Premier League GB2 Championship
ES1 LaLiga L1 Bundesliga
IT1 Serie A FR1 Ligue 1
NL1 Eredivisie PO1 Liga Portugal
BE1 Jupiler Pro League TR1 Süper Lig
SC1 Scottish Premiership GR1 Super League 1
MLS1 Major League Soccer BRA1 Brasileirão Série A
SA1 Saudi Pro League JAP1 J1 League
CL UEFA Champions League EL UEFA Europa League

Seasons. A season is the year it starts in: --season 2025 (or 2025/26, 25/26) is the 25/26 season. For leagues played in one calendar year (MLS, Brasileirão) it is that year. Without --season you get the current season.

Option Meaning
games --summary competition: totals per season and competition; season: per season over all competitions.
games --no-names Skip the club/competition name lookups (ids only, one request).
market-values --position A position id or name: goalkeeper, centre-back, left-back, right-back, defensive-midfield, central-midfield, attacking-midfield, left-winger, right-winger, second-striker, centre-forward, ...
market-values --position-group goalkeepers, defenders, midfielders, forwards.
market-values --age-group u17 ... u23, 23-30, o30, o32, o34.
market-values --continent uefa, conmebol, concacaf, caf, afc, ofc.
market-values --competition One league's most valuable players (the other filters don't apply).
transfers --loans all (default), only loans, no-returns (drop players coming back from loan), exclude loans.
transfers --internal Include moves inside a club (e.g. from its U21 or B team).

Requests per command:

Command Requests
search, market-values One per result page.
player One, plus one for --history and one for --transfers.
games One, plus usually two name lookups (none with --no-names).
competition One (two for a cup).
squad, table, transfers One each.

The CLI prints the request count to stderr.

Library usage

from transfermarkt_scraper import TransfermarktScraper, summarize

tm = TransfermarktScraper()  # reads SCRAPEUNBLOCKER_KEY

# Find ids
hit = tm.search("haaland")[0]  # PlayerHit(player_id='418560', ...)
club = tm.search("real madrid", "clubs")[0]  # ClubHit: club_id, league, squad value

# A player with market value and transfer history
player = tm.player(hit.player_id, market_value_history=True, transfer_history=True)
print(player.market_value_eur, player.contract_until, player.agent)
for move in player.transfer_history:
    print(move.date, move.from_club, "->", move.to_club, move.fee_eur, move.fee_kind)

# Game log and season totals
games = tm.games(hit.player_id, season=2025)  # 25/26, all competitions
league_games = tm.games(hit.player_id, competition="GB1", played_only=True)
for row in summarize(games, by="competition"):
    print(row.season, row.competition, row.appearances, row.goals, row.assists, row.minutes)

# Clubs and leagues
squad = tm.squad(281)  # Manchester City, current season
old_squad = tm.squad(281, season=2020)
league = tm.competition("GB1")  # overview + clubs with squad values
table = tm.table("ES1", season=2024)

# Market values
top = tm.market_values(max_pages=4)  # top 100 worldwide
wonderkids = tm.market_values(age_group="u21", position=14, max_pages=2)
bundesliga = tm.market_values(competition="L1", max_results=50)

# A league's transfer window
moves, balances = tm.transfers_page("GB1", 2025, window="summer")
big_buys = [m for m in moves if m.direction == "in" and (m.fee_eur or 0) >= 50_000_000]

Every result is a dataclass with to_dict(). tm.requests counts the API requests made. NotFoundError is raised for an unknown id.

Example output

transfermarkt-scraper games 418560 --season 2025 --summary competition -f table:

SEASON  COMPETITION               CLUB             APPS  MIN   G   A  YC  RC
2026    World Cup                 Norway           5     465   7   1  0   0
25/26   International Friendlies  Norway           3     180   1   0  0   0
25/26   Premier League            Manchester City  35    2959  27  8  2   0
25/26   FA Cup                    Manchester City  4     231   3   1  0   0
25/26   EFL Cup                   Manchester City  3     199   0   0  0   0
25/26   UEFA Champions League     Manchester City  10    755   8   0  0   0
6 rows, 3 requests

transfermarkt-scraper squad 281 -f table (first rows):

#   ID       NAME                  POSITION            AGE  CONTRACT    VALUE
1   315858   Gianluigi Donnarumma  Goalkeeper          27   2030-06-30  €45.00m
28  229604   Gerónimo Rulli        Goalkeeper          34   2028-06-30  €5.00m
13  116648   Marcus Bettinelli     Goalkeeper          34   2027-06-30  €300k
6   392757   Marc Guéhi            Centre-Back         26   2031-06-30  €75.00m
3   258004   Rúben Dias (C)        Centre-Back         29   2029-06-30  €50.00m

transfermarkt-scraper transfers GB1 --season 2025 --balances -f table (first rows):

CLUB                     IN  OUT  SPENT     RECEIVED  BALANCE
Arsenal FC               15  16   €297.57m  €13.88m   -€283.69m
Aston Villa              23  22   €83.50m   €57.85m   -€25.65m
AFC Bournemouth          21  21   €181.10m  €310.41m  €129.31m
Brentford FC             17  19   €108.70m  €159.61m  €50.91m
Brighton & Hove Albion   30  30   €82.00m   €149.95m  €67.95m
20 clubs, 1 requests

transfermarkt-scraper market-values --age-group u21 --position-group forwards -f table (first rows):

#   ID       NAME                   POSITION        AGE  CLUB                     VALUE
1   937958   Lamine Yamal           Right Winger    19   FC Barcelona             €220.00m
2   1390649  Yan Diomande           Right Winger    19   Real Madrid              €120.00m
3   1056993  Estêvão                Right Winger    19   Chelsea FC               €80.00m
4   955357   Junior Kroupi          Centre-Forward  20   AFC Bournemouth          €70.00m

transfermarkt-scraper player 418560 --history --transfers (abridged):

{
  "player_id": "418560",
  "name": "Erling Haaland",
  "full_name": "Erling Braut Håland",
  "date_of_birth": "2000-07-21",
  "age": 26,
  "citizenship": ["Norway"],
  "height_cm": 195,
  "foot": "left",
  "position": "Centre-Forward",
  "club": "Manchester City",
  "club_id": "281",
  "league": "Premier League",
  "joined": "2022-07-01",
  "contract_until": "2034-06-30",
  "last_contract_extension": "2025-01-17",
  "agent": "Rafaela Pimenta",
  "market_value_eur": 220000000,
  "market_value_updated": "2026-07-22",
  "national_team": "Norway",
  "caps": 59,
  "international_goals": 65,
  "market_value_history": [
    {"date": "2026-06-03", "market_value_eur": 200000000, "club": "Manchester City", "age": 25},
    {"date": "2026-07-22", "market_value_eur": 220000000, "club": "Manchester City", "age": 26}
  ],
  "transfer_history": [
    {
      "date": "2022-07-01",
      "season": "22/23",
      "from_club": "Dortmund",
      "from_club_id": "16",
      "to_club": "Man City",
      "to_club_id": "281",
      "market_value_eur": 150000000,
      "fee_eur": 60000000,
      "fee_kind": "fee",
      "fee": "€60.00m",
      "upcoming": false,
      "url": "https://www.transfermarkt.com/erling-haaland/transfers/spieler/418560/transfer_id/3845482"
    }
  ]
}

One game from transfermarkt-scraper games 418560 (some fields omitted):

{
  "game_id": "4899313",
  "date": "2026-10-17",
  "kickoff_utc": "2026-10-17T14:00:00+00:00",
  "season": "26/27",
  "competition_id": "GB1",
  "competition": "Premier League",
  "matchday": 7,
  "venue": "home",
  "club": "Manchester City",
  "opponent": "Ipswich Town",
  "goals_for": 3,
  "goals_against": 2,
  "result": "W",
  "participation": "played",
  "started": true,
  "minutes": 90,
  "goals": 2,
  "assists": 0,
  "yellow_card_minute": null,
  "position": "Centre-Forward",
  "shots": 5,
  "shots_on_target": 2,
  "passes": 15,
  "passes_completed": 9,
  "url": "https://www.transfermarkt.com/spielbericht/index/spielbericht/4899313"
}

Examples

  • examples/player_report.py: find a player by name and save profile, market value history, transfers and per-season totals as one JSON report.
  • examples/league_snapshot.py: join a league table with each club's squad market value in one CSV and print the clubs far above or below their value rank.
  • examples/net_spend.py: rank every club of the Big Five leagues by transfer balance for a season.

Notes

  • Transfermarkt's market values are its own estimates, and its pages are in English on transfermarkt.com with amounts in EUR.
  • Data on transfermarkt.com belongs to Transfermarkt. Respect its terms of use, keep request volumes reasonable and cache what you have already fetched. This project is not affiliated with Transfermarkt.

Project layout

src/transfermarkt_scraper/
  __init__.py   package exports and version
  scraper.py    TransfermarktScraper: fetching, retries, paging, name lookups
  parsing.py    HTML and JSON parsers (BeautifulSoup) and the season summary
  models.py     dataclasses for every result type
  urls.py       URL builders, id/season parsing and filter vocabularies
  values.py     money, fee, date, height and number parsing
  export.py     JSON / JSONL / CSV / table output
  cli.py        the transfermarkt-scraper command
examples/       runnable scripts (JSON report, CSV exports)
tests/          offline tests with real, trimmed Transfermarkt pages (no API calls)

Development

python -m pip install -e ".[dev]"
make lint     # ruff check + format check
make test     # pytest, fully offline: the API client is mocked

pre-commit install runs ruff on every commit (see .pre-commit-config.yaml).

Links

License

MIT © 2026 ScrapeUnblocker

About

Scrape Transfermarkt football data as clean JSON/CSV - player profiles, market value and transfer history, game logs, squads, league tables, season transfers with fees and the most valuable players - via the ScrapeUnblocker API. CLI + Python library.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages