# TennisExplorer Scraper - Tennis Results, Odds & H2H (`datagleaner/tennisexplorer-scraper`) Actor

TennisExplorer scraper for tennis results, fixtures and bookmaker odds: by date, tournament, player or match URL. Players, rankings, set scores, surface, round, head-to-head and odds per bookmaker with opening odds. Plain HTTP, no login. $1.50 per 1,000 matches.

- **URL**: https://apify.com/datagleaner/tennisexplorer-scraper.md
- **Developed by:** [Data Gleaner](https://apify.com/datagleaner) (community)
- **Categories:** Automation, Developer tools, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.50 / 1,000 tennis matches

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?

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

## TennisExplorer Scraper - Tennis Results, Fixtures, Odds & Head-to-Head

Scrape tennis matches from TennisExplorer: players and rankings, tournament, round, surface, score by set, head-to-head history and the odds every bookmaker offers, with the opening odds. Pull a whole day, a tournament, a player's season or single matches, as one clean JSON record per match.

It reads the public TennisExplorer pages over plain HTTP, with no browser, no login and no account, and costs **$1.50 per 1,000 matches**.

### What does TennisExplorer Scraper do?

It returns what you see on a TennisExplorer match page, through the Apify dataset and API as JSON, CSV or Excel. Typical uses:

- **Betting models and backtests:** results with pre-match odds per bookmaker, opening odds and head-to-head, by player, tournament or day.
- **Odds comparison and line movement:** each bookmaker's current and opening price on the same match, so you can see who is out of line.
- **Daily results and fixtures feeds:** schedule it every morning for yesterday's results or tomorrow's fixtures with average odds.
- **Player and surface research:** a player's full season with surface, round, opponent ranking and set scores.
- **Sports data products and AI agents:** feed tennis results and odds into a dashboard, newsletter or an LLM agent.

### Input

| Field | Meaning |
|---|---|
| `dates` | Day mode. `2026-10-12`, `yesterday`, `today`, `tomorrow`, an offset like `-3` or `+2`, or a range `2026-10-01..2026-10-07` (at most 31 days). Past days return results, today returns results and fixtures, future days return fixtures. |
| `tour` | Day mode filter: `all` (default), `atp-single`, `wta-single`, `atp-double`, `wta-double`. |
| `tournamentUrls` | Tournament mode, e.g. `https://www.tennisexplorer.com/shanghai/2026/atp-men/`. All results and upcoming matches of that edition. |
| `playerUrls`, `playerYears` | Player mode, e.g. `https://www.tennisexplorer.com/player/alcaraz-5ab70/` with seasons such as `["2025", "2024"]` (current season if empty). Singles only. |
| `matchUrls` | Single matches by URL or ID: `https://www.tennisexplorer.com/match-detail/?id=3344135` or `3344135`. |
| `maxMatches` | Per date, tournament and player. Default 100, max 20,000. A busy day lists 150 to 250 matches. |
| `includeDetails` | Default on: read each match page for odds per bookmaker, head-to-head and player profile. Off (day mode only): one request per day, list fields and average odds, with one request instead of one per match. |
| `includeAllMarkets` | Adds over/under, Asian handicap and correct-score odds from every bookmaker. Off by default because records get large (about 40 KB each). |
| `requestDelaySecs` | Pacing between request starts, default 0.4. |
| `proxyConfiguration` | Off by default; TennisExplorer answered Apify's datacenter IPs in testing. |

With no input at all, the Actor runs a built-in example of 5 matches from yesterday's results. If you already call another tennis Actor, these input names are also accepted: `startUrls` (a mixed list of date, tournament, player and match URLs), `days`, `maxItems`, `maxResults`.

```json
{
  "dates": ["yesterday"],
  "tour": "atp-single",
  "maxMatches": 50
}
```

### Output

```json
{
  "matchId": "3345103",
  "url": "https://www.tennisexplorer.com/match-detail/?id=3345103",
  "status": "finished",
  "startDate": "2026-10-12",
  "startTime": "14:30",
  "startsAt": "2026-10-12T14:30:00+02:00",
  "timezone": "Europe/Berlin",
  "tournament": "Shanghai",
  "tournamentUrl": "https://www.tennisexplorer.com/shanghai/2026/atp-men/",
  "round": "3. round",
  "surface": "hard",
  "player1Name": "Paul Tommy",
  "player2Name": "Vacherot Valentin",
  "player1Ranking": 17,
  "player2Ranking": 14,
  "player1": {"name": "Paul Tommy", "url": "https://www.tennisexplorer.com/player/paul-324c3", "ranking": 17, "birthDate": "17. 5. 1997", "heightCm": 185, "weightKg": 82, "plays": "right"},
  "player2": {"name": "Vacherot Valentin", "url": "https://www.tennisexplorer.com/player/vacherot", "ranking": 14, "birthDate": "16. 11. 1998", "heightCm": 193, "weightKg": null, "plays": "right"},
  "setsWon1": 2,
  "setsWon2": 0,
  "winner": "Paul Tommy",
  "setScores": [{"games1": 6, "games2": 4, "tiebreak": null}, {"games1": 6, "games2": 2, "tiebreak": null}],
  "score": "6-4, 6-2",
  "resultNote": null,
  "h2h": {"player1Wins": 1, "player2Wins": 0},
  "h2hMatches": [{"year": 2026, "tournament": "Shanghai", "surface": "hard", "round": "3R", "score": "6-4, 6-2", "player1Name": "Paul", "player2Name": "Vacherot", "player1Sets": 2, "player2Sets": 0, "matchId": "3345103"}],
  "oddsAverage1": 1.6,
  "oddsAverage2": 2.32,
  "bookmakerCount": 15,
  "oddsHomeAway": [
    {"bookmaker": "10Bet", "odds1": 1.57, "odds2": 2.3, "opening1": 1.57, "opening2": 2.3},
    {"bookmaker": "1xBet", "odds1": 1.6, "odds2": 2.39, "opening1": 1.44, "opening2": 2.5}
  ],
  "source": "date",
  "input": "2026-10-12",
  "scrapedAt": "2026-10-12T23:49:44Z"
}
```

`player1` is the first-listed (home) player and `odds1` is the price for player 1 to win. Odds are decimal. `opening1` and `opening2` are the first prices TennisExplorer recorded for that bookmaker. With `includeAllMarkets` the record also has `oddsOverUnder`, `oddsAsianHandicap` and `oddsCorrectScore` lists (one row per bookmaker and line, plus an average row marked `isAverage`; in correct score, `value` is the set score and `odds1` its price). The dataset has a **Matches** view with the main columns.

#### Which fields are filled

In a platform test of 100 matches from one day:

- **Always filled:** `matchId`, `url`, `status`, `startDate`, `tournament`, `player1`, `player2`, `h2h`, `source`, `input`, `scrapedAt`.
- **Usually filled (about 90%):** `score`, `setScores`, `surface`, `round`. Walkovers and abandoned matches have no score.
- **Often filled (about 70%):** `startTime` and `startsAt` (lower-level events show `--:--` for unknown times), rankings, and the odds fields, because lower-tier ITF and Futures matches have few or no bookmaker odds (`bookmakerCount` is then 0 and the odds fields are null or empty).
- **Sometimes filled:** `weightKg`, `heightCm`, `birthDate`, `plays` (TennisExplorer lacks them for many players), `resultNote`.

`h2h` counts earlier meetings, and for a finished match it includes that match itself, because TennisExplorer lists it in the head-to-head table.

### Pricing

Pay per result: **$1.50 per 1,000 matches** ($0.0015 per match). You pay only for matches pushed to the dataset. A day with no matches, or a match page that cannot be read, costs nothing. The run stops at your maximum charge.

Worked example: yesterday's full ATP and WTA singles results, about 150 matches, cost $0.23. A player's season of 60 matches costs $0.09. Running the same day-list every morning for 30 days at 150 matches costs about $6.75. A 10-match test costs $0.015.

### Use it from the API

Run it synchronously and get the matches back as JSON. Replace `<YOUR_APIFY_TOKEN>` with your token.

```bash
curl -X POST "https://api.apify.com/v2/acts/datagleaner~tennisexplorer-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"dates": ["yesterday"], "tour": "atp-single", "maxMatches": 20}'
```

It also works from n8n, Make or Zapier through the Apify integrations, since the output is a flat JSON record per match.

### Use with Python

`pip install apify-client`, set the `APIFY_TOKEN` environment variable, and run. This input returns at most 10 matches, about $0.015.

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("datagleaner/tennisexplorer-scraper").call(run_input={
    "playerUrls": ["https://www.tennisexplorer.com/player/alcaraz-5ab70/"],
    "playerYears": ["2025"],
    "maxMatches": 10,
})
for m in client.dataset(run.default_dataset_id).iterate_items():
    print(m["startDate"], m["player1Name"], "-", m["player2Name"], m["score"], m["oddsAverage1"], m["oddsAverage2"])
```

### Use with JavaScript / Node.js

`npm install apify-client`, set `APIFY_TOKEN`, and run it as an ES module (`.mjs`).

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('datagleaner/tennisexplorer-scraper').call({
    dates: ['yesterday'],
    maxMatches: 10,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const m of items) console.log(m.startDate, m.player1Name, '-', m.player2Name, m.score);
```

### Use it from n8n, Make, Zapier or an AI agent

Actor ID: `datagleaner/tennisexplorer-scraper`

Minimal input:

```
{"dates": ["yesterday"], "tour": "atp-single", "maxMatches": 10}
```

Each tool below runs this Actor with your own Apify API token.

- **n8n:** add the **Apify** node (`@apify/n8n-nodes-apify`). On n8n Cloud you install it from the community node registry. Choose **Run an Actor and get dataset**, set Actor to `datagleaner/tennisexplorer-scraper` and paste the input above.
- **Make:** use the Apify app's **Run an Actor** module, then **Get Dataset Items** to read the results. **Watch Actor Runs** can trigger a scenario when a run finishes.
- **Zapier:** use the Apify action **Run Actor**, then the search **Fetch dataset items**. The trigger **Finished Actor run** starts a Zap when a run ends.
- **AI agents (MCP):** connect to `https://mcp.apify.com?tools=datagleaner/tennisexplorer-scraper`. In Claude Code:

  ```
  claude mcp add --transport http apify "https://mcp.apify.com?tools=datagleaner/tennisexplorer-scraper"
  ```

  Then run `/mcp` to sign in to Apify in your browser. Then ask in plain language, for example: "Get yesterday's ATP results with the bookmaker odds and tell me which favourites lost." An agent without the Actor preloaded can find it with Apify's `search-actors` tool by searching "tennis odds".
- **LangChain (Python):**

```python
## pip install langchain-apify, then set APIFY_TOKEN in your environment
import json
from langchain_apify import ApifyActorsTool
tool = ApifyActorsTool("datagleaner/tennisexplorer-scraper")
result = tool.invoke({"run_input": json.loads('{"dates": ["yesterday"], "tour": "atp-single", "maxMatches": 10}')})
```

### Limits

- **TennisExplorer data only.** Rankings, odds and results are what TennisExplorer shows. There are no point-by-point or live in-play statistics. A match that is in play can show a partial result and the status `finished`.
- **Times are in TennisExplorer's default timezone** (Europe/Berlin, with daylight saving). `startsAt` carries the matching UTC offset. Unknown start times (`--:--` on the site) leave `startTime` and `startsAt` null.
- **Odds exist only where bookmakers quote them.** Main-tour matches have 9 to 15 bookmakers, many lower-level matches have none. Odds are the site's displayed decimal prices at the time of the scrape, and for finished matches they are the last prices before the match.
- **Player mode reads singles only**, one season page per year in `playerYears`.
- **One tournament page lists the matches of one edition and phase.** Qualifying and doubles draws have their own URLs; paste those too.
- **A tournament page the site itself fails to serve** (it sometimes answers 500) is skipped with a log line, not billed.
- TennisExplorer can slow or refuse heavy traffic. The Actor paces its requests, retries with backoff and switches connection after repeated refusals. It never solves captchas or tries to get around them. If it is blocked it stops, keeps what it collected and says so in the status message. Lower the volume or raise `requestDelaySecs`.

### FAQ

**Is there a TennisExplorer API?** TennisExplorer does not offer a public API. This Actor works as a simple one: you call the Apify API with a date, tournament or player and it returns JSON.

**How is this different from a Flashscore tennis scraper?** Flashscore Actors return scores and fixtures. This one adds the bookmaker odds on each match with opening odds, the head-to-head history, and results by tournament or player for any season, not only a 14-day window around today.

**Can I get live scores?** No. It returns results, fixtures and pre-match odds. Use it for models and research, not in-play feeds.

**Why do some matches have no odds or no time?** TennisExplorer has none for lower-tier events. The record then has `bookmakerCount` 0 and null odds.

**How fast is it?** Full details take about 0.8 second per match, so 100 matches take about 80 seconds. With `includeDetails` off, a whole day of 200 matches comes back in one request, in a few seconds, with average odds only.

**Do I need a proxy?** No. It worked from Apify datacenter IPs without one.

### Responsible use

This Actor reads publicly visible TennisExplorer pages without logging in. Use the data in line with TennisExplorer's terms and your local betting and data laws. The records are sports data: player names, rankings and birth dates of professional players, as the site publishes them. If you combine them with anything else about individuals, the data-protection obligations are yours. Nothing here is betting advice.

# Actor input Schema

## `dates` (type: `array`):

Day mode: every ATP, WTA, Challenger and ITF match on these days. Use `2026-10-12`, `yesterday`, `today`, `tomorrow`, an offset like `-3` or `+2`, or a range `2026-10-01..2026-10-07` (at most 31 days). Past days return results, today returns results and fixtures, future days return fixtures with their odds.

## `tour` (type: `string`):

Narrow the day lists to one tour. Applies to `dates` only.

## `tournamentUrls` (type: `array`):

Tournament mode: every match of an event, results and upcoming, e.g. `https://www.tennisexplorer.com/shanghai/2026/atp-men/`. Open the tournament on TennisExplorer and copy the address; the year in the URL picks the edition.

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

Player mode: a player's singles matches, e.g. `https://www.tennisexplorer.com/player/alcaraz-5ab70/`. Newest first, one season at a time (see `playerYears`).

## `playerYears` (type: `array`):

Seasons to read for each player URL, e.g. `2026`, `2025`. Seasons are read in the order given until `maxMatches` is reached. Leave empty for the current season.

## `matchUrls` (type: `array`):

Single matches: `https://www.tennisexplorer.com/match-detail/?id=3344135` or just the ID `3344135`.

## `maxMatches` (type: `integer`):

Stops after this many matches for each date, tournament and player (a busy day lists 150 to 250 matches). Every match returned is billed.

## `includeDetails` (type: `boolean`):

On: each match page is read for bookmaker odds with opening odds, head-to-head history and player profile. Off (day mode only): one request per day returns the list with average odds only, which is much faster. The price per match is the same.

## `includeAllMarkets` (type: `boolean`):

Adds the over/under, Asian handicap and correct-score odds tables from every bookmaker. Records get much larger. The home/away (match winner) odds are always included with full details.

## `requestDelaySecs` (type: `number`):

Polite pacing between request starts. Lower is faster and raises the chance of being blocked.

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

Optional. Off by default: TennisExplorer answered Apify datacenter IPs in testing. Enable a proxy if runs start getting blocked at high volume.

## Actor input object example

```json
{
  "dates": [
    "yesterday"
  ],
  "tour": "all",
  "maxMatches": 10,
  "includeDetails": true,
  "includeAllMarkets": false,
  "requestDelaySecs": 0.4
}
```

# Actor output Schema

## `matches` (type: `string`):

No description

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

No description

# 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 = {
    "dates": [
        "yesterday"
    ],
    "maxMatches": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("datagleaner/tennisexplorer-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 = {
    "dates": ["yesterday"],
    "maxMatches": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("datagleaner/tennisexplorer-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 '{
  "dates": [
    "yesterday"
  ],
  "maxMatches": 10
}' |
apify call datagleaner/tennisexplorer-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datagleaner/tennisexplorer-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/oQGtSuSK0oxdcqq2L/builds/JgAkowhkteEJKazdY/openapi.json
