ATP Tour Match Stats Scraper avatar

ATP Tour Match Stats Scraper

Pricing

from $15.00 / 1,000 matches

Go to Apify Store
ATP Tour Match Stats Scraper

ATP Tour Match Stats Scraper

Scrape official ATP Tour player search, match history, and detailed match statistics: aces, break points, serve/return percentages, and set scores. Discover players, build deduplicated match plans, and pull match records at scale.

Pricing

from $15.00 / 1,000 matches

Rating

0.0

(0)

Developer

ParseBird

ParseBird

Maintained by Community

Actor stats

1

Bookmarked

2

Total users

1

Monthly active users

5 days ago

Last modified

Share

ATP Tour Match Stats Scraper

Scrape player profiles, match history, and match statistics directly from the official ATP Tour website — the same data source that powers atptour.com's own live scoreboards and match centre pages.

Search the ATP player directory, build a deduplicated match plan across one or more players, and pull match records with aces, double faults, break points saved/converted, and serve/return percentages — per set where ATP publishes them.

Copy to your AI assistant

Copy this block into ChatGPT, Claude, Cursor, or any LLM to start using this actor.

parsebird/atp-match-stats-scraper on Apify. Call: ApifyClient("TOKEN").actor("parsebird/atp-match-stats-scraper").call(run_input={...}), then client.dataset(run["defaultDatasetId"]).list_items().items for results. Key inputs: operation (string, required: "discover-players" | "discover-matches" | "scrape-matches" | "scrape-match-ids"), search (string, player name for discover-players, e.g. "Sinner"), playerIds (string array, ATP player IDs for discover-matches/scrape-matches, e.g. ["S0AG"]), matchRequests (array of {matchId, requestedPlayerIds} for scrape-match-ids, up to 250, matchId format "{year}#{tournamentId}#{matchCode}" e.g. "2026#540#MS001"), maxMatchesPerPlayer (integer, default 5, 0=all), maxTotalMatches (integer, default 20, hard cap after dedup), matchFormat ("all" default or "singles"), includeStats (boolean, default true). Output recordType is "player", "match-reference", "match", or "match-exclusion". Match records include tournament, round, surface, sets (per-set game scores where published), and stats (flattened service/return/points rows with teamAValue/teamBValue). Full actor spec: fetch build via GET https://api.apify.com/v2/acts/parsebird~atp-match-stats-scraper (Bearer TOKEN). Get token: https://console.apify.com/account/integrations

What does ATP Tour Match Stats Scraper do?

