# EnduHub Runner Results Scraper (`alex_demeniuk/enduhub-runner-results-scraper`) Actor

Scrape EnduHub runner results for athletes from a TXT file, filtered by distance and date range.

- **URL**: https://apify.com/alex\_demeniuk/enduhub-runner-results-scraper.md
- **Developed by:** [Alex Demeniuk](https://apify.com/alex_demeniuk) (community)
- **Categories:** Other, Automation
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

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

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

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

## EnduHub Runner Results Scraper

Apify Actor for collecting runner results from EnduHub by athlete name, with filters for distance and date range.

Users run it directly in Apify Console by uploading a TXT file with athlete names, selecting a distance, and setting the date interval.

### What is EnduHub?

[EnduHub](https://enduhub.com/) is a Polish sports results platform that describes itself as the largest database of sports event results in Poland.

It aggregates and organizes results from running races, triathlons, marathons, and other sports events so athletes can find results, track progress, compare themselves with others, and explore new competitions.

This Actor works with EnduHub athlete search pages and extracts structured race-result data for a list of athlete names.

It is designed for cloud use on Apify, so users configure the run through the Actor input form in the web interface.

### What the Actor does

For each athlete from the uploaded TXT list, the Actor:

- searches EnduHub by athlete name;
- applies the selected distance filter;
- applies the selected date range;
- parses the visible results table;
- continues collecting older results by moving backward through dates;
- deduplicates repeated rows;
- normalizes dates and selected distance labels;
- computes average pace when possible;
- stores one dataset item per athlete.

The scraper is designed to continue beyond the newest visible rows by repeatedly narrowing the upper date bound.

This helps collect more results than a single visible search page would provide.

Inside each athlete item, results are sorted from fastest pace to slowest pace, and rows without a computable pace are placed last.

### How to use in Apify Console

Open the Actor in Apify Console and fill in the input form.

The form allows you to upload a TXT file, choose a distance from a dropdown, and enter the start and end dates directly in the web UI.

#### Input fields

- **Athletes TXT file** — upload a `.txt` file with one athlete name per line.
- **Distance** — choose the EnduHub distance filter.
- **Date from** — lower bound for the search interval, inclusive, in `YYYY-MM-DD` format.
- **Date to** — upper bound for the search interval, inclusive, in `YYYY-MM-DD` format.

The uploaded TXT file should contain one athlete name per line, for example:

    Jan Kowalski
    Anna Nowak
    Piotr Zielinski

The Actor automatically trims extra spaces, ignores blank lines, and removes duplicate names while preserving the original order from the uploaded file.

#### Supported distance values

- `""` — All
- `"7"` — 5 km
- `"8"` — 10 km
- `"20"` — 15 km
- `"2"` — Half Marathon
- `"1"` — Marathon
- `"9"` — 0-5 km
- `"10"` — 5-20 km
- `"11"` — 20-50 km
- `"12"` — 50-200 km
- `"13"` — Above 200 km

#### Example cloud input

If you run the Actor through the Apify API, `athletesFile` can be provided either as plain TXT content or as a file reference generated by Apify file upload fields.

Example with inline TXT content:

    {
      "athletesFile": "Jan Kowalski\nAnna Nowak\nPiotr Zielinski\n",
      "distanceId": "",
      "dateMin": "2024-01-01",
      "dateMax": "2026-12-31"
    }

In Apify Console, you usually do not need to paste raw JSON manually, because the input UI provides a file upload field and separate controls for the other values.

### Output

The Actor stores results in the default Apify dataset.

It writes one dataset item per input athlete.

Each dataset item contains these top-level fields:

- `input_name`
- `results_count`
- `best_event`
- `best_date`
- `best_result`
- `best_distance`
- `best_average_pace`
- `results`

Each entry inside `results` can include:

- `place`
- `bib_number`
- `year_of_birth`
- `athlete_name`
- `athlete_url`
- `event_name`
- `event_url`
- `location`
- `result`
- `country`
- `distance`
- `sport`
- `date`
- `average_pace`

#### Example dataset item

    {
      "input_name": "Jan Kowalski",
      "results_count": 3,
      "best_event": "Example Race",
      "best_date": "2025-10-12",
      "best_result": "01:25:30",
      "best_distance": "Half Marathon",
      "best_average_pace": "04:03 /km",
      "results": [
        {
          "place": "12",
          "bib_number": "145",
          "year_of_birth": "1990",
          "athlete_name": "Jan Kowalski",
          "athlete_url": "https://enduhub.com/...",
          "event_name": "Example Race",
          "event_url": "https://enduhub.com/...",
          "location": "Warsaw",
          "result": "01:25:30",
          "country": "Poland",
          "distance": "Half Marathon",
          "sport": "Running",
          "date": "2025-10-12",
          "average_pace": "04:03 /km"
        }
      ]
    }

### Data normalization

The Actor normalizes several values before saving output.

Dates are converted to `YYYY-MM-DD` when parsing succeeds.

Selected Polish distance labels such as `Półmaraton` and `Maraton` are normalized to `Half Marathon` and `Marathon`.

Average pace is computed from result time and distance whenever both values are usable.

If pace cannot be computed, the `average_pace` field is left empty.

Rows with missing or non-computable pace are still included in the output, but they are sorted after rows with valid pace values.

### Typical use cases

- Build a structured history of race results for a club or training group.
- Compare multiple athletes over the same date range and distance filter.
- Export the dataset from Apify for additional ranking, filtering, or reporting.
- Analyze recent race activity for a shortlist of runners uploaded as a TXT file.

### Notes

The Actor works against EnduHub search result pages and depends on the structure of those pages remaining reasonably stable over time.

If EnduHub changes its HTML structure, filtering behavior, or visibility rules, the Actor may require an update.

This Actor is intended for Apify cloud usage.

Users configure runs in the Apify web interface or through the Apify API, and the dataset output can then be viewed, exported, or connected to downstream automations on the Apify platform.

# Actor input Schema

## `athletesFile` (type: `string`):

Upload a TXT file with one athlete name per line in the format Firstname Lastname. Empty lines, duplicates, and extra whitespace will be cleaned automatically.
## `distanceId` (type: `string`):

Select the EnduHub distance filter.
## `dateMin` (type: `string`):

Lower bound for the EnduHub date filter (inclusive), in YYYY-MM-DD format.
## `dateMax` (type: `string`):

Upper bound for the EnduHub date filter (inclusive), in YYYY-MM-DD format. Leave empty to use today's date.

## Actor input object example

```json
{
  "distanceId": "",
  "dateMin": "2025-12-31"
}
````

# Actor output Schema

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

Dataset items with one output item per athlete and nested race results.

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

API endpoint of the default dataset used by the Actor run.

## `kvStore` (type: `string`):

API endpoint of the default key-value store for the Actor run.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("alex_demeniuk/enduhub-runner-results-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("alex_demeniuk/enduhub-runner-results-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call alex_demeniuk/enduhub-runner-results-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=alex_demeniuk/enduhub-runner-results-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "EnduHub Runner Results Scraper",
        "description": "Scrape EnduHub runner results for athletes from a TXT file, filtered by distance and date range.",
        "version": "0.0",
        "x-build-id": "3E1xzdkytACsdUAmb"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/alex_demeniuk~enduhub-runner-results-scraper/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-alex_demeniuk-enduhub-runner-results-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/alex_demeniuk~enduhub-runner-results-scraper/runs": {
            "post": {
                "operationId": "runs-sync-alex_demeniuk-enduhub-runner-results-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/alex_demeniuk~enduhub-runner-results-scraper/run-sync": {
            "post": {
                "operationId": "run-sync-alex_demeniuk-enduhub-runner-results-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "distanceId",
                    "dateMin"
                ],
                "properties": {
                    "athletesFile": {
                        "title": "Athletes TXT file",
                        "type": "string",
                        "description": "Upload a TXT file with one athlete name per line in the format Firstname Lastname. Empty lines, duplicates, and extra whitespace will be cleaned automatically."
                    },
                    "distanceId": {
                        "title": "Distance",
                        "enum": [
                            "",
                            "7",
                            "8",
                            "20",
                            "2",
                            "1",
                            "9",
                            "10",
                            "11",
                            "12",
                            "13"
                        ],
                        "type": "string",
                        "description": "Select the EnduHub distance filter.",
                        "default": ""
                    },
                    "dateMin": {
                        "title": "Date from",
                        "type": "string",
                        "description": "Lower bound for the EnduHub date filter (inclusive), in YYYY-MM-DD format.",
                        "default": "2025-12-31"
                    },
                    "dateMax": {
                        "title": "Date to",
                        "type": "string",
                        "description": "Upper bound for the EnduHub date filter (inclusive), in YYYY-MM-DD format. Leave empty to use today's date."
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
