Transfermarkt Scraper avatar

Transfermarkt Scraper

Pricing

Pay per event

Go to Apify Store
Transfermarkt Scraper

Transfermarkt Scraper

Extract structured football data from Transfermarkt: player profiles, current and peak market values, the full value history, complete transfer history with clubs, dates and fees as integers, plus per-competition career stats (appearances, goals, assists). One flat JSON record per player.

Pricing

Pay per event

Rating

5.0

(2)

Developer

WebDataLabs

WebDataLabs

Maintained by Community

Actor stats

4

Bookmarked

164

Total users

10

Monthly active users

a day ago

Last modified

Share

Transfermarkt Scraper - Football Player Data

Paste Transfermarkt player URLs, get one clean JSON record per player: profile, current and peak market value, the complete transfer history with fees, and whole-career appearances, goals, assists and minutes per competition.

What you get

One flat record per player, ready for spreadsheets, n8n, Zapier or a database:

Field groupFields
IdentityplayerId, playerName, fullName (name in home country), profileUrl
PersonaldateOfBirth, age, placeOfBirth, nationality, secondNationality, height, foot
Positionposition, detailedPosition
Club & contractcurrentClub, currentClubId, joinedDate, contractExpiry, contractOption
Market valuemarketValue (integer), marketValueCurrency, marketValueFormatted, highestMarketValue, highestMarketValueDate
HistorytransferHistory[] (season, date, both clubs + ids, market value at the time, fee text, feeNumeric), marketValueHistory[] (full valuation time series)
Career statscareerStats[] — one row per competition and club: competition, competitionId, club, clubId, appearances, goals, assists, minutesPlayed
Extrasagent, agentUrl, outfitter, instagramUrl, achievements[]
MetadatascrapedAt, scrapedBy

Nulls mean "Transfermarkt does not publish this" — the Actor never writes N/A, 0, Unknown or any other placeholder into a field it could not fill.

Quick start

{
"urls": [
"https://www.transfermarkt.us/kylian-mbappe/profil/spieler/342229",
"https://www.transfermarkt.us/erling-haaland/profil/spieler/418560"
],
"maxItems": 10
}

That is the whole configuration. Everything else has a working default.

Input parameters

ParameterTypeDefaultDescription
urlsarrayMbappé + HaalandRequired. Transfermarkt player URLs, one per player.
maxItemsinteger100Hard cap on players scraped and billed. Extra URLs are reported as skipped.
useProxiesbooleanfalseRoute through Apify Proxy. Only needed if you see blocks.
proxyGroupsarray["DATACENTER"]DATACENTER (cheap, recommended) or RESIDENTIAL.
delayBetweenRequestsinteger1500Milliseconds between player requests.

Supported URLs

Any Transfermarkt player URL works — the Actor rewrites it to the profile page automatically:

https://www.transfermarkt.us/kylian-mbappe/profil/spieler/342229 profile
https://www.transfermarkt.us/filip-stankovic/geruechte/spieler/552223 rumours -> profile
https://www.transfermarkt.de/x/leistungsdaten/spieler/342229 stats page -> profile
https://www.transfermarkt.co.uk/x/transfers/spieler/342229 transfers -> profile

All regional hosts are accepted (.us, .com, .de, .co.uk, .es, .it, .fr). Output is always in English.

Not supported: club/squad URLs (/verein/) and competition URLs (/wettbewerb/). Passing one is rejected before any billable work with a message telling you what to pass instead.

Sample output

