# Decathlon.es - Prices, Discounts, Specs & Reviews (`abotapi/decathlon-es-scraper`) Actor

Scrape Decathlon Spain (decathlon.es) sporting goods: current price plus strike-through discount, brand, seller, technical specifications, colour/size availability, GTINs, and reviews. Search by keyword/category with brand and price filters, or paste product and listing links.

- **URL**: https://apify.com/abotapi/decathlon-es-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** E-commerce, Automation, Developer tools
- **Features:** Limited permissions
- **Stats:** 4 total users, 0 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 product 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

## Decathlon Spain Scraper: Prices, Discounts, Specs & Reviews

Decathlon Spain Scraper turns decathlon.es into structured product data. Get the current price plus the strike through original price and discount percentage when a product is marked down, technical specifications, colour and size availability, first party vs marketplace seller, and reviews straight from Decathlon's own reviews API. Search by keyword or a real decathlon.es category, filter by brand and price, or paste product and listing links directly, then export to JSON, CSV or Excel, or pull results straight into your app through the API.

### Why This Scraper?

- **Was price and discount, structured, not scraped from a badge.** When a product is marked down, both prices are captured plus the discount percentage and sale end date, gated on the site's own real reference price pair, never fabricated on full price items.
- **First party vs marketplace seller.** Decathlon Spain also runs a marketplace with third party sellers, and every record tells you which one sold it.
- **Real technical specifications.** Practice level, terrain, fit, material, waterproofing and more, straight from the product page's own structured attribute list, not scraped free text.
- **Reviews straight from Decathlon's own API.** Overall rating, review count, recommended count and individual reviews, rating, title, body, author, date and verified buyer flag, not a third party review vendor.
- **Real filters and sort.** Brand narrows the actual server side result set, on sale filters each page as it's read, and sorting by price, highest discount or highest rated reorders the full result set, not just one fetched page.
- **Two ways in.** Keyword or category search with real site filters, or paste any product, search or category link and pagination continues forward automatically.
- **Incremental mode for scheduled monitoring.** Run the same search again and again and get back only new and updated products by default, with unchanged products suppressed and not billed. Turn on emit expired to also mark products that disappear once a run scans the whole tracked search, and if one of those later comes back it is reported as reappeared.

### Use Cases

- **Price and promotion tracking:** watch the was price, discount percentage and sale end date across a category to catch markdowns as they go live.
- **Assortment and competitive research:** compare brand mix, price bands and first party vs marketplace listings across a category or search.
- **Product content for catalogs:** pull structured technical specifications, composition and care instructions for a comparison site or internal catalog.
- **Review and rating analysis:** aggregate rating, review counts and individual reviews to gauge customer sentiment on specific models.
- **Size and stock monitoring:** track which sizes and colours remain available online for a watched list of products.

### Data You Get

| Field | Example |
| --- | --- |
| `productId` / `supermodelId` / `sku` | `"0000000"` / `"000000"` / `"00000000-0000-0000-0000-000000000000"` |
| `name` | `"Sample Trail Running Jacket - Navy and Lime"` |
| `brand` | `"SAMPLE BRAND"` |
| `url` | `"https://www.decathlon.es/es/p/sample-trail-running-jacket/000000/c0000m0000000"` |
| `price` / `currency` | `44.99` / `"EUR"` |
| `originalPrice` / `discountPercent` | `64.99` / `31` |
| `isOnSpecial` / `saleEndsAt` | `true` / `"2026-11-30T22:00:00.000Z"` |
| `onlineAvailable` / `inStock` | `true` / `true` (`inStock` is the same availability signal under the name most stock monitors expect) |
| `availableSizes` | `["S", "M", "L", "XL"]` |
| `seller` | `{"name": "Decathlon", "type": "FIRST_PARTY"}` |
| `sports` | `["Hiking"]` |
| `image` / `images` | `["https://contents.mediadecathlon.com/sample/picture.jpg"]` |
| `rating` / `reviewCount` | `4.3` / `214` |
| `category` / `categoryPath` | `"Sample Brand"` / `["Deportes", "Trekking", "Marcas", "Sample Brand"]` |
| `description` | full product description text as shown on the site |
| `specifications` | `[{"name": "Nivel de práctica", "value": "Intermedio"}, {"name": "Material", "value": "Poliéster reciclado"}]` |
| `composition` / `careInstructions` / `guarantee` | material composition, care text and guarantee terms, when the site provides them |
| `colors` | `[{"name": "azul marino", "hexaCode": "#1B2A4A"}]` |
| `gtins` | `["0000000000000"]` |
| `reviews.averageRating` / `reviews.totalReviewCount` / `reviews.recommendedCount` / `reviews.percentageSatisfied` / `reviews.ratingDistribution` | `4.31` / `214` / `178` / `83.75` / `{"5": 120, "4": 40, "3": 20, "2": 14, "1": 20}` |
| `reviews.items[]` | `[{"rating": 4, "title": "Muy resistente", "body": "Sample review text.", "author": "Sample User", "date": "2026-08-02T09:15:00+00:00", "recommended": true, "verifiedBuyer": true, "reviewId": "00000000-0000-0000-0000-000000000000", "replies": [{"author": "Respuesta de Decathlon", "date": "2026-08-03T10:00:00+00:00", "message": "Sample reply."}], "usedSince": "de 2 a 8 semanas", "incentivized": false}]` |
| `scrapedAt` | `"2026-09-29T00:00:00.000000+00:00"` |
| `changeType` (incremental mode only) | `"NEW"`, `"UPDATED"`, `"UNCHANGED"`, `"REAPPEARED"` or `"EXPIRED"` |
| `changedFields` (incremental mode only) | `["price", "isOnSpecial"]` |
| `firstSeenAt` / `lastSeenAt` (incremental mode only) | `"2026-09-01T00:00:00Z"` / `"2026-09-29T00:00:00Z"` |

> Sample shape: values above are illustrative placeholders, not from a live product.

Every record is a product. `specifications`, `composition`, `careInstructions`, `guarantee`, `colors`, `gtins`, `categoryPath` and a short list of merchandising tags are only present when full product detail is fetched; `reviews` reflects whatever the reviews toggle was set to (an empty `reviews` object when it is off); `changeType`, `changedFields`, `firstSeenAt` and `lastSeenAt` only appear when incremental mode is on.

### How to Use

1. Pick a **mode**: `search` (keyword or category, with filters and sort) or `url` (paste product, search or category links).
2. Fill in the fields for that mode, and switch on full product detail and/or reviews if you need them.
3. Set **Max products total** (and optionally **Max pages per search**) to control run size and cost, then click **Start**.
4. Download the dataset as JSON, CSV or Excel, or read it through the API.

**Keyword search, first page, with reviews (default):**

```json
{
  "mode": "search",
  "searchTerm": "zapatillas running",
  "maxItems": 20
}
```

**Category browse, filtered to a brand, sorted by highest discount, on sale only:**

```json
{
  "mode": "search",
  "category": "deportes/running/zapatillas-running",
  "brand": "ADIDAS",
  "specialsOnly": true,
  "sortBy": "DISCOUNT_DESC",
  "maxItems": 50,
  "maxPages": 5
}
```

**Full product detail (specifications and GTINs) and reviews for a pasted product link:**

```json
{
  "mode": "url",
  "urls": ["https://www.decathlon.es/es/p/zapatillas-running-adidas-runblaze-mujer-gris-y-rosa/361443/c2c24m8930464"],
  "fetchDetails": true,
  "fetchReviews": true
}
```

**Paste a category listing page and continue pagination forward from where it left off:**

