# Transfermarkt Scraper — Market Values & Transfers | $3/1K (`bovi/transfermarkt-scraper`) Actor

Scrape public Transfermarkt player data for profiles, squads, rankings, values, and transfers. Get structured player records. Run with supported inputs; no login or API key. Pricing: $0.003 per player record plus $0.00005 per Actor Start event.

- **URL**: https://apify.com/bovi/transfermarkt-scraper.md
- **Developed by:** [Vitalii Bondarev](https://apify.com/bovi) (community)
- **Categories:** Other, MCP servers
- **Features:** Limited permissions
- **Stats:** 17 total users, 2 monthly users, 99.4% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.91 / 1,000 player records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Transfermarkt Player Data Scraper

This Actor reads public Transfermarkt pages and the site's own JSON data (`ceapi`) to extract player profiles, squads, rankings, market values and transfers.

- Scrape individual player profiles, club squads or whole leagues.
- Extract current and peak market values, contracts, biographies and transfer history (profile mode; club, league and ranking rows lack these fields).
- Search player names or collect the most-valuable-players ranking.
- Use `market_value_source`, `parse_confidence` and `warnings` to inspect data quality.
- No login or API key is required; Apify Proxy is optional.

### What you get

Each player record can include the following fields. `null` means the source does not provide a value; `warnings` is `[]` when there are no warnings.

| Field family | Fields | Type | Empty value |
|---|---|---|---|
| Identity | `player_id`, `name`, `full_name`, `shirt_number` | string | `null` when missing or empty |
| Club and role | `current_club`, `league`, `position` | string | `null` when absent |
| Biography | `age` | integer | `null` |
| Biography | `date_of_birth` | string (`DD/MM/YYYY`) | `null` |
| Biography | `place_of_birth`, `height`, `foot` | string | `null` when absent |
| Biography | `nationality` | array of strings | `null` |
| Contract and representation | `contract_until`, `agent`, `outfitter` | string | `null` when absent |
| Links | `profile_url`, `image_url` | string | `null` when absent |
| Numeric values | `market_value`, `market_value_peak` | integer | `null` |
| Display values | `market_value_str`, `market_value_currency`, `market_value_peak_str` | string | `null` |
| Peak date | `market_value_peak_date` | string (`DD/MM/YYYY`) | `null` |
| Value provenance | `market_value_source` | string (`html`, `ceapi`, `html+ceapi`) | `null` when there is no value |
| Value history | `market_value_history` | array of objects | `null` when not included or unavailable |
| Transfers | `transfers` | array of objects | `null` in club, league and rankings rows; `[]` in profile mode when there are none |
| Quality | `parse_confidence` | number, 0 to 1 | never `null` |
| Quality | `warnings` | array of strings | `[]` |
| Metadata | `scraped_at` | ISO 8601 string | never `null` |

- `market_value` is an integer in the currency shown in `market_value_currency`; no conversion is applied.
- `market_value_history` is included in profile mode when `includeMarketValueHistory` is enabled, with `date`, `value`, `value_str` and `club`.
- Profile URLs and profiles resolved from `search` provide transfer data when available. Transfer entries contain `season`, `date`, `from_club`, `to_club`, `fee`, `fee_str`, `fee_type` and `market_value_str`. `fee_type` is `fee`, `free`, `loan`, `end_of_loan` or `unknown`, or `null` when there is no fee text.

### Sample output

This club-squad row is from a `leagueCodes` run (`TDeC`, `maxResults: 3`) and shows 17 of 30 fields. Club, league and rankings rows have the three peak fields, `market_value_history` and `transfers` as `null`; profile mode fills peak fields and transfers, and fills history only with `includeMarketValueHistory: true`.

```json
{
  "player_id": "643063",
  "name": "Diego Romero",
  "current_club": "Universitario de Deportes",
  "league": "Liga 1 Clausura",
  "position": "Goalkeeper",
  "age": 25,
  "nationality": ["Peru"],
  "market_value": 1000000,
  "market_value_str": "€1.00m",
  "market_value_source": "html",
  "market_value_peak": null,
  "market_value_peak_date": null,
  "market_value_history": null,
  "transfers": null,
  "parse_confidence": 1,
  "warnings": [],
  "scraped_at": "2026-10-27T07:07:01.630603+00:00"
}
```

### Pricing

This is Pay per event. The listed events are the Actor Start event and the primary dataset-item event for each football player record. Platform usage is not billed separately because this Actor does not use the Pay per event + usage option.

| Event | When it applies |
|---|---|
| `apify-actor-start` — Actor Start | Charged once per GB of run memory, minimum one event |
| `apify-default-dataset-item` — player record | For one football player profile pushed to the dataset |

On the Free plan, 500 records cost 500 × $0.003 + $0.00005 = $1.50005, with one Actor Start event.

| Plan | Player-record event | Actor Start event |
|---|---:|---:|
| Free | $0.003 per record | Free plan: $0.00005 per event |
| Bronze | $0.00297 per record | $0.0000495 per event |
| Silver | $0.00294 per record | $0.000049 per event |
| Gold | $0.00291 per record | $0.0000485 per event |
| Platinum | $0.00288 per record | $0.000048 per event |
| Diamond | $0.00285 per record | $0.0000475 per event |

On the Free plan, 1,000 player records cost 1,000 × $0.003 = $3.00 and 10,000 player records cost 10,000 × $0.003 = $30.00. Each run also adds the Free plan's $0.00005 for the Actor Start event.

The Apify Free plan's $5 monthly platform usage credit covers 1,666 player records after one Actor Start event: floor((5 − 0.00005) / 0.003) = 1,666. This is a billing figure, not a promise that an input returns that many records.

The `transfer-record` and `mv-history-point` events have no price, so you are not charged for them.

### How to use

1. Choose one or more modes: `playerUrls`, `clubUrls`, `leagueCodes`, `search` or `rankings`.
2. Enter Transfermarkt profile, squad or competition URLs, or supported bare IDs and codes.
3. Clear the prefilled `playerUrls` value `28003` and `search` value `Haaland` unless you want those records; every filled mode runs.
4. Set `maxResults` for club, league, rankings and search requests; direct player mode returns one record per supplied URL.
5. Enable `includeMarketValueHistory` for complete dated valuation points in profile requests.
6. Add `proxyConfiguration` only when you want Apify Proxy.
7. Run the Actor, inspect the returned quality fields, and repeat through Apify Schedules, with weekly as a starting point.

Example input for the first 100 Premier League records:

```json
{
  "leagueCodes": ["GB1"],
  "maxResults": 100,
  "includeMarketValueHistory": false
}
```

Export the resulting dataset as JSON or CSV, or read it through the Apify dataset API.

### Input parameters

| Parameter | What it does | Default |
|---|---|---|
| `playerUrls` | Array of Transfermarkt player profile URLs or bare numeric player IDs. Each resolved player is scraped as a profile. | Empty when omitted |
| `clubUrls` | Array of Transfermarkt squad URLs or bare numeric club IDs. Each squad player becomes one record. | Empty when omitted |
| `leagueCodes` | Array of competition codes or URLs. The Actor finds member clubs and processes their squads. | Empty when omitted |
| `search` | Free-text player name query. Matching players are resolved to profiles. | Empty when omitted |
| `rankings` | When `true`, scrapes the most-valuable-players ranking. | `false` |
| `maxResults` | Maximum player records for club, league, rankings and search requests. Accepted range: 1–10000. | `100` |
| `includeMarketValueHistory` | Adds full dated market-value history in profile mode for `playerUrls` and `search`. | `false` |
| `proxyConfiguration` | Optional Apify Proxy configuration. | `useApifyProxy: false` |
| `disableUsageStats` | Set to `true` to turn off the run summary described in Usage statistics. | `false` |

Common competition codes include `GB1` for the Premier League, `ES1` for LaLiga, `IT1` for Serie A, `L1` for the Bundesliga, `FR1` for Ligue 1 and `MLS1` for MLS. The Actor also accepts 2–8-character codes starting with a letter or competition URLs, with case preserved.

### Limits and edge cases

| Condition | What you see | What to do |
|---|---|---|
| No input mode is filled, or no player is delivered | The run ends Failed. No player-record events are charged, but the Actor Start event is charged when the run starts. `OUTPUT` contains `message`, `emptyReason` (`NO_INPUT`, `NO_MATCH` or `BLOCKED`) and `totalItems: 0`; `NO_MATCH` and `BLOCKED` also contain `failures`, `scrapedAt` and `failedSources`, while `NO_INPUT` contains `error: "no_input"`. | Fill at least one input mode. For `NO_MATCH` or `BLOCKED`, check `failedSources`. |
| A player is skipped or partly filled | A failed profile fetch, missing identity, or `parse_confidence` below `0.5` prevents the record from being pushed or charged. A failed `ceapi` call can still deliver the profile with `transfers: []` and null peak fields. | Inspect `warnings` and the returned fields. |
| A league page or club produces no valid records | The league page and each club get up to three attempts on fresh sessions. A 404 or missing competition is `NO_MATCH`; otherwise it is `BLOCKED`. Empty sources appear in `failedSources`; no overall records means Failed. | Check the competition code or URL and review `failedSources`. |
| Some sources succeed and others fail | The run ends Succeeded. Delivered records are pushed and `OUTPUT` contains totals, failures, `scrapedAt`, `failedSources` and a `message` beginning `Partial: …`. | Use delivered records and inspect failed sources before rerunning. |
| Records are produced during a run | Profile and search rows arrive one player at a time; club and league rows one club at a time; rankings rows after the ranking is read. Each batch is charged after its push. | Use the dataset as rows arrive. |
| An abort, timeout or failure stops a run | Pushed rows remain and stay charged. `OUTPUT` is written only at the end or when the run fails for lack of results. Aborted or timed-out runs have no `OUTPUT`; an interrupted rankings run can leave no ranking rows. | Check the dataset and rerun if needed. |
| `maxResults` is reached | It applies per club URL, league code, search and ranking, and is ignored for `playerUrls`. | Increase it within 1–10000. |
| A league contains many clubs | One competition input processes at most 40 clubs. | Split the work across inputs. |
| Rankings are enabled | Pages contain 25 rows. Reading stops at `maxResults`, an empty page, a page with fewer than 25 rows, or a fetch failure. Earlier rows remain delivered and the failure appears in `failedSources`. | Set `maxResults` to the desired number of ranking rows. |
| Fetch errors occur | HTTP 429, 403, 503, block-like or shorter-than-12,000-character HTML is retried after 10 s, 20 s and 40 s, for up to four requests. HTTP 404, other non-2xx responses, network errors, empty bodies and non-JSON `ceapi` responses stop without retry. A failed profile is skipped; a failed `ceapi` call can still deliver the profile with `transfers: []` and null peak fields. | Check the input or run again for a transient error. |

### FAQ

#### How much does it cost to scrape player data?

The Free plan charges $0.003 per player record and $0.00005 per Actor Start event. All plan rates are in Pricing.

#### What happens when no players are returned?

The run ends as Failed and no player-record events are charged. The reason is in `OUTPUT`.

#### Why might a player or league not work?

A player can be skipped if its page fails, its identity is missing, or confidence is below `0.5`. Blocked responses are retried under the conditions in Limits and edge cases.

#### Can I use this public data legally?

The Actor reads publicly available player, club and market-value pages from Transfermarkt and requires no authentication. Whether a particular use is allowed depends on the source terms and applicable law for your use case; this README provides no legal guarantee.

### Related actors

- [Transfermarkt Scraper RU](https://apify.com/bovi/transfermarkt-scraper-ru) — related football data Actor focused on transfers and market values for players in Russia and the CIS.

### Usage statistics

This Actor creates a small, content-free summary at the end of each run, used only to monitor reliability and improve this Actor. No input values, credentials or scraped data are sent.

A copy of the exact record for your run is saved as `USAGE_STATS` in your own Apify key-value store.

To opt out, set `disableUsageStats` to `true` in the input. Nothing is sent then.

[Full list of collected fields](https://api.apify.com/v2/key-value-stores/v9rgHjMKLDGtHf8tD/records/index.html)

# Actor input Schema

## `playerUrls` (type: `array`):

Transfermarkt player profile URLs (e.g. https://www.transfermarkt.com/lionel-messi/profil/spieler/28003) or bare numeric player IDs (e.g. 28003). Each player profile is scraped with market-value peak and full transfer history.

## `clubUrls` (type: `array`):

Transfermarkt club squad URLs (e.g. https://www.transfermarkt.com/fc-barcelona/kader/verein/131) or bare numeric club IDs (e.g. 131). Each squad player becomes one record (name, position, market value, age, nationality).

## `leagueCodes` (type: `array`):

Whole-league scraping. Paste competition codes (GB1 = Premier League, ES1 = LaLiga, IT1 = Serie A, L1 = Bundesliga, FR1 = Ligue 1, MLS1 = MLS) or full competition URLs (e.g. https://www.transfermarkt.com/premier-league/startseite/wettbewerb/GB1). Every member club's full squad is scraped, capped by Max Results across the whole league.

## `search` (type: `string`):

Free-text player name search (e.g. 'Haaland', 'Mbappe', 'Bellingham'). Top matching players are resolved to full profiles — market value, transfers, contract. Use instead of playerUrls when you know a name but not the ID.

## `rankings` (type: `boolean`):

If enabled, scrape Transfermarkt's most-valuable-players ranking (paginated, 25 per page) up to Max Results. Great for a market overview of the top-valued footballers.

## `maxResults` (type: `integer`):

Maximum player records per club / league / rankings / search request. Default 100. Player-URL mode ignores this (one record per URL).

## `includeMarketValueHistory` (type: `boolean`):

Profile mode (playerUrls / search) only. Attaches each player's complete market-value history (every dated valuation + club) as market_value_history. Adds payload size; off by default.

## `proxyConfiguration` (type: `object`):

Optional Apify Proxy. Moderate volumes work proxy-free (curl_cffi chrome impersonation retrieves Transfermarkt without a Cloudflare challenge). For high-volume runs, enable Apify RESIDENTIAL to avoid 429 rate limits. Billed to the run owner; no external proxy account needed.

## `disableUsageStats` (type: `boolean`):

See the README Usage statistics section for details.

## Actor input object example

```json
{
  "playerUrls": [
    "28003"
  ],
  "search": "Haaland",
  "rankings": false,
  "maxResults": 100,
  "includeMarketValueHistory": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "disableUsageStats": false
}
```

# Actor output Schema

## `results` (type: `string`):

Dataset containing Transfermarkt Scraper records (name, current_club, league, position, age, nationality, market_value_str, market_value_peak_str, contract_until, profile_url, parse_confidence, scraped_at).

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "playerUrls": [
        "28003"
    ],
    "search": "Haaland",
    "maxResults": 100,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("bovi/transfermarkt-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "playerUrls": ["28003"],
    "search": "Haaland",
    "maxResults": 100,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("bovi/transfermarkt-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "playerUrls": [
    "28003"
  ],
  "search": "Haaland",
  "maxResults": 100,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call bovi/transfermarkt-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,bovi/transfermarkt-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/YMpmcPbDIOcf2zH3n/builds/r9jJePXG1Valka5HF/openapi.json