{
"playerId": "421873",
"playerName": "Elia Caprile",
"profileUrl": "https://www.transfermarkt.us/elia-caprile/profil/spieler/421873",
"fullName": null,
"dateOfBirth": "2001-08-25",
"age": 24,
"placeOfBirth": "Verona",
"nationality": "Italy",
"secondNationality": null,
"height": "1.91m",
"foot": "right",
"position": "Goalkeeper",
"detailedPosition": "Goalkeeper",
"currentClub": "Cagliari Calcio",
"currentClubId": "1390",
"joinedDate": "2025-07-01",
"contractExpiry": "2029-06-30",
"contractOption": null,
"marketValue": 15000000,
"marketValueCurrency": "EUR",
"marketValueFormatted": "€15.00m",
"highestMarketValue": 15000000,
"highestMarketValueDate": "2025-12-23",
"agent": "GB1",
"agentUrl": "https://www.transfermarkt.us/gb1-football-agency/beraterfirma/berater/11148",
"outfitter": null,
"instagramUrl": "https://www.instagram.com/eliacaprile/",
"transferHistory": [
{
"season": "25/26",
"date": "2025-07-01",
"clubFrom": "Napoli",
"clubFromId": "6195",
"clubTo": "Cagliari",
"clubToId": "1390",
"marketValue": 9000000,
"fee": "€8.20m",
"feeNumeric": 8200000
},
{
"season": "24/25",
"date": "2025-01-07",
"clubFrom": "Napoli",
"clubFromId": "6195",
"clubTo": "Cagliari",
"clubToId": "1390",
"marketValue": 6000000,
"fee": "loan transfer",
"feeNumeric": null
}
],
"marketValueHistory": [
{ "date": "2019-09-01", "age": 18, "club": "Chievo Primavera", "clubId": "14845", "value": 50000 }
],
"careerStats": [
{ "competition": "Serie A", "competitionId": "IT1", "club": "Cagliari Calcio", "clubId": "1390", "appearances": 56, "goals": 0, "assists": 0, "minutesPlayed": 5040 },
{ "competition": "Serie B", "competitionId": "IT2", "club": "SSC Bari", "clubId": "332", "appearances": 37, "goals": 0, "assists": 0, "minutesPlayed": 3330 },
{ "competition": "Serie A", "competitionId": "IT1", "club": "SSC Napoli", "clubId": "6195", "appearances": 4, "goals": 0, "assists": 0, "minutesPlayed": 324 }
],
"achievements": [
{ "title": "Serie A winner", "count": 1, "season": null, "club": null, "clubId": null, "competition": null, "competitionId": null }
],
"scrapedAt": "2026-07-27T10:28:17.818+00:00",
"scrapedBy": "transfermarkt-scraper"
}

All money fields are integers in base currency units (8200000 = €8.20m), so you can sort and aggregate them directly.

careerStats is truncated above — the real record has 20 rows, one per competition and club Caprile actually played in. He is a goalkeeper, hence the zeroes in goals and assists: those are Transfermarkt's real counts, not placeholders.

Dataset views

  1. Overview — name, age, nationality, position, club, market value
  2. Market Value — current and peak valuations
  3. Full Details — every field

How you know a run worked

Every run writes a machine-readable OUTPUT record to the key-value store:

{
"outcome": "ok",
"message": "Delivered 2 player record(s) for all 2 requested URL(s).",
"urlsSubmitted": 2,
"urlsAttempted": 2,
"itemsDelivered": 2,
"results": [
{ "inputUrl": "...", "scrapedUrl": "...", "playerId": "342229", "status": "succeeded", "reason": null }
]
}
  • outcome: "ok" — every requested player was delivered.
  • outcome: "partial" — some URLs were skipped or failed; each one is listed with its reason, and the run log says so too.
  • outcome: "empty" — nothing was delivered, and the run status is FAILED with the reason in the status message. A run that scrapes nothing will never report success.

itemsDelivered is read back from the dataset itself, so it is never larger than what actually landed.

Use cases

  • Sports analytics — track valuation trends and transfer fee patterns over time
  • Scouting — build a watchlist and monitor market value movements
  • Journalism — transfer market research with sourced fees and dates
  • Fantasy football — squad metadata and player background
  • Automation — feed n8n / Zapier / Make / Google Sheets

Technical details

  • HTTP only (Cheerio + got-scraping) — Transfermarkt profile pages are server-rendered, so no browser is started. Runs are typically well under 30 seconds per player and cost a fraction of a browser-based scraper.
  • Data sources — the profile page HTML plus Transfermarkt's own ceapi/transferHistory, ceapi/marketValueDevelopment and ceapi/performance-game JSON endpoints. Career statistics are aggregated from the same per-game feed the site's own stats widget reads, so the numbers match the ones on the page.
  • Proxies optional — works without a proxy in most cases; a datacenter proxy is enough if you hit rate limits.
  • Retries — a request is retried only when the response is actually unusable (block page, redirect, 5xx). Deterministic failures are reported, not retried.