```json
{
  "mode": "url",
  "urls": ["https://www.decathlon.es/es/deportes/running/zapatillas-running?from=40&size=40"],
  "maxItems": 100,
  "maxPages": 10
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/decathlon-es-scraper").call(run_input={"mode": "search", "searchTerm": "zapatillas running", "maxItems": 20})
for product in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(product["name"], product["price"], product["currency"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/decathlon-es-scraper').call({ mode: 'search', searchTerm: 'zapatillas running', maxItems: 20 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### Caps: Max pages per search and Max products total

**Max products total** is the hard stop across every search, category or pasted URL in the run. **Max pages per search** additionally caps how many result pages are walked per individual search, category or listing URL before moving to the next one; leave it at `0` (unlimited) to let **Max products total** be the only stop. A search or category listing walk stops naturally at an empty page or a page with fewer products than the page size, in addition to either cap.

#### Resume and recurring updates

- **Resume** (`resumeFromRunId`) continues one interrupted run: paste its run or dataset id and the actor skips every product already collected there, so you don't pay twice. It walks the same search from the start again, using that skip list to return only what wasn't collected before, so a capped or interrupted run can still reach genuinely new products further down the result set. This does not currently extend to a pasted single product URL in URL mode: a pasted product is read (and billed) again on every run regardless of Resume, so avoid relying on Resume to save cost on a URL-mode run made up only of individual product links.
- **Incremental mode** (`incrementalMode`) is for running the same search, category or URL set again and again on a schedule. Each product is classified `NEW`, `UPDATED` (with `changedFields`), or `UNCHANGED` (suppressed and not billed unless `emitUnchanged` is on). `REAPPEARED` and `EXPIRED` are different: a product is only ever marked `EXPIRED` after a run that scans the tracked search completely, with no page cap reached, no `resumeFromRunId`, and `emitExpired` turned on; only a product tombstoned that way can later come back as `REAPPEARED`. With `emitExpired` off, a product that stops showing up simply stays at its last known state forever, it is never marked `EXPIRED` and can never be marked `REAPPEARED` either. For a pasted single product URL specifically, this currently behaves differently than intended: because a pasted product is never added to the run's own collected-ids list (the same gap Resume runs into above), a URL-mode run made up only of individual product links can count as a complete scan every time, so with Emit expired on, each pasted product gets billed an `EXPIRED` row on every run, then reports as `REAPPEARED` from the second run onward, even though the product never actually disappeared. Leave Emit expired off for a URL-mode run of individual product links until this is addressed. `stateKey` names or shares the stored state; leaving it empty derives a key from the search and detail/reviews settings automatically. With incremental mode off, output is exactly as before this feature existed.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape: the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human readable **summary** of each product, not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify, Settings, Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. If the picker is empty, you haven't authorized a connector yet.
3. For **Notion**, also set `notionParentPageUrl` to the page where item pages should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` (keyword or category) or `url` (paste product or listing links). |
| `searchTerm` | string | none (prefill: `"zapatillas running"`) | Keyword, search mode only. |
| `category` | string | none | A decathlon.es category path (not a full URL, search mode only accepts the path), used only when Search keyword is empty (search mode only). To paste a full category URL, use URL mode instead. |
| `brand` | string | none | Exact brand name to keep, e.g. `ADIDAS`. Applies in both search and URL mode. |
| `specialsOnly` | boolean | `false` | Keep only products currently marked down. Applies in both search and URL mode. |
| `minPrice` | number | none | Only return products priced at or above this amount, in euros. Applies in both search and URL mode. |
| `maxPrice` | number | none | Only return products priced at or below this amount, in euros. Applies in both search and URL mode. |
| `sortBy` | string | `RELEVANCE` | `RELEVANCE`, `PRICE_ASC`, `PRICE_DESC`, `DISCOUNT_DESC` or `RATING_DESC`. Applies in both search and URL mode, and for a pasted search or category URL this overrides whatever sort order was already in the URL. |
| `urls` | array | one example per URL shape (6 URLs) | Product, search or category listing URLs to scrape, URL mode only. Pagination continues forward automatically from any `?from=` already in the URL. A pasted `sort=`/`Ns=` and `facets=` (e.g. a brand facet) are honoured; `sortBy` and `brand` override them when set. A pasted `from` above 0 is a partial scan, so it never marks products `EXPIRED`. Non-decathlon.es and other-country Decathlon URLs are skipped with a warning. |
| `fetchDetails` | boolean | `false` | Adds the full breadcrumb category path, description, technical specifications, composition/care/guarantee text, colour swatches and GTINs. Not applied to a bare pasted product URL, which always fetches full detail regardless of this toggle. |
| `fetchReviews` | boolean | `true` | Fetches the product's own reviews from decathlon.es's reviews API. On by default. |
| `maxReviewsPerProduct` | integer | `20` | Cap on reviews per product when Fetch reviews is on. Review pages are walked until the cap is reached or the product has no more; maximum 1000. |
| `maxPages` | integer | `0` | Cap on result pages per search, category or URL entry. `0` means unlimited, the run then stops at Max products total instead. |
| `maxItems` | integer | `20` | Hard cap on total products returned across every search, category or URL entry. `0` means unlimited; maximum 100000. |
| `resumeFromRunId` | string | none | Paste a previous run id or its dataset id to continue that run: products already collected there are skipped, so this run only returns new products. |
| `incrementalMode` | boolean | `false` | Turn on for daily or recurring monitoring of the same search, category or URL set. |
| `stateKey` | string | none | Incremental mode only. Names or deliberately shares a monitoring campaign's stored state. |
| `emitUnchanged` | boolean | `false` | Incremental mode only. Also return, and bill, products that have not changed, marked `UNCHANGED`. |
| `emitExpired` | boolean | `false` | Incremental mode only. Also return, and bill, products that were tracked before and are no longer found, marked `EXPIRED`, and gates whether such a product can ever be tombstoned at all, not just whether the row is emitted. Only takes effect after a run that fully scans the tracked search. |
| `ignoreFieldsForChanges` | array | `[]` | Incremental mode only. Extra field names (dotted paths for nested fields, e.g. `reviews.items`) that never make a product `UPDATED` or appear in `changedFields`; they stay in the output. |
| `proxy` | object | `{"useApifyProxy": true}` | Proxy & connection settings. Residential proxy is required, see Plan Requirement. |
| `mcpConnectors` | array | none | Optional MCP connectors to export results into (Notion, Linear, Airtable, Apify). |
| `notionParentPageUrl` | string | none | Notion connector only: page under which item pages are created. |
| `maxNotifyListings` | integer | `50` | Cap on items exported to each connector per run. Does not affect the dataset. |

