Skip to main content
Thirdwatchthirdwatch
Other

Build Surface-Split Stats from a Player's Match Log

A player's full match log labelled by surface — the input for clay/grass/hard performance splits.

Sep 16, 2026 · 3 min read · 508 words
See the scraper →

Surface is tennis's biggest split — every match row carries it. Thirdwatch's Tennis Abstract Scraper returns any ATP or WTA player's full match log — surface, round, opponent, score, and the deep serve/return stats that make Tennis Abstract the analyst's source.

Skip the setup: Run this as a ready-to-go task on Apify — pre-loaded with the configuration from this guide.

Why build surface splits

'Nadal on clay vs Nadal elsewhere' is the canonical example, but surface splits matter for every player and every matchup model. The split needs the whole log labelled by surface.

surface is on every row — group the pulled log by it and the splits compute themselves.

The scraper reads the site's per-player match-log data directly, so a player name in means the complete history out.

How does this compare to the alternatives?

Approach Cost model Coverage Effort
Tennis Abstract site Free Browser tables Copy-paste per player
Tennis-data CSV dumps Free Historic bulk files No recent matches, coarse fields
Thirdwatch Tennis Abstract Scraper Pay per match row Full match logs, deep stats Player name in, rows out

Why this Actor

  • Full match logs per player — the complete career arc, not just recent form.
  • Deep stats per match: serve/return points, aces, double faults, break points.
  • ATP and WTA coverage — any player name resolves.
  • Context fields on every row: tournament, surface, round, both rankings.
  • Player names or slugs — no ID lookup needed.

How to do it in 3 steps

Step 1: Configure the input

Set the inputs as shown below — players takes the targets, player names or slugs; maxMatches bounds how far back each player's log goes.

Step 2: Run the Actor

Run it from the console, the API, or the linked saved task. One dataset row is written per match.

Step 3: Use the output

Each row carries the match context — date, tournament, surface, round, opponent, score, rankings — plus the serve and return stat columns Tennis Abstract publishes.

{
  "players": [
    "Rafael Nadal"
  ],
  "maxMatches": 50
}

Each dataset row looks like:

{
  "player": "Novak Djokovic",
  "playerSlug": "NovakDjokovic",
  "date": "2026-07-13",
  "tournament": "Wimbledon",
  "surface": "Grass",
  "round": "F",
  "opponent": "Jannik Sinner",
  "score": "6-4 3-6 7-6 6-3",
  "rank": "3",
  "opp_rank": "1",
  "serve_pts": "74/102",
  "aces": "9"
}

What to watch for

Coverage follows Tennis Abstract's own archive — exhibitions and some lower-tier events are absent. A few unusual table columns may appear as colN on older rows.

Related use cases

Run the Tennis Abstract Scraper on Apify Store — pay per result, free to try, no credit card to test.

Frequently asked questions

How far back does the data go?

As far as Tennis Abstract's own archive for that player — full career for established players.

Does it cover WTA as well as ATP?

Yes — women's players work the same way; pass the name and the slug resolves.

What stats come back per match?

The columns Tennis Abstract publishes — date, tournament, surface, round, opponent, score, rankings, plus serve/return performance fields.

Can I compare two players?

Yes — pass both names in `players` and each row is labelled with `player`/`playerSlug` for grouping.

What about very old matches?

Older archive rows can be sparser on advanced stats — coverage follows the site's own data depth.

How do I get just this season?

`maxMatches` bounds the pull from most-recent backwards — 50 covers a full season for most players.

Related

Try it yourself

100 free credits, no credit card.

About 30 real searches. Add the MCP to Claude or Cursor in two minutes.