# Tennis Results & Odds Scraper (`axiomworks/tennis-explorer-scraper`) Actor

Scrape Tennis Explorer for ATP and WTA results and upcoming matches, singles and doubles: dates, tournament, players, set scores and listed decimal odds. Pick a date range for finished results; upcoming matches come from the public schedule. No login.

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

## Pricing

from $1.05 / 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.
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

## Tennis Explorer Scraper

**Returns:** one row per tennis match with date, tournament, player1, player2, setScores, sets1, sets2, winner, oddsPlayer1, oddsPlayer2 (plus rankings and player profiles).
**Input:** `{"mode": "results", "tour": "atp-single", "dateFrom": "2026-09-28", "dateTo": "2026-09-28", "playerFilter": ["hurkacz"], "maxItems": 20}`
**Best for:** tennis betting models and odds history, results databases by tour and day, tracking one player or tournament.

```json
{"id": "match-3334240", "date": "2026-09-28", "tournament": "Chengdu", "player1": "Hurkacz H.", "player2": "Shapovalov D.", "setScores": ["6-7(8)", "6-3", "6-3"], "winner": "player1", "oddsPlayer1": 1.61, "oddsPlayer2": 2.29}
```

### What does Tennis Explorer Scraper do?

Tennis Explorer Scraper collects public tennis data from Tennis Explorer and returns it as clean, structured records. It covers four kinds of data: finished match results for a day or a range of days, upcoming matches with decimal odds, the ATP and WTA singles rankings, and player profile details. You choose the mode, the tour and, where it applies, the dates or player links, and the Actor does the paging and parsing for you.

The Actor reads the same public pages you can open in a browser. It does not log in, it does not solve CAPTCHAs, and it keeps to a polite request rate of about one request per second. Results and upcoming pages list every match of a day on a single page, so a single day of ATP singles usually costs one request, even when it contains several hundred matches. When no filters are supplied, parsing stops once enough matches are available for the remaining maxItems budget. Filtered runs inspect the full table so later matching records are still included. Each page request has a bounded retry window of about 65 seconds, and missing pages return without retries.

Typical uses:

Build a historical results table of ATP or WTA matches, with set scores and bookmaker odds.

Feed a betting model or a sports analytics notebook with match odds and outcomes.

Track the current ranking and points of the top players and compare them from week to week.

Collect player profile facts such as age, height, playing hand and career-high ranking.

Monitor the daily schedule of upcoming matches together with the listed odds.

This Actor is an independent tool. It is not affiliated with or endorsed by Tennis Explorer.

### What data can you get?

Every record has an `id`, a `sourceUrl` and a `recordType` of `match`, `ranking` or `player`. The other fields depend on the type. Fields that do not apply to a record type, or that the page does not show, are `null`.

| Field | Type | Record type | Description |
|---|---|---|---|
| id | string | all | Stable record ID (match ID, ranking tour with player slug, or player slug) |
| sourceUrl | string | all | Page the record was read from |
| recordType | string | all | match, ranking or player |
| matchId | string | match | Tennis Explorer match identifier |
| matchUrl | string | match | Link to the match detail page |
| tour | string | match | atp-single, wta-single, atp-double or wta-double |
| date | string | match | Match date in ISO format |
| time | string | match | Start time as shown on the page |
| tournament | string | match | Tournament name |
| tournamentUrl | string | match | Tournament page link, when the page provides one |
| player1, player2 | string | match | Players or doubles teams as shown, for example "Hurkacz H." |
| player1Url, player2Url | string | match | Profile links of the players or teams |
| player1Seed, player2Seed | number | match | Seed in brackets, when shown |
| sets1, sets2 | number | match | Sets won by each side (results mode) |
| setScores | array | match | Games per set such as "6-7(8)", "6-3"; tiebreak points of the set loser in brackets |
| oddsPlayer1, oddsPlayer2 | number | match | Decimal odds, when the page lists them |
| winner, winnerName | string | match | player1 or player2 and that player's name, for finished matches with a decided set count; null otherwise |
| resultType | string | match | completed, retired (fewer set scores than sets won) or walkover (no set scores), derived from the result row; null for upcoming matches |
| round, surface | string | match | Round (for example semifinal) and court surface from the match detail page. Only filled when includeMatchDetails is true |
| player1Rank, player2Rank | integer | match | Singles ranking of each player on the match page. Only with includeMatchDetails |
| player1HeightCm, player2HeightCm, player1Plays, player2Plays | integer, string | match | Height in cm and playing hand from the match page. Only with includeMatchDetails |
| rankingTour | string | ranking | atp-men or wta-women |
| rank | number | ranking | Position in the ranking |
| player | string | ranking, player | Player name |
| playerUrl | string | ranking, player | Player profile link |
| country | string | ranking, player | Country shown on the page |
| points | number | ranking | Ranking points |
| age | number | player | Age in years |
| dateOfBirth | string | player | ISO date of birth |
| height, weight | string | player | As shown, for example "196 cm" |
| plays | string | player | Playing hand |
| sex | string | player | As listed on the profile |
| currentRankSingles, highestRankSingles | number | player | Current and career-high singles rank |
| currentRankDoubles, highestRankDoubles | number | player | Current and career-high doubles rank |

The `time` field is `null` when the site shows no start time (`--:--`), which is common for Futures matches. Odds are not available for every match. Low-tier events and unplayed or retired matches often have no odds, and `oddsPlayer1` and `oddsPlayer2` are then `null`. Some tournament headers on the site, such as Futures groups, do not link to a tournament page, so `tournamentUrl` is `null` for those matches.

### How to use

1. Open the Actor in Apify Console and pick a mode.
2. For results or upcoming matches, choose a tour and, optionally, a date range.
3. For rankings, choose the ATP or WTA tour.
4. For player profiles, paste one or more player links.
5. Set `maxItems` to the number of records you want, then start the run.
6. Download the dataset as JSON, CSV, Excel or HTML, or read it through the API.

The modes work like this:

**Results**: one page per day and tour. The Actor walks from `dateFrom` to `dateTo` (inclusive, at most 62 days) and stops as soon as `maxItems` records are saved. If you give no date, it uses yesterday.

**Upcoming**: the schedule page for the tour. Without dates it reads today's schedule; with dates it reads each day in the range. Upcoming records have no scores, and odds are shown when the site has them.

**Filters and details**: `playerFilter` and `tournamentFilter` are case-insensitive substring matches applied to each parsed match, so a filtered run still reads one page per day; `maxItems` counts matches that pass the filter. With `includeMatchDetails` set to true, the Actor opens each kept match's detail page, which adds round, surface, rankings, height and playing hand and makes the run slower.

**Rankings**: the singles ranking of the ATP (men) or WTA (women) tour, about 50 players per page, until `maxItems` is reached or the list ends. Doubles tours map to the same singles ranking.

**Players**: one request per profile link. Only `tennisexplorer.com/player/` links are accepted.

If a date has no matches, or a player link does not exist, the Actor logs a warning and finishes without creating placeholder records. If a mode needs input that is missing or malformed (for example a date that is not in YYYY-MM-DD format), the run fails with a clear message.

### Input

| Field | Type | Description |
|---|---|---|
| mode | string | results, upcoming, rankings or players. Default results |
| tour | string | atp-single, wta-single, atp-double or wta-double. Default atp-single |
| dateFrom | string | First day, YYYY-MM-DD (results and upcoming) |
| dateTo | string | Last day, YYYY-MM-DD, inclusive. Defaults to dateFrom |
| playerUrls | array | Player profile links for players mode |
| playerFilter | array | Keep only matches where a player's name or profile slug contains one of these strings, e.g. hurkacz (results and upcoming) |
| tournamentFilter | array | Keep only matches whose tournament name contains one of these strings, e.g. Chengdu (results and upcoming) |
| includeMatchDetails | boolean | Default false. Opens each match's detail page to add round, surface, rankings, height and playing hand. One extra request per match |
| maxItems | integer | Maximum records to output, 1-10000. Default 50 |
| proxyConfiguration | object | Apify Proxy settings. Off by default; enable it if requests get blocked |

Example input for one day of ATP singles results:

```json
{
  "mode": "results",
  "tour": "atp-single",
  "dateFrom": "2026-09-28",
  "dateTo": "2026-09-28",
  "maxItems": 50
}
```

Example input for player profiles:

```json
{
  "mode": "players",
  "playerUrls": [
    "https://www.tennisexplorer.com/player/hurkacz/",
    "https://www.tennisexplorer.com/player/medvedev-e0d2d/"
  ],
  "maxItems": 10
}
```

### Output

A match record from results mode looks like this:

```json
{
  "id": "match-3334240",
  "sourceUrl": "https://www.tennisexplorer.com/results/?type=atp-single&year=2026&month=09&day=28",
  "recordType": "match",
  "matchId": "3334240",
  "matchUrl": "https://www.tennisexplorer.com/match-detail/?id=3334240",
  "tour": "atp-single",
  "date": "2026-09-28",
  "time": "13:25",
  "tournament": "Chengdu",
  "tournamentUrl": "https://www.tennisexplorer.com/chengdu/2026/atp-men/",
  "player1": "Hurkacz H.",
  "player2": "Shapovalov D.",
  "player1Url": "https://www.tennisexplorer.com/player/hurkacz/",
  "player2Url": "https://www.tennisexplorer.com/player/shapovalov/",
  "player1Seed": 5,
  "player2Seed": 7,
  "sets1": 2,
  "sets2": 1,
  "setScores": ["6-7(8)", "6-3", "6-3"],
  "oddsPlayer1": 1.61,
  "oddsPlayer2": 2.29
}
```

A ranking record:

```json
{
  "id": "wta-women-rybakina",
  "sourceUrl": "https://www.tennisexplorer.com/ranking/wta-women/?page=1",
  "recordType": "ranking",
  "rankingTour": "wta-women",
  "rank": 1,
  "player": "Rybakina Elena",
  "playerUrl": "https://www.tennisexplorer.com/player/rybakina/",
  "country": "Kazakhstan",
  "points": 9901
}
```

A player record:

```json
{
  "id": "player-hurkacz",
  "sourceUrl": "https://www.tennisexplorer.com/player/hurkacz/",
  "recordType": "player",
  "player": "Hurkacz Hubert",
  "country": "Poland",
  "age": 29,
  "dateOfBirth": "1997-02-11",
  "height": "196 cm",
  "weight": "81 kg",
  "plays": "right",
  "currentRankSingles": 41,
  "highestRankSingles": 6
}
```

Set scores list the games of the first side, then the second side, in the order the sets were played. When a set went to a tiebreak, the tiebreak points of the set loser are added in brackets. The dataset has an overview view with the most useful columns, and you can export the full records in any format Apify supports.

### How much does it cost?

The Actor is charged per result: each record saved to the dataset counts as one result. The exact price is shown on the Pricing tab of the Actor page in Apify Console, together with your plan's discounts. Because `maxItems` is honoured exactly, you can cap the cost of a run by capping the number of records. Platform usage such as compute is billed according to your Apify plan.

### Use with AI agents (MCP)

You can call this Actor from any MCP-compatible AI agent through the Apify MCP server. Add this to your MCP client configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=axiomworks/tennis-explorer-scraper"
    }
  }
}
```

Example prompts:

"Get all ATP singles results from 2026-09-28 with set scores and odds, limit to 30 matches."

"List the top 50 players in the WTA ranking with their points."

"Fetch the profile of https://www.tennisexplorer.com/player/hurkacz/ and tell me his highest singles rank."

"Show today's upcoming ATP doubles matches with odds."

### Integrations and API

Run the Actor from code with the Apify API client or plain HTTP. Replace `<YOUR_API_TOKEN>` with your own token.

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("axiomworks/tennis-explorer-scraper").call(run_input={
    "mode": "rankings",
    "tour": "atp-single",
    "maxItems": 50,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["rank"], item["player"], item["points"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });
const run = await client.actor('axiomworks/tennis-explorer-scraper').call({
    mode: 'results',
    tour: 'atp-single',
    dateFrom: '2026-09-28',
    maxItems: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((i) => process.stdout.write(`${i.id}\n`));
```

cURL:

```bash
curl -X POST "https://api.apify.com/v2/acts/axiomworks~tennis-explorer-scraper/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"mode":"players","playerUrls":["https://www.tennisexplorer.com/player/hurkacz/"],"maxItems":5}'
```

You can also connect the dataset to Google Sheets, Make, Zapier, n8n or webhooks, schedule the Actor to run daily, and compare runs over time.

### FAQ

**How many matches can I get per day?**
ATP singles often has several hundred matches on a busy day, including lower-tier events. All of them are on one page, so a single day is quick to collect.

**Why are some odds empty?**
The site shows odds only for some matches. Where it shows none, the odds fields are `null`.

**Why is `sets1` empty for some matches?**
When a match is unplayed, abandoned or not yet decided, the page shows no sets won, so the value is `null`. Upcoming matches never have set scores.

**Which timezone are times in?**
Times are copied as shown on the page. The Actor does not convert them.

**Do I need a proxy?**
Not normally. The site answers ordinary requests. If you see blocks, enable Apify Proxy in the input, and the Actor retries failed requests with backoff on a fresh session.

**Can I get doubles?**
Yes, pick atp-double or wta-double as the tour for results and upcoming matches. Doubles teams are shown as the site displays them, and rankings are the singles ranking.

**How often can I run it?**
As often as you like, but results for past days do not change, so scheduling a daily run for the previous day is usually enough.

### Is it legal to scrape Tennis Explorer?

The Actor only reads publicly visible pages that need no login. Scraping public data is generally lawful in many places, but rules differ by country and by use. Facts such as scores and rankings are treated differently from creative content, and personal data in player profiles is subject to data protection laws such as GDPR. Check the site's terms of use and your local law, use the data responsibly, and take legal advice if you plan commercial redistribution.

### Feedback

If a field is missing, a page layout changes, or you need another view of the data, open an issue from the Actor page in Apify Console. Bug reports with the run link and input help the most.

# Actor input Schema

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

What to scrape: 'results' (finished matches per day), 'upcoming' (scheduled matches with odds), 'rankings' (ATP/WTA singles ranking) or 'players' (profiles from playerUrls). Default results.

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

Tour for results, upcoming and rankings: atp-single, wta-single, atp-double or wta-double. Rankings use the singles ranking of the ATP (men) or WTA (women) tour. Default atp-single.

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

First day in YYYY-MM-DD for results and upcoming modes, e.g. '2026-09-28'. Results default to yesterday; upcoming defaults to today's schedule. One page request per day.

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

Last day in YYYY-MM-DD (inclusive), e.g. '2026-09-30'. Defaults to dateFrom. At most 62 days are scanned.

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

Required when mode is 'players', ignored otherwise. Player profile URLs, e.g. \['https://www.tennisexplorer.com/player/hurkacz/']. Only tennisexplorer.com /player/ URLs are accepted.

## `playerFilter` (type: `array`):

Only keep matches where either player's name or profile slug contains one of these strings (case-insensitive), e.g. \['hurkacz', 'djokovic']. Applies to results and upcoming modes. Empty keeps all matches.

## `tournamentFilter` (type: `array`):

Only keep matches whose tournament name contains one of these strings (case-insensitive), e.g. \['Chengdu', 'Masters']. Applies to results and upcoming modes. Empty keeps all tournaments.

## `includeMatchDetails` (type: `boolean`):

If true, opens each match's detail page to add round, surface, both players' singles ranking, height and playing hand. Slower (one extra request per match). Default false.

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

Maximum number of records to output, 1-10000, default 50. The run stops once this many are saved.

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

Apify Proxy settings. The site works without a proxy, so it is off by default; enable it if requests get blocked.

## Actor input object example

```json
{
  "mode": "results",
  "tour": "atp-single",
  "dateFrom": "2026-09-28",
  "dateTo": "2026-09-28",
  "playerUrls": [],
  "playerFilter": [],
  "tournamentFilter": [],
  "includeMatchDetails": false,
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `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 = {
    "mode": "results",
    "tour": "atp-single",
    "dateFrom": "2026-09-28",
    "dateTo": "2026-09-28",
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("axiomworks/tennis-explorer-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 = {
    "mode": "results",
    "tour": "atp-single",
    "dateFrom": "2026-09-28",
    "dateTo": "2026-09-28",
    "maxItems": 20,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("axiomworks/tennis-explorer-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 '{
  "mode": "results",
  "tour": "atp-single",
  "dateFrom": "2026-09-28",
  "dateTo": "2026-09-28",
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call axiomworks/tennis-explorer-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,axiomworks/tennis-explorer-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/V1NQU0zccGPtghQ8n/builds/7lRQdsAiEiRqJ3mxI/openapi.json