#### URL mode

URL-mode shapes:

| Shape | Example |
|---|---|
| Keyword search | `https://www.decathlon.es/es/search?Ntt=zapatillas+running` |
| Keyword search, paginated and sorted | `https://www.decathlon.es/es/search?Ntt=mochila&from=40&size=40&sort=asc` |
| Keyword search with brand facet | `https://www.decathlon.es/es/search?Ntt=zapatillas&facets=brandLabel%3AKIPRUN_&sort=asc&from=40` |
| Category listing | `https://www.decathlon.es/es/deportes/running/zapatillas-running` |
| Category listing, paginated and sorted | `https://www.decathlon.es/es/deportes/running/zapatillas-running?from=40&size=40&Ns=priceAscending` |
| Sport hub page (partial: only the featured products; not part of the default URLs) | `https://www.decathlon.es/es/deportes/running` |
| Product detail page | `https://www.decathlon.es/es/p/zapatilla-versatil-running-carretera-hombre-kiprun-kipcore-premium-azul/353370/c98c40c227m8958673` |

Behaviour change: in URL mode, leaving `urls` untouched now scrapes one example per shape (previously the field had one sample category prefill and no default). Set `maxItems` to bound the run. A sport hub page such as `/es/deportes/running` also loads, but it mixes a few featured products with many links, so it returns only the handful of products shown on the hub itself (8 in testing), not a full listing; use a leaf category for a full listing. If a pasted page returns no products the run logs a warning for that URL.

### Output Example