This Actor scrapes atptour.com, the official ATP Tour website, using the same JSON endpoints its own player pages and match centre use — no unofficial mirror, no stale cache.

  • 🎾 Player directory search — look up any ATP player, past or present, by name and get their stable ATP player ID
  • 📋 Deduplicated match discovery — request matches for several players at once; a match both players share is only fetched (and billed) once
  • 📊 Real match statistics — aces, double faults, first/second serve percentages, break points saved and converted, and total points won, at match level and per set where ATP publishes a set-by-set breakdown
  • 🏆 Full match context — tournament, round, surface, court, umpire, score, and both players' seeds and countries
  • 🔀 Singles and doubles — doubles matches are detected automatically (via ATP's own MD/QD match codes); filter to singles-only with one input field
  • 🎯 Direct match lookup — already know the match(es) you want? Skip discovery and fetch them by ID
  • ⏱️ Scheduling — run this Actor on a recurring Apify Schedule to keep a player's results current through a tournament or season
  • 🔌 API access — trigger runs and pull results programmatically via the Apify API or client libraries
  • 📁 Flexible export — download results as JSON, CSV, or Excel from the Apify Console, or query them via API

What data can you extract from ATP Tour?

FieldDescription
playerId / fullName / countryCodePlayer identity, from the ATP player directory
matchId{year}#{tournamentId}#{matchCode} — ATP's own tournament and match numbering, e.g. 2026#540#MS001
tournament / round / surface / courtNameMatch context
players / winnerId / matchFormatBoth players (and partners, for doubles), the winner, and singles/doubles
setsPer-set game score and tie-break, where ATP publishes a set-by-set breakdown
statsFlattened service/return/points rows — aces, double faults, break points saved/converted, serve/return % — at match level and per set
headToHeadUrlLink to the two players' ATP head-to-head page
sourceUrlThe exact ATP Tour match-stats page the record was built from

Set-by-set statistics are published for most matches, but some ATP feeds (notably several Grand Slam matches we tested) only expose the match-level aggregate — in that case sets and per-set stats rows are simply absent, while match-level stats are still complete. Every match record's stats array is built directly from ATP's own field names, not inferred or estimated.

Input parameters

ParameterTypeRequiredDefaultDescription
operationstringYesdiscover-playersdiscover-players, discover-matches, scrape-matches, or scrape-match-ids
searchstringFor discover-playersPlayer name, e.g. Sinner
playerIdsstring arrayFor discover-matches / scrape-matchesATP player IDs, e.g. S0AG
matchRequestsarrayFor scrape-match-idsUp to 250 {matchId, requestedPlayerIds} objects
maxMatchesPerPlayerintegerNo5Most recent matches per player (0 = no per-player cap)
maxTotalMatchesintegerNo20Hard ceiling on billed match records per run, after dedup
matchFormatstringNoallall or singles (skip doubles entirely)
includeStatsbooleanNotrueInclude the stats array in match records
proxyobjectNoApify ResidentialProxy configuration. ATP's Cloudflare protection blocks non-residential IPs, so this defaults to Apify's residential proxy group automatically — most users never need to change it

The four operations

discover-players — search the player directory:

{ "operation": "discover-players", "search": "Sinner" }

discover-matches — build a deduplicated match plan across players, at no charge (no match records are fetched yet):

{
"operation": "discover-matches",
"playerIds": ["S0AG"],
"maxMatchesPerPlayer": 10,
"maxTotalMatches": 50
}

scrape-matches — discover and fetch full match records in one run:

{
"operation": "scrape-matches",
"playerIds": ["S0AG"],
"maxMatchesPerPlayer": 5,
"maxTotalMatches": 20,
"matchFormat": "singles",
"includeStats": true
}

scrape-match-ids — fetch specific matches you already identified (e.g. from a previous discover-matches run):

{
"operation": "scrape-match-ids",
"matchRequests": [
{ "matchId": "2026#540#MS001", "requestedPlayerIds": ["S0AG"] }
]
}

Output example

A match record (Dinko Dinev vs. Valentin Basel, Plovdiv Challenger 2026 — a match with a full set-by-set breakdown):

{
"recordType": "match",
"matchId": "2026#3169#QS024",
"title": "Dinko Dinev vs Valentin Basel | 2026#3169#QS024 | ATP Tour Match Stats",
"tournament": "Plovdiv 2",
"eventType": "CH",
"location": "Plovdiv",
"surface": "Clay",
"round": "1st Round Qualifying",
"matchFormat": "singles",
"status": "F",
"playerIds": ["D0HS", "B0NF"],
"winnerId": "B0NF",
"sets": [
{ "setNumber": 1, "teamAGames": "4", "teamBGames": "6", "tieBreak": null },
{ "setNumber": 2, "teamAGames": "4", "teamBGames": "6", "tieBreak": null }
],
"stats": [
{ "scope": "match", "setNumber": null, "category": "Service", "metric": "Aces", "teamAValue": "1", "teamBValue": "2" },
{ "scope": "set", "setNumber": 1, "category": "Service", "metric": "Break Points Saved", "teamAValue": "60% (3/5)", "teamBValue": "0% (0/1)" }
],
"headToHeadUrl": "https://www.atptour.com/en/players/atp-head-2-head/dinko-dinev-vs-valentin-basel/d0hs/b0nf",
"sourceUrl": "https://www.atptour.com/en/scores/match-stats/archive/2026/3169/qs024"
}

Download results in JSON, CSV, or Excel directly from the Apify Console, or pull them programmatically via the API.

Use cases

  • Match scouting and opponent prep — pull a player's recent results and serve/return numbers before a matchup
  • Tennis analytics and modeling — build serve-performance or match-outcome models from official ATP stats
  • Content and media — power score tickers, head-to-head graphics, or tournament recaps
  • Betting research — compare break-point conversion and serve dominance across recent matches
  • Historical tracking — schedule recurring runs to build a running log of a player's season

How it works

  1. Go to the ATP Tour Match Stats Scraper page on Apify Store
  2. Click Try for free — new users get a free trial to test the Actor before paying
  3. Run discover-players with a player name to find their ATP player ID
  4. Run discover-matches with one or more player IDs to preview a deduplicated match plan at no charge
  5. Run scrape-matches (or scrape-match-ids with specific matches) to fetch full match records with statistics
  6. When the run finishes, download your data as JSON, CSV, or Excel, or fetch it via the API

Use with the Apify API

Python

from apify_client import ApifyClient
client = ApifyClient("YOUR_API_TOKEN")
run = client.actor("parsebird/atp-match-stats-scraper").call(run_input={
"operation": "scrape-matches",
"playerIds": ["S0AG"],
"maxMatchesPerPlayer": 5,
"maxTotalMatches": 20,
"matchFormat": "singles",
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
if item["recordType"] == "match":
print(f"{item['tournament']} {item['round']}: {item['title']}")

JavaScript

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_API_TOKEN' });
const run = await client.actor('parsebird/atp-match-stats-scraper').call({
operation: 'scrape-matches',
playerIds: ['S0AG'],
maxMatchesPerPlayer: 5,
maxTotalMatches: 20,
matchFormat: 'singles',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items
.filter((item) => item.recordType === 'match')
.forEach((item) => console.log(`${item.tournament} ${item.round}: ${item.title}`));

See the Apify API documentation for full reference, or the Actor's API tab for ready-to-run request snippets in more languages. This Actor also works out of the box with Make, Zapier, and other Apify integrations.

How much does it cost to scrape ATP Tour match stats?

This Actor uses Pay-per-event pricing — you only pay for player and match records actually returned, with no separate platform rental fee. discover-matches is free: it returns lightweight match-reference records so you can preview and size a run before spending anything.

EventPrice per eventPrice per 1,000
match-scraped$0.025$25.00
player-scraped$0.0019$1.90

match-scraped is charged once per full match record (recordType: "match") returned by scrape-matches or scrape-match-ids — not for match-reference or match-exclusion records. player-scraped is charged once per player returned by discover-players. Pulling 20 matches for one player costs about $0.50 on the Free plan. Start with a small maxTotalMatches on the free trial to preview a player's data before scaling up.

FAQ

Where does this data come from? Directly from atptour.com, the official ATP Tour website — the same JSON endpoints that power its own player pages, match centre, and live scoreboards.

Does this include shot-by-shot, point-by-point rally data? No. This Actor returns match- and set-level statistics (aces, double faults, break points, serve/return percentages) and set scores, which is the full depth of statistical data ATP's public site exposes for the vast majority of matches. It does not include ball-by-ball rally or shot-type data.

Why is the sets array sometimes empty? ATP publishes a full set-by-set statistics breakdown for most matches, but for some matches — including several Grand Slam matches in our own testing — only the match-level aggregate is available from the source. When that happens, sets and per-set stats rows are omitted, but match-level stats and all match metadata are still complete.

What's the difference between discover-matches and scrape-matches? discover-matches returns lightweight match-reference records (score, round, opponent) for free, so you can review and size a batch first. scrape-matches does the same discovery step and then fetches the full match record — including statistics — for each match, billed per match.

How do I avoid paying for matches I don't want? Run discover-matches first, review the match-reference records, then pass the specific matchId values you want into scrape-match-ids. This guarantees you only pay for matches you've explicitly chosen.

Do I need to configure a proxy? No. ATP Tour's Cloudflare protection blocks non-residential traffic, so this Actor defaults to Apify's residential proxy group automatically on every run — you only need to touch the proxy input if you want to use your own proxy instead.

Does matchFormat: "singles" save money on doubles matches? Yes. When set to singles, doubles matches are detected from ATP's own match code (MD/QD) and skipped before any match-stats request is made — they're returned as unbilled match-exclusion records instead of full match records.

Can I schedule recurring runs? Yes. Use Apify's Scheduler to run this Actor daily or weekly and pick up new matches as a tournament or season progresses.

Can I access results via API? Yes. Every run's dataset is available through the Apify API and client libraries (Python, JavaScript) — see the Actor's API tab for ready-made snippets.

Something not working? Open an issue on the Actor's Issues tab in Apify Console — the ParseBird team monitors it directly.

ATP Tour publishes player profiles, match results, and statistics for public viewing without a login or paywall. This Actor only reads publicly accessible data — it does not bypass any authentication. You're responsible for complying with atptour.com's terms of use for your specific use case. See Apify's blog post on the legality of web scraping for general background.

Other tennis and data-extraction Actors from ParseBird: