# Chess Player Stats Scraper (`bigibyte/chess-player-stats-scraper`) Actor

Chess.com and Lichess scraper for public player ratings, game history, and leaderboards. It collects profiles, win/loss records by time control, PGN, openings, Chess.com accuracies, titled player lists, and club info from the official APIs. No login.

- **URL**: https://apify.com/bigibyte/chess-player-stats-scraper.md
- **Developed by:** [BigiByte](https://apify.com/bigibyte) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## 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

## Chess.com and Lichess Scraper: Player Ratings, PGN Game History, and Leaderboards

This chess scraper reads the public Chess.com and Lichess APIs and saves player ratings, game history with PGN, leaderboards, titled-player lists, and club info. You can download JSON, CSV, or Excel, or send the dataset to Google Sheets, Make, Zapier, or n8n. There is no login.

Requests are strictly one at a time per site. Chess.com and Lichess can run at the same time as each other. That matches both sites' published rules. A 429 waits 60 seconds and retries once. A second 429 stops that site for the rest of the run. A Chess.com 410 is not requested again.

### Use cases

- Chess coaches and scouts: pull an opponent's ratings and recent games before a match.
- Streamers: keep a public rating card and a stream link for the players you cover.
- Tournament organizers: export a Chess.com GM, IM, or FM list for invitations.
- Chess model and engine work: download rated games with PGN, openings, and Chess.com accuracies.
- Club admins: record member count, average daily rating, and admins for a Chess.com club.
- Sponsors: find titled streamers and the follower counts Chess.com publishes.

### How to run it

1. Pick a mode and a platform. A bare username on "both" is looked up on Chess.com and on Lichess. A profile URL stays on that site.
2. For games, set a per-player cap. The newest months are read first.
3. Set the row cap. The free cap is 20,000 rows per run.
4. Start the run. A missing username becomes an error row and the run continues.

### Input

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `mode` | string | `players` | `players`, `games`, `leaderboards`, `titled`, `club`, `country`, or `perf`. |
| `platform` | string | `both` | `chesscom`, `lichess`, or `both`. |
| `usernames` | string\[] | `hikaru`, `DrNykterstein` | Usernames or `chess.com/member/` and `lichess.org/@/` URLs. |
| `includeClubs` | boolean | `false` | Chess.com clubs on each player. Extra request per player. |
| `gamesFromMonth` | string | empty | Oldest month, `YYYY-MM`. |
| `gamesToMonth` | string | empty | Newest month, `YYYY-MM`. |
| `timeClasses` | string\[] | empty | bullet, blitz, rapid, classical, daily, correspondence, ultraBullet. |
| `includePgn` | boolean | `true` | Store PGN on each game. |
| `maxGamesPerPlayer` | integer | `200` | Newest games kept. Maximum 5,000. |
| `lichessPerf` | string | `blitz` | Lichess leaderboard or perf type. |
| `leaderboardSize` | integer | `100` | Lichess top N. Maximum 200. Top 10 was the live check. |
| `titles` | string\[] | `GM` | Chess.com title lists. |
| `clubIds` | string\[] | empty | Club URL id or club page URL. |
| `countryCodes` | string\[] | empty | ISO codes such as `US`. |
| `enrichUsernames` | boolean | `false` | Fetch profiles for titled and country usernames, inside the row cap. |
| `maxItems` | integer | `500` | Row cap. Maximum 20,000. |
| `timeoutSecs` | integer | `300` | Stop after this many seconds and keep saved rows. |
| `requestTimeoutSecs` | integer | `30` | Per-request timeout. |
| `proxyConfiguration` | object | No proxy | Leave off unless a site returns 403 or 429. |

#### Player ratings

```json
{
    "mode": "players",
    "platform": "both",
    "usernames": ["hikaru", "https://lichess.org/@/DrNykterstein"],
    "includeClubs": false
}
```

#### Games with PGN

```json
{
    "mode": "games",
    "platform": "chesscom",
    "usernames": ["hikaru"],
    "gamesFromMonth": "2026-09",
    "gamesToMonth": "2026-09",
    "timeClasses": ["blitz"],
    "maxGamesPerPlayer": 50,
    "includePgn": true
}
```

#### Leaderboards, titles, clubs, countries, perf

```json
{ "mode": "leaderboards", "platform": "lichess", "lichessPerf": "blitz", "leaderboardSize": 10 }
```

```json
{ "mode": "titled", "platform": "chesscom", "titles": ["GM"], "maxItems": 200 }
```

```json
{ "mode": "club", "platform": "chesscom", "clubIds": ["chess-com-developer-community"] }
```

```json
{ "mode": "country", "platform": "chesscom", "countryCodes": ["US"], "maxItems": 100 }
```

```json
{ "mode": "perf", "platform": "lichess", "usernames": ["DrNykterstein"], "lichessPerf": "blitz" }
```

### What data does this actor extract?

Player rows include `platform`, `username`, `displayName`, `profileUrl`, `title`, `country`, `followers`, `joinedAt`, `ratings`, and `blitzRating`. Lichess rows also include `gamesCount` and `playTimeTotalSec`. Chess.com ratings cover bullet, blitz, rapid, daily, chess960, fide, tactics, and puzzle rush.

The player table uses `playerTitle` because Apify's dataset schema rejects the field name `title`. The JSON row still includes `title`. `blitzRating` is the same number as `ratings.blitz.rating`.

Game rows include both players, `result` (`1-0`, `0-1`, or `½-½`), `timeClass`, `opening`, Chess.com `accuracies` when the site sent them, and `pgn` when you leave that option on. Chess960 games keep `initialSetup` when the start position is not the standard one.

Leaderboard rows include `board`, `rank`, `username`, `score`, and `profileUrl`. Titled and country rows include `listName` and `username`. Club rows include `membersCount`, `averageDailyRating`, `admins`, and `clubDescription`. The club member list endpoint is not called. It returned an internal error in testing.

Failures use `error` and `statusCode`. Codes: `NOT_FOUND`, `NO_DATA`, `HTTP_<code>`, `HTTP_410`, `RATE_LIMITED`, `PARSE_ERROR`, `INVALID_INPUT`. The same player or game is saved once.

### Limits and fair use

- One request at a time per site. Do not raise that. Both Chess.com and Lichess say parallel traffic can be blocked.
- On a 429 the actor waits 60 seconds, retries once, then stops that site.
- Chess.com says its published data refreshes at most once every 12 hours.
- Public APIs only. No login, no private games, and no password-protected content.
- Free runs cap at 20,000 rows, and at 5,000 games per player.
- For a full Lichess game dump, use the monthly database at https://database.lichess.org instead of paging the user export.
- The Lichess rating-history endpoint came back empty for the accounts we checked, so it is not used.
- Proxy stays off. One reused proxy is available if your IP is blocked. Rotating the address on every request is a bad fit for these APIs.

### FAQ

**Does this need a Chess.com or Lichess account?**
No. It uses the public published APIs.

**Why is a username missing on one site?**
`hikaru` and `DrNykterstein` are not the same account on both sites. With platform set to both, a name that exists on only one site produces a `NOT_FOUND` row for the other.

**Can I download every game a player has ever played?**
You can page monthly archives up to the per-player cap. A single Chess.com month can be hundreds of games. For all of Lichess, use database.lichess.org.

**Why are emails and phone numbers not in the output?**
These APIs do not publish them.

**What does a second 429 mean?**
That site asked us to slow down even after a one-minute wait. The actor stops calling it and keeps whatever it already saved. The other site is not affected.

### Related

BigiByte also builds Twitch and YouTube creator actors if you want streamer audience data next to these ratings.

Need chess analytics dashboards, player scouting reports, or a custom game-data pipeline? BigiByte builds it: https://bigibyte.com

### Trademark Notice

Chess.com and Lichess and related names, logos, and trademarks belong to their respective owners. Their use in this documentation is solely to identify the source of the data and does not imply affiliation, sponsorship, endorsement, or partnership.

### Disclaimer

This Actor is an independent tool and is not affiliated with, endorsed by, sponsored by, or otherwise officially associated with Chess.com and Lichess.

### Is web scraping legal?

Collecting information that is already public is generally legal. Scraping is not the same thing as hacking, and there is no single law that bans it. The limits are the usual ones: do not break into accounts, do not take private or confidential data, and be careful with personal data and copyrighted works. A site's terms can also restrict how you access it. Facts such as a public name, price, or rating are not the same thing as republishing someone's article or photo.

This actor reads public pages or official public APIs. It does not log in and it does not bypass access controls.

This is not legal advice. For the longer explanation, see Apify's article [Is web scraping legal?](https://blog.apify.com/is-web-scraping-legal/).

# Actor input Schema

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

Player profiles, game history, leaderboards, Chess.com titled or country lists, club info, or a Lichess perf report.

## `platform` (type: `string`):

Chess.com, Lichess, or both. A profile URL stays on the site it points to. Titled, club, and country lists are Chess.com. Perf stats are Lichess.

## `usernames` (type: `array`):

Usernames or profile URLs. chess.com/member/x and lichess.org/@/x URLs are accepted, and the platform is detected from the URL.

## `includeClubs` (type: `boolean`):

Add the Chess.com clubs listed on the player profile. This makes one extra request per player.

## `gamesFromMonth` (type: `string`):

Oldest month to include, as YYYY-MM. Leave empty to start at the newest games.

## `gamesToMonth` (type: `string`):

Newest month to include, as YYYY-MM. Leave empty to include the latest month.

## `timeClasses` (type: `array`):

Keep only these time controls. Leave empty for every time control the site returns.

## `includePgn` (type: `boolean`):

Store the PGN on each game. Turn this off to keep the dataset smaller.

## `maxGamesPerPlayer` (type: `integer`):

Stop after this many games for each player. Newest games are kept first.

## `lichessPerf` (type: `string`):

Lichess speed or variant for leaderboard and perf modes.

## `leaderboardSize` (type: `integer`):

How many Lichess players to take from the top list. Only the top 10 was verified live.

## `titles` (type: `array`):

Chess.com title lists to download, such as GM or IM.

## `clubIds` (type: `array`):

Club URL ids or club page URLs, for club mode. Example: chess-com-developer-community.

## `countryCodes` (type: `array`):

Two-letter country codes for Chess.com country mode, such as US or BD.

## `enrichUsernames` (type: `boolean`):

After a titled or country list, fetch each player's profile and ratings. This stays inside the row cap and the one-request-at-a-time rule.

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

Free cap: 20,000 rows per run.

## `timeoutSecs` (type: `integer`):

Stop the run after this many seconds and keep the rows already saved.

## `requestTimeoutSecs` (type: `integer`):

Per-request timeout.

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

Leave this off. Both sites allow serial requests from a normal connection. Enable it only if you see 403 or 429.

## Actor input object example

```json
{
  "mode": "players",
  "platform": "both",
  "usernames": [
    "hikaru",
    "https://lichess.org/@/DrNykterstein"
  ],
  "includeClubs": false,
  "includePgn": true,
  "maxGamesPerPlayer": 200,
  "lichessPerf": "blitz",
  "leaderboardSize": 100,
  "titles": [
    "GM"
  ],
  "clubIds": [
    "chess-com-developer-community"
  ],
  "countryCodes": [
    "US"
  ],
  "enrichUsernames": false,
  "maxItems": 500,
  "timeoutSecs": 300,
  "requestTimeoutSecs": 30,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `players` (type: `string`):

Username, platform, title, country, and blitz rating.

## `games` (type: `string`):

Played time, player, result, time class, and game URL.

## `leaderboards` (type: `string`):

Platform, board, rank, username, and score.

## `errors` (type: `string`):

Inputs that failed, with the error code and HTTP status.

## `dataset` (type: `string`):

Every row from the run, without a table view applied.

# 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 = {
    "usernames": [
        "hikaru",
        "DrNykterstein"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("bigibyte/chess-player-stats-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 = {
    "usernames": [
        "hikaru",
        "DrNykterstein",
    ],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("bigibyte/chess-player-stats-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 '{
  "usernames": [
    "hikaru",
    "DrNykterstein"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call bigibyte/chess-player-stats-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,bigibyte/chess-player-stats-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/0a9bud7qwK7KvQFEE/builds/gVvj9qMaopO5ncPYp/openapi.json