```json
{
  "productId": "0000000",
  "supermodelId": "000000",
  "sku": "00000000-0000-0000-0000-000000000000",
  "name": "Sample Trail Running Jacket - Navy and Lime",
  "brand": "SAMPLE BRAND",
  "url": "https://www.decathlon.es/es/p/sample-trail-running-jacket/000000/c0000m0000000",
  "image": "https://contents.mediadecathlon.com/sample/picture.jpg",
  "images": ["https://contents.mediadecathlon.com/sample/picture.jpg"],
  "price": 44.99,
  "currency": "EUR",
  "originalPrice": 64.99,
  "discountPercent": 31,
  "isOnSpecial": true,
  "saleEndsAt": "2026-11-30T22:00:00.000Z",
  "availableSizes": ["S", "M", "L", "XL"],
  "onlineAvailable": true,
  "inStock": true,
  "seller": { "name": "Decathlon", "type": "FIRST_PARTY" },
  "sports": ["Hiking"],
  "nature": "chaqueta de trekking",
  "rating": 4.31,
  "reviewCount": 214,
  "categoryPath": ["Deportes", "Trekking", "Marcas", "Sample Brand"],
  "category": "Sample Brand",
  "description": "Sample product description as shown on the site.",
  "specifications": [
    { "name": "Nivel de práctica", "value": "Intermedio" },
    { "name": "Material", "value": "Poliéster reciclado" }
  ],
  "colors": [{ "name": "azul marino", "hexaCode": "#1B2A4A" }],
  "gtins": ["0000000000000"],
  "reviews": {
    "averageRating": 4.31,
    "totalReviewCount": 214,
    "recommendedCount": 178,
    "percentageSatisfied": 83.75,
    "ratingDistribution": { "5": 120, "4": 40, "3": 20, "2": 14, "1": 20 },
    "items": [
      { "rating": 4, "title": "Muy resistente", "body": "Sample review text.", "author": "Sample User", "date": "2026-08-02T09:15:00+00:00", "recommended": true, "verifiedBuyer": true, "reviewId": "00000000-0000-0000-0000-000000000000", "replies": [], "usedSince": "de 2 a 8 semanas", "incentivized": false }
    ]
  },
  "scrapedAt": "2026-09-29T00:00:00.000000+00:00"
}
```

> Sample shape: values above are illustrative placeholders, not from a live product.