Limitations (read before buying)

  • careerStats counts appearances, not squad call-ups. Only games Transfermarkt marks as played are counted; being an unused substitute, injured or absent produces no appearance and no row. Transfermarkt does not record assists for every competition or era, so assists: 0 can mean "none recorded" as well as "none made".
  • Players only. Club, squad and competition scraping are not implemented. Passing such a URL fails fast with a clear message.
  • No search. You supply the URLs; there is no keyword lookup.
  • Retired players have no market value on Transfermarkt, so marketValue, marketValueFormatted and marketValueHistory are null for them. That is the site's data, not a scraping failure.
  • Public data only. No login, no restricted content, no live/real-time feed.

Pricing

Pay-per-event: you are charged per player record actually written to the dataset. A URL that produces nothing is not charged. Current per-event prices are shown in the Apify Console when you start a run, on top of Apify platform compute.

API integration

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('webdatalabs/transfermarkt-scraper').call({
urls: ['https://www.transfermarkt.us/kylian-mbappe/profil/spieler/342229'],
maxItems: 1,
});
// Always check the run outcome, not just the status
const { value: outcome } = await client.keyValueStore(run.defaultKeyValueStoreId).getRecord('OUTPUT');
console.log(outcome.outcome, outcome.message);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items[0].marketValue, items[0].transferHistory);
from apify_client import ApifyClient
client = ApifyClient("YOUR_TOKEN")
run = client.actor("webdatalabs/transfermarkt-scraper").call(run_input={
"urls": ["https://www.transfermarkt.us/kylian-mbappe/profil/spieler/342229"],
"maxItems": 1,
})
outcome = client.key_value_store(run["defaultKeyValueStoreId"]).get_record("OUTPUT")["value"]
print(outcome["outcome"], outcome["message"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item["playerName"], item["marketValue"])

FAQ

Do I need a Transfermarkt account? No. Only publicly accessible pages are read.

Can I scrape many players at once? Yes — put every player URL in urls and raise maxItems.

What if I get blocked? Set useProxies: true with DATACENTER first; switch to RESIDENTIAL only if that still fails.

Do the career stats match the numbers on the Transfermarkt page? Yes. They are aggregated from the same per-game feed the site's own stats widget reads, using Transfermarkt's own definition of an appearance. Rows are split by club as well as competition, so a player's Serie A record appears once per club rather than merged into a single line.

Why is marketValue null for some players? Transfermarkt publishes no value for them (typically retired players). Check the OUTPUT record — if the run says ok, the page really had no value.

Will a run ever silently return nothing? No. A run that delivers zero records finishes as FAILED with the reason in the status message and in OUTPUT.


Explore more of our actors

ActorDescription
Shopify Store IntelligenceShopify store analysis for competitive intelligence

Need this data as a managed feed?

If you would rather receive this data on a schedule than run the Actor yourself, we can build and operate the feed: the fields you specify, the cadence you set, delivered to a dataset, S3, a webhook, or your database. Every scheduled run is checked against an agreed shape rather than assumed to be fine, missing values are reported as null instead of filled with placeholders, and repairing the collector when the source changes is covered by the monthly rate. Priced as a one-time setup fee plus a monthly rate, scoped in writing before anything is built.

Contact: support@webdatalabs.net

Leave a review

Is this actor saving you time? A quick review on the Store helps other people find it — and tells us which fields to add next. Rate this actor →

This actor is a general-purpose tool for analyzing publicly accessible web data. The user bears sole responsibility for ensuring their specific use complies with applicable laws (GDPR/DSGVO, copyright law), the target website's Terms of Service, and Apify's Terms of Service. The provider (webdatalabs) expressly disclaims liability for any unauthorized or unlawful use. By using this actor, the user agrees to indemnify the provider against any third-party claims arising from their use of the data.


This tool is not affiliated with Transfermarkt. All trademarks belong to their respective owners.