# Tennis Scraper — Flashscore, SofaScore & TennisExplorer (`seemuapps/flashscore-sofascore-tennis-scraper`) Actor

Scrape tennis fixtures, results, per-bookmaker odds history, match statistics, player profiles, head-to-head records and ATP/WTA rankings from Flashscore, SofaScore and TennisExplorer.

- **URL**: https://apify.com/seemuapps/flashscore-sofascore-tennis-scraper.md
- **Developed by:** [Andrew](https://apify.com/seemuapps) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Tennis Scraper — Flashscore, SofaScore & TennisExplorer

Get tennis fixtures, results, per-bookmaker odds history, match statistics, point-by-point data, player profiles, head-to-head records and ATP/WTA rankings as clean JSON, CSV or Excel — from three of the biggest tennis sites in a single run. Pick a source and a mode, or paste TennisExplorer URLs and let the actor route them.

### What you get

| Source | Best for | Cost per day of fixtures |
|---|---|---|
| **TennisExplorer** | Fixtures and results with odds, **per-bookmaker odds history** (opening price + every timestamped move across Home/Away, Over/Under, Asian Handicap and Correct Score), player profiles, head-to-head, weekly ranking snapshots going back years, player search | 1 page request |
| **Flashscore** | Breadth — every tournament from Grand Slams to ITF and juniors, with surface, set-by-set scores and tiebreaks, today ±7 days | 1 request |
| **SofaScore** | Depth — live matches, 20+ per-match statistics, point-by-point, live and pre-match odds, player bios with prize money, current ATP/WTA rankings | 1 request (live feed) |

Every row carries `type`, `source`, `sourceUrl` and `scrapedAt`, and the same field names are used across sources so you can merge or compare them without glue code:

- **Match rows** (`match`): `matchId`, `status` (scheduled / live / finished), `date`, `time`, `startTime`, `tournament`, `tour` (atp / wta), `discipline` (singles / doubles), `level`, `surface`, `round`, `home` / `away` (players with slugs and URLs, seed, sets won, per-set games with tiebreak points split out, match odds, head-to-head wins), `winner`.
- **Match-detail rows** (`matchDetail`): TennisExplorer adds both players' bios (ranking, birthdate, height, weight, plays), the structured final score, head-to-head record with every mutual match, surface and career win/loss balances, recent form, and the odds block — for each bookmaker and market the current price, trend, opening price and full history. SofaScore adds match statistics (aces, double faults, break points, service and return points…), point-by-point with serving player, and decimal odds with opening prices.
- **Player rows** (`player`): name, country, height, weight, age, birthdate, plays, current and highest singles/doubles rank, career and per-season win/loss by surface, titles per year, injuries and the next scheduled match. SofaScore profiles add residence, birthplace, turned-pro year, prize money, live ranking, UTR and the last ten matches.
- **Head-to-head rows** (`h2h`): official record, bio comparison and every mutual match with year, tournament, surface, round and set scores.
- **Ranking rows** (`rankingEntry`): rank, move, player, country, points and the snapshot date.
- **Search rows** (`playerSearchResult`): player name, slug or id, country and current rank — the identifiers the other modes need.

### Use cases

- **Betting models and line-movement archives** — capture opening-to-closing odds per bookmaker every morning and join them to results by `matchId`.
- **Tennis analytics** — surface-adjusted win probability, form and head-to-head features from structured set scores, balances and statistics.
- **Sports media and content sites** — auto-generate match previews (form, H2H, odds) and recaps (scores, ranking movers).
- **Fantasy and prediction apps** — daily fixtures as the slate, results feed for scoring, player bios for profiles.
- **Research datasets** — weekly ranking snapshots and results archives exported to JSON, CSV or Excel.

### How to use

1. Choose a **Data source** — TennisExplorer, Flashscore, SofaScore or all three.
2. Choose a **Mode**: fixtures, results, matchDetails, players, h2h, rankings or searchPlayers.
3. Fill in the field for that mode — a date range, match ids, player slugs, head-to-head pairs, a search query or a ranking table.
4. Set **Maximum results** to cap billed rows, then run. Results land in the **Dataset** tab.

Or skip the mode and paste any tennisexplorer.com URLs into **TennisExplorer URLs**: day listings, `/results/`, `/match-detail/?id=…`, `/player/…`, `/mutual/…/…`, `/ranking/…` and `/list-players/…` pages are recognised automatically.

#### Typical chains

- **Fixtures → match details**: run `fixtures` today, feed the `matchId` values into `matchDetails` to archive odds history.
- **Search → players / h2h**: run `searchPlayers` for a name, copy the `slug`, then run `players` or pair two slugs in `h2hPairs` (`djokovic vs alcaraz-5ab70`).
- **Historical rankings**: set `rankingDate` to any Monday (e.g. `2025-06-02`) to read that week's table.

### Example input

```json
{
  "source": "tennisexplorer",
  "mode": "matchDetails",
  "matchIds": ["3315143", "https://www.tennisexplorer.com/match-detail/?id=3290771"],
  "includeOddsHistory": true,
  "maxItems": 50
}
```

### Example output

A trimmed match-detail row:

```json
{
  "type": "matchDetail",
  "source": "tennisexplorer",
  "matchId": "3315143",
  "title": "Tiafoe - Medvedev",
  "date": "2026-09-06",
  "time": "21:40",
  "tournament": "US Open",
  "round": "round of 16",
  "surface": "hard",
  "tour": "atp",
  "home": { "name": "Tiafoe Frances", "slug": "tiafoe", "ranking": 12, "birthdate": "1998-01-20", "heightCm": 188, "weightKg": 86, "plays": "right" },
  "away": { "name": "Medvedev Daniil", "slug": "medvedev-e0d2d", "ranking": 8, "birthdate": "1996-02-11", "heightCm": 198, "weightKg": 83, "plays": "right" },
  "result": { "raw": "3 : 0(7-61, 6-4, 7-66)", "setsHome": 3, "setsAway": 0, "sets": [{ "home": 7, "away": 6, "awayTiebreak": 1 }, { "home": 6, "away": 4 }, { "home": 7, "away": 6, "awayTiebreak": 6 }] },
  "headToHead": { "record": "3 - 6", "matches": [{ "year": 2026, "tournament": "US Open", "surface": "Hard", "round": "R16", "winner": "Tiafoe", "loser": "Medvedev", "winnerSets": 3, "loserSets": 0 }] },
  "surfaceComparison": { "hard": { "home": { "wins": 24, "losses": 10 }, "away": { "wins": 24, "losses": 8 } } },
  "odds": {
    "homeAway": [{ "bookmaker": "10Bet", "line": null, "side1": { "odds": 2.35, "trend": "up", "opening": 2.05, "history": [{ "at": "06.09. 19:31", "odds": 2.35, "delta": 0.05 }, { "at": "05.09. 04:38", "odds": 2.05, "delta": null }] }, "side2": { "odds": 1.55, "trend": "down", "opening": 1.73 } }],
    "overUnder": [{ "bookmaker": "10Bet", "line": "2.5", "side1": { "odds": 2.2 }, "side2": { "odds": 1.6 } }],
    "asianHandicap": [],
    "correctScore": []
  },
  "sourceUrl": "https://www.tennisexplorer.com/match-detail/?id=3315143",
  "scrapedAt": "2026-09-07T03:10:31.866Z"
}
```

A Flashscore match row:

```json
{
  "type": "match",
  "source": "flashscore",
  "matchId": "thCAmNM0",
  "status": "finished",
  "startTime": "2026-09-06T02:10:00.000Z",
  "tournament": "US Open",
  "tour": "atp",
  "discipline": "singles",
  "level": "ATP",
  "surface": "hard",
  "home": { "players": [{ "name": "Mensik J.", "slug": "mensik-jakub", "country": "Czech Republic" }], "setsWon": 2, "setScores": [{ "games": 3 }, { "games": 6 }, { "games": 7, "tiebreak": 7 }, { "games": 3 }, { "games": 4 }] },
  "away": { "players": [{ "name": "Tien L.", "slug": "tien-learner", "country": "USA" }], "setsWon": 3, "setScores": [{ "games": 6 }, { "games": 1 }, { "games": 6, "tiebreak": 2 }, { "games": 6 }, { "games": 6 }] },
  "winner": "away"
}
```

### Pricing

Pay per row. Every row costs the base **result** price; the heavier row types add a surcharge on top because they cost several page fetches each:

| Row type | Charged |
|---|---|
| match, rankingEntry, playerSearchResult | result |
| matchDetail | result + match-detail-record |
| player | result + player-profile |
| h2h | result + h2h-record |
| any of the three above from SofaScore | + sofascore-detail (SofaScore only answers through residential proxy, which costs bandwidth per page) |

Failed pages never produce placeholder rows, so you only pay for data you receive. Use **Maximum results** as your spend guard.

### Notes and limits

- **Proxies.** TennisExplorer and Flashscore are read over a direct connection. SofaScore blocks datacenter IPs, so the actor routes it through a residential proxy automatically (or the proxy you configure); its API is occasionally flaky and every request is retried across fresh sessions.
- **SofaScore modes.** `fixtures` returns the live (in-play) feed rather than a date schedule, `results` and `h2h` are not available from SofaScore — use TennisExplorer or Flashscore for those. `matchDetails` takes SofaScore event ids and `players` takes SofaScore ids, slugs (`alcaraz-carlos`) or names.
- **Flashscore** covers today ±7 days; matches finishing after midnight appear in the next day's feed and are de-duplicated within a run.
- **Date ranges** are capped at 62 days per run. Fixtures for past dates are served from the results pages.
- **Live status** on TennisExplorer day listings is inferred from the score (a side with the winning number of sets is finished); Flashscore and SofaScore report status explicitly.
- Doubles teams appear as two `players` entries per side.
- Each run is a snapshot — schedule the actor for live-score style updates.

### Disclaimer

This actor is an independent tool and is not affiliated with, endorsed by, or sponsored by Flashscore, SofaScore, TennisExplorer, the ATP, WTA, ITF, any tournament or any bookmaker whose odds appear in the data. It reads only publicly available pages and feeds. Odds data is informational and nothing here constitutes betting advice. You are responsible for complying with each site's terms of service and the laws that apply to you.

# Actor input Schema

## `source` (type: `string`):

Which site to read. TennisExplorer: fixtures, results, per-bookmaker odds history, player profiles, head-to-head, rankings, player search — no proxy needed. Flashscore: one request per day of fixtures/results (±7 days) with surfaces and set scores — no proxy needed. SofaScore: live matches, match statistics, point-by-point, odds, player profiles and rankings — uses a residential proxy automatically because its API blocks datacenter IPs. "All sources" runs every source that supports the chosen mode and splits the row budget evenly.

## `mode` (type: `string`):

fixtures = upcoming matches with odds (today by default; SofaScore returns its live feed), results = finished matches with scores and closing odds (yesterday by default), matchDetails = deep per-match records (TennisExplorer: per-bookmaker odds history; SofaScore: statistics, point-by-point, odds), players = player profiles, h2h = head-to-head records (TennisExplorer), rankings = ATP/WTA tables, searchPlayers = find players and their slugs/ids by name.

## `startUrls` (type: `array`):

Any tennisexplorer.com URLs: /matches/, /next/, /results/, /match-detail/?id=…, /player/{slug}/, /mutual/{a}/{b}/, /ranking/… or /list-players/…. Each URL is auto-classified and routed to the right parser. Leave empty to use Mode instead.

## `tourType` (type: `string`):

Which tournaments to include: both tours, or a single tour, singles or doubles.

## `dateFrom` (type: `string`):

First day to scrape. Defaults: fixtures = today, results = yesterday. Fixtures for past dates are served from the results pages. Flashscore covers today ±7 days.

## `dateTo` (type: `string`):

Last day, inclusive. Defaults to Date from (a single day). Ranges are capped at 62 days per run.

## `matchIds` (type: `array`):

TennisExplorer numeric match ids (e.g. 3290771) or /match-detail/?id=… URLs — take them from the matchId field of fixtures/results rows. With source = SofaScore, pass SofaScore event ids instead.

## `includeOddsHistory` (type: `boolean`):

Keep the full per-bookmaker odds movement — every price change with timestamp plus the opening price — on match-detail rows, across Home/Away, Over/Under, Asian Handicap and Correct Score. Turn off for lighter rows with current + opening odds only.

## `includeRecentForm` (type: `boolean`):

Keep each player's recent-matches block on match-detail rows.

## `playerSlugs` (type: `array`):

For the players mode: TennisExplorer slugs like djokovic or full /player/… URLs (find slugs with the searchPlayers mode). With source = SofaScore, pass SofaScore team ids, slugs like alcaraz-carlos, or player names.

## `h2hPairs` (type: `array`):

For the h2h mode. One pair per line: djokovic vs alcaraz-5ab70, djokovic, alcaraz-5ab70 or a full /mutual/…/…/ URL.

## `searchQuery` (type: `string`):

For the searchPlayers mode: part of a player's name, case-insensitive (e.g. alcaraz). Returns matching players with their slugs/ids and current rank.

## `rankingTour` (type: `string`):

Which ranking to walk in rankings mode.

## `rankingDate` (type: `string`):

Historical weekly snapshot to read (Mondays), TennisExplorer only. Leave empty for the latest ranking.

## `maxItems` (type: `integer`):

Global cap on dataset rows for the run. You are billed per row, so this is your spend guard.

## `maxConcurrency` (type: `integer`):

Parallel page fetches. 10 is a good default; raise it for large matchDetails batches.

## `proxy` (type: `object`):

TennisExplorer and Flashscore are served fine over a direct connection, so no proxy is used for them unless you configure one here. SofaScore always goes through a residential proxy (yours if configured, otherwise Apify residential).

## `sofaMaxTournaments` (type: `integer`):

Reserved for SofaScore day fan-out. Currently SofaScore fixtures return the live feed in a single request, so this setting has no effect.

## Actor input object example

```json
{
  "source": "tennisexplorer",
  "mode": "fixtures",
  "startUrls": [],
  "tourType": "all",
  "matchIds": [],
  "includeOddsHistory": true,
  "includeRecentForm": true,
  "playerSlugs": [],
  "h2hPairs": [],
  "rankingTour": "atp-men",
  "maxItems": 200,
  "maxConcurrency": 10,
  "sofaMaxTournaments": 20
}
```

# Actor output Schema

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

Rows carry a `type` (match, matchDetail, player, h2h, rankingEntry, playerSearchResult) and a `source` (tennisexplorer, flashscore, sofascore). Match rows: matchId, status, date, time, tournament, tour, discipline, surface, home/away players, set scores, odds, winner. Match-detail rows add bios, per-bookmaker odds with opening price and history, surface balances, head-to-head and recent form. Player rows: bio, rankings, season and surface balances, titles, injuries. Ranking rows: rank, move, player, country, points, snapshotDate.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("seemuapps/flashscore-sofascore-tennis-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("seemuapps/flashscore-sofascore-tennis-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 '{}' |
apify call seemuapps/flashscore-sofascore-tennis-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,seemuapps/flashscore-sofascore-tennis-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/B09XtQhKq7USh0sf9/builds/dZIK9jPrKVFGQDeog/openapi.json