**Views.** The dataset has these views in the Console: Products (overview), Technical specifications (nested), Reviews (one row per product, with that product's nested `reviews` block holding the summary and every collected review), Detail fields (needs Fetch details) and Changes (incremental mode). The run also stores a short `OUTPUT` summary record.

### Plan Requirement

A residential proxy is required. Decathlon Spain answers datacenter connections (and most hosting IPs) with a "Just a moment..." challenge instead of the page, so the default connection is Apify residential proxy, which uses residential traffic on your Apify account. Leaving the proxy setting untouched starts directly on residential. If you explicitly pick another Apify proxy group or a country, that choice is tried first (one attempt) and the run then escalates to residential. Even on residential, an individual exit IP is often challenged, and in testing roughly one page in three was refused by every residential exit it was tried on. So after two fresh residential exits the run escalates, only after those refusals, to a backup connection: Apify's premium unblocking proxy group, which uses the same Apify proxy credential and is billed to your own Apify plan's proxy usage. It is slower (about 35 to 50 seconds per page in testing, against a few seconds on a residential exit that works) but loaded every default example URL in every one of 3 tries each. Once it has loaded a page, later pages go to it directly; if it is refused 3 times in a row it is dropped for the rest of the run and the run goes back to retrying residential exits. A page that does not exist (HTTP 404) is reported as missing and is never retried on the backup. When a page is served by the backup the run log says so once ("Served over the backup connection"). If you supply your own proxy URLs, only those are used and the backup connection is never started. Pinning a country is not recommended: a country-ES residential exit was challenged in testing while the default residential pool mostly loaded.

Apify free plans do not include any Apify proxy. On a free plan the run logs "The selected connection is unavailable to this run. Apify free plans do not include residential proxies." and continues on a backup connection, where some pages may be unavailable. If no page could be loaded the run ends as failed with one of these messages:

- "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required: https://apify.com/pricing. If you entered your own proxy URLs, check that their credentials are valid."
- "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested Decathlon pages. Upgrade at https://apify.com/pricing or supply your own proxy URLs, then try again."
- "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later."

The last one is a connection problem, not an empty search. To get residential access, upgrade at https://apify.com/pricing or supply your own proxy URLs under Proxy & connection.

### FAQ

#### How much does it cost?

Billing has two events: one `apify-default-dataset-item` event per product row pushed to the dataset, and one `detail-enrichment` event once per pushed product on which full detail and/or reviews were actually fetched. Walking more review pages or raising the reviews cap does not add charges. The surcharge is a small one time surcharge on a product when full detail and/or reviews were actually fetched for it. Fetch reviews is on by default, so most runs include that surcharge; turn it off, along with Fetch full product detail, if you only need the listing fields. A pasted single product URL always fetches full detail and is always charged that surcharge, regardless of the Fetch full product detail toggle. Suppressed incremental mode duplicates are never billed. The **Pricing** tab shows the current rates, and **Max products total** caps the cost of any run.

#### Is it legal to scrape Decathlon Spain?

This actor collects only publicly available product data. You are responsible for how you use it: follow decathlon.es's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Product prices and specifications are generally facts, but photos and review text may be subject to third party rights.

#### Can I get only new or changed products on a schedule?

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. Later runs then return only new and updated products, unchanged ones are not billed. Products that disappear are only tracked as expired, and can only later be reported as reappeared, once you turn on **Emit expired** and a run scans the tracked search completely.

#### Ignore fields when detecting changes (incremental mode only)

In incremental mode a product is `UPDATED` when a compared field changes, and `changedFields` names the fields that moved. `scrapedAt`, `rating`, `reviewCount` and the whole `reviews` block are excluded by default, so new reviews never trigger `UPDATED`; they remain in every row. Image CDN shard hosts and cache-buster parameters are normalised too. Add your own names to `ignoreFieldsForChanges` (a dotted path such as `reviews.items` targets a nested field). Changing the list never causes a one-off wave of `UPDATED` rows, because stored products are compared again using the current list.

#### What's the difference between Resume and Incremental mode?

**Resume** continues one specific interrupted run from a pasted run or dataset id, walking the search again but skipping everything that run already collected. **Incremental mode** tracks a recurring search, category or URL set by itself, across separately scheduled runs, and classifies each product against what it saw last time.

#### What happens when pages cannot be loaded?

It depends on how many pages loaded. If no page at all could be loaded (every request was refused or failed), the run ends as failed with one of the three connection messages listed under Plan Requirement, and nothing is collected or billed for that run; run it again in a few minutes. If some pages loaded and others were refused, the run finishes with the products it did collect and logs a warning for each page that could not be loaded, so check the log when a result looks short. A search that loads fine but simply has no matching products finishes successfully with an empty dataset. The run also stops with an error status for a genuine input problem, such as an invalid Resume id, or turning on Incremental mode together with Resume when that search already has saved incremental state.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

### 🔗 Want more e-commerce data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🔌 <a href="https://apify.com/abotapi/leroymerlin-es-scraper"><b>LeroyMerlin.es Scraper</b></a><br>Scrape Leroy Merlin Spain (leroymerlin.es) DIY and home-improvement products: price...</td><td>🛒 <a href="https://apify.com/abotapi/mediamarkt-es-scraper"><b>MediaMarkt.es Scraper</b></a><br>Scrape MediaMarkt Spain (mediamarkt.es) products: current price, strike-through original...</td></tr>
<tr><td>🛒 <a href="https://apify.com/abotapi/druni-es-scraper"><b>druni.es</b></a><br>Scrape Druni beauty, cosmetics and perfume products. Search by keyword, category, Ofertas...</td><td>🧩 <a href="https://apify.com/abotapi/miravia-es-scraper"><b>Miravia Spain Scraper</b></a><br>Scrape Miravia Spain by keyword, seller store or URL. Every row carries the selling price...</td></tr>
<tr><td>🥦 <a href="https://apify.com/abotapi/mercadona-es-scraper"><b>Mercadona Scraper</b></a><br>Scrape Mercadona products by delivery-area postal code. Search by keyword or category, or...</td><td>🛒 <a href="https://apify.com/abotapi/castorama-fr-scraper"><b>Castorama.fr</b></a><br>Scrape Castorama.fr DIY and home-improvement products by keyword, category, or URL...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/decathlon-es-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

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

'search' finds products by keyword or by a real decathlon.es category, with a brand filter, price range, specials toggle, and sort. 'url' scrapes any product, search, or category listing page URL you paste, walking pagination forward automatically.

## `searchTerm` (type: `string`):

Free-text keyword, e.g. 'zapatillas running' or 'mochila trekking'. Leave empty and use Category below to browse a category without a keyword (the two are alternative ways in, not combined). Search mode only.

## `category` (type: `string`):

A real decathlon.es category path, e.g. 'deportes/running/zapatillas-running' (not a full link; to paste a full category link use URL mode). Search mode only. Used only when Search keyword is empty.

## `brand` (type: `string`):

Only return products from this exact brand, e.g. 'ADIDAS', 'KIPRUN', 'QUECHUA'. Match the brand name as shown on the site. Leave empty for all brands. Applies to both search and URL mode.

## `specialsOnly` (type: `boolean`):

Keep only products currently marked down -- decathlon.es's own live discount signal (a real strike-through original price plus discount percentage on the product itself), not a guessed category or a separate landing-page feed. Applies to both search and URL mode.

## `minPrice` (type: `number`):

Only return products priced at or above this amount, in euros. Applies to both search and URL mode.

## `maxPrice` (type: `number`):

Only return products priced at or below this amount, in euros. Applies to both search and URL mode.

## `sortBy` (type: `string`):

decathlon.es's own sort order, verified live to reorder the full server-side result set (so it stays correct across pagination, not just within one fetched page). Applies to both search and URL mode; any value other than Relevance overrides the sort in a pasted URL.

## `urls` (type: `array`):

Paste one or more decathlon.es product pages, search result pages, or category listing pages. Pagination continues forward automatically from any ?from= already in the URL. Brand, On sale only and the price filters above still narrow the results in this mode, and Sort order and Brand override any sort= / Ns= / facets= already in a pasted URL. Leaving this field untouched in URL mode scrapes one example per URL shape. URLs from other sites or other-country Decathlon sites are skipped with a warning.

## `fetchDetails` (type: `boolean`):

Adds the full breadcrumb category path, complete description, technical characteristics (level, terrain, fit, material, etc.), composition/care/guarantee text, colour swatches, and per-size GTIN/EAN codes from the product page. Current price, was-price/discount, brand, and card image are already included without this toggle.

## `fetchReviews` (type: `boolean`):

Fetch the product's own reviews (not a third-party vendor) from decathlon.es's reviews API: overall rating, total review count, recommended count, and individual reviews (rating, title, body, author, date, verified-buyer flag). Products with no reviews simply return an empty list.

## `maxReviewsPerProduct` (type: `integer`):

Cap on reviews fetched per product when 'Fetch reviews' is on, walking the review pages as needed; maximum 1000.

## `maxPages` (type: `integer`):

Stop after this many result pages per search/category/URL entry. 0 (default) = unlimited -- the run stops at Max products total instead.

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

Hard cap on total products returned across every search/category/URL entry. 0 = unlimited (still bounded by Max pages per search); maximum 100000.

## `proxy` (type: `object`):

Decathlon challenges datacenter connections, so the run uses an Apify residential connection when your plan includes one (this uses residential traffic on your account). Without it, the run continues on a backup connection and some pages may be unavailable. You can also supply your own proxy URLs.

## `resumeFromRunId` (type: `string`):

Paste a previous run id (or its dataset id) to continue that run without returning or charging for products already collected there. Use this after an interrupted run. For recurring daily/scheduled monitoring of the same search, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring. The first run returns all matching products as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED products. A product is UPDATED when a compared field changes (price, original price, promotion, sizes, availability, name, brand, images order, specifications...); changedFields names them. Excluded from the comparison (still present in the output): scrapedAt, rating, reviewCount and the whole reviews block, so new reviews never trigger UPDATED. Image CDN shard hosts and cache-buster parameters are ignored too. Turn on "Emit unchanged" or "Emit expired" only when you also want those products returned (and billed). State is kept separately for each search/category/URL and detail/reviews setup; use State key when you want to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Leave empty to let the actor derive a key automatically from the search/category/URL and detail/reviews settings -- different searches then never mix state with each other.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return products that have not changed since the last run, marked UNCHANGED. This returns -- and bills -- extra rows you already have, so leave it off unless you specifically want the full snapshot every run.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return products that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search -- not when Max products capped it or when Resume was used. This returns -- and bills -- extra synthetic rows, so leave it off unless you need expiry tracking.

## `ignoreFieldsForChanges` (type: `array`):

Incremental mode only. Extra field names that must never make a product UPDATED or appear in changedFields (they stay in the output). Use a dotted path for a nested field, for example reviews.items. Already ignored by default: scrapedAt, rating, reviewCount, reviews.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify -> Settings -> Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON; the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "searchTerm": "zapatillas running",
  "specialsOnly": false,
  "sortBy": "RELEVANCE",
  "urls": [
    "https://www.decathlon.es/es/search?Ntt=zapatillas+running",
    "https://www.decathlon.es/es/search?Ntt=mochila&from=40&size=40&sort=asc",
    "https://www.decathlon.es/es/search?Ntt=zapatillas&facets=brandLabel%3AKIPRUN_&sort=asc&from=40",
    "https://www.decathlon.es/es/deportes/running/zapatillas-running",
    "https://www.decathlon.es/es/deportes/running/zapatillas-running?from=40&size=40&Ns=priceAscending",
    "https://www.decathlon.es/es/p/zapatilla-versatil-running-carretera-hombre-kiprun-kipcore-premium-azul/353370/c98c40c227m8958673"
  ],
  "fetchDetails": false,
  "fetchReviews": true,
  "maxReviewsPerProduct": 20,
  "maxPages": 0,
  "maxItems": 20,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `specifications` (type: `string`):

No description

## `reviews` (type: `string`):

No description

## `details` (type: `string`):

No description

## `changes` (type: `string`):

No description

## `summary` (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": "search",
    "searchTerm": "zapatillas running",
    "urls": [
        "https://www.decathlon.es/es/search?Ntt=zapatillas+running",
        "https://www.decathlon.es/es/search?Ntt=mochila&from=40&size=40&sort=asc",
        "https://www.decathlon.es/es/search?Ntt=zapatillas&facets=brandLabel%3AKIPRUN_&sort=asc&from=40",
        "https://www.decathlon.es/es/deportes/running/zapatillas-running",
        "https://www.decathlon.es/es/deportes/running/zapatillas-running?from=40&size=40&Ns=priceAscending",
        "https://www.decathlon.es/es/p/zapatilla-versatil-running-carretera-hombre-kiprun-kipcore-premium-azul/353370/c98c40c227m8958673"
    ],
    "proxy": {
        "useApifyProxy": true
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/decathlon-es-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": "search",
    "searchTerm": "zapatillas running",
    "urls": [
        "https://www.decathlon.es/es/search?Ntt=zapatillas+running",
        "https://www.decathlon.es/es/search?Ntt=mochila&from=40&size=40&sort=asc",
        "https://www.decathlon.es/es/search?Ntt=zapatillas&facets=brandLabel%3AKIPRUN_&sort=asc&from=40",
        "https://www.decathlon.es/es/deportes/running/zapatillas-running",
        "https://www.decathlon.es/es/deportes/running/zapatillas-running?from=40&size=40&Ns=priceAscending",
        "https://www.decathlon.es/es/p/zapatilla-versatil-running-carretera-hombre-kiprun-kipcore-premium-azul/353370/c98c40c227m8958673",
    ],
    "proxy": { "useApifyProxy": True },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/decathlon-es-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": "search",
  "searchTerm": "zapatillas running",
  "urls": [
    "https://www.decathlon.es/es/search?Ntt=zapatillas+running",
    "https://www.decathlon.es/es/search?Ntt=mochila&from=40&size=40&sort=asc",
    "https://www.decathlon.es/es/search?Ntt=zapatillas&facets=brandLabel%3AKIPRUN_&sort=asc&from=40",
    "https://www.decathlon.es/es/deportes/running/zapatillas-running",
    "https://www.decathlon.es/es/deportes/running/zapatillas-running?from=40&size=40&Ns=priceAscending",
    "https://www.decathlon.es/es/p/zapatilla-versatil-running-carretera-hombre-kiprun-kipcore-premium-azul/353370/c98c40c227m8958673"
  ],
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/decathlon-es-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/decathlon-es-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/ujDoL7eajor4J2U8x/builds/LAQUIEjh11JT019LS/openapi.json
