# El Corte Inglés Price Tracker (elcorteingles.es) (`superslowsloth/elcorteingles-products`) Actor

Products from El Corte Inglés category pages, one flat row each: name, brand, price in EUR, original price, discount, category, photo, product URL - and whether each product is new, cheaper or dearer than on the last run.

- **URL**: https://apify.com/superslowsloth/elcorteingles-products.md
- **Developed by:** [Superslow Sloth](https://apify.com/superslowsloth) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 product scrapeds

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

## El Corte Inglés Price Tracker (elcorteingles.es)

Read product prices from [El Corte Inglés](https://www.elcorteingles.es)
category pages - Spain's largest department store - and tell which products
are **new in a category since your last run** and which ones **changed price**.
Give it one or more category pages; it returns one flat row per product: price
in EUR, original price and discount, brand, category, photo, product link,
seller and availability. Schedule it daily with *Watch mode* on to get a price
feed that costs nothing for products whose price did not move.

### Input

| Field | Notes |
|---|---|
| `categoryUrls` | Category pages, as full URLs or paths: `https://www.elcorteingles.es/electrodomesticos/lavadoras/` or `/electrodomesticos/lavadoras/`. Only `elcorteingles.es` is accepted; any other host is refused. A page number at the end (`/lavadoras/3/`) starts the walk on that page. |
| `maxItems` | Stop reading each category after this many products (default 100; the site shows 12 per page). |
| `changesOnly` | Watch mode: return and charge only products that are new in the category or whose price changed since the last run over it. |
| `proxyConfiguration` | Apify datacenter proxy by default. |

A query string is passed through unchanged. `?sorting=priceAsc` (also
`priceDesc`, `discountPerDesc`, `bestSellerQtyDesc`, `newInAsc`) was measured
to change the order and to keep working on later pages; other parameters
(`?s=bosch`) were silently ignored by the site. The default order is the
site's own "Recomendados".

#### A path the site does not know

El Corte Inglés does not answer an unknown category with 404. It answers `200`
with the *parent* category (`/lavadoras/nonexistent-xyz/` returned the whole
`/lavadoras/` listing, 2026-10-07) and gives itself away only in the page's
canonical link. The actor compares that link with the path you asked for and
refuses the category with a message naming what the site served instead,
rather than hand you the parent's products as if they were yours. A genuinely
missing section (`/moda/` answered HTTP 404) is reported as a failure of that
category; the other categories in the run carry on.

### Output

| Field | Notes |
|---|---|
| `change` | `new`, `price_drop`, `price_rise` or `unchanged`, compared with the previous run over the same category. |
| `product_id`, `code`, `gtin` | `product_id` is the reference of the variant on the card (a string, leading zeros kept); `code` is the product-level code, the same across a product's colours; `gtin` is the barcode. |
| `name`, `brand`, `url` | `url` is the product page on elcorteingles.es. The store's own label is the brand `El Corte Inglés`. |
| `price`, `currency` | The price to pay now (`f_price` on the site), in EUR. |
| `original_price` | The price before the reduction (`o_price`). **Null** for a product that is not reduced. |
| `discount_amount`, `discount_percent` | The site's own reduction in EUR and rounded percentage. `0` when the site says the product is not reduced. |
| `category`, `category_path` | Deepest category name, and the full path joined with `>`. |
| `image_url` | The product photo. |
| `seller_name`, `marketplace` | `El Corte Ingles, S.A.` for the store's own stock, the seller's name for a marketplace listing (`marketplace: true`). `marketplace` is null for products sold in several variants, where the page carries no single seller block. |
| `status`, `available` | The site's own status word (`ADD`, `add`, `NO_SALE`), and `available`: `true` for ADD in any case, `false` for NO_SALE, null for any other word. |
| `page`, `position` | The page of the category the card was on, and its position there. |
| `category_url` | The category this row was read from, so a multi-category dataset can be split. |

#### One price per card

A product sold in several colours or sizes (an iPhone in three storage sizes)
is one card on the page, and the card shows one variant's price. That is the
price returned; `product_id` is that variant's reference. Variants are not
expanded into rows.

#### Honest nulls, and what is not here

A field the site did not send is `null`, never `0` or `""`: `original_price`
on an unreduced product is the clearest case. **Star rating and review count
are not returned** - the site loads them from a review vendor in the browser,
so the category page carries no value for them.

Products with no numeric price are dropped (counted in the run log), because
there is no price to track or to bill for.

### Change tracking

Each category (its path and query string) has its own memory, kept in a named
key-value store on **your** Apify account (`elcorteingles-products-watch`), so
it is never visible to anyone else, and adding or removing a category in the
input does not reset the others. The first run over a category has nothing to
compare against: it labels every product `new`. From the second run on, a
product is `new` if the last run did not read it, `price_drop` / `price_rise`
if its price moved, and `unchanged` otherwise. The key is the product-level
`code`, so a product whose representative colour changes between runs is not
reported as new.

With `changesOnly`, `unchanged` products are neither returned nor charged. The
walk does **not** stop at the first page with no changes: a category is in the
site's own order, not newest-first, so one quiet page says nothing about the
next. `maxItems` bounds how many products are read per category, which is also
how far the watch reaches - a product beyond that point is not seen, so not
tracked.

A product that appears in two categories of the same run is returned and
charged once, under the first category listed.

### Pricing

Pay per event:

- **$0.002** per run (`actor-start`), charged only after your input is
  validated - a run that fails on bad input costs nothing.
- **$0.0014** per product row written (`product-scraped`), i.e. $1.40 per
  1,000 products. With `changesOnly`, unchanged products are not charged.

### How it fetches

The category page is server-rendered and embeds its product data as JSON in
the page (the `dataLayer` analytics block and the page's render state); the
actor decodes that JSON, it does not scrape the visible HTML. Twelve products
per request, about 1 MB per page. No account and no login.

The site sits behind Akamai, which answers a Python HTTP client with HTTP 403
"Access Denied" whatever headers it sends, and answers a client that presents
Chrome's TLS handshake. The actor presents Chrome's handshake, and when an
address is refused anyway it retries from a fresh one through the proxy. Local
(non-proxy) requests with this fingerprint got HTTP 200 on 2026-10-07; the
Apify datacenter proxy has not yet been confirmed for this site.

# Actor input Schema

## `categoryUrls` (type: `array`):

El Corte Inglés category pages to read, as a full URL or a path: https://www.elcorteingles.es/electrodomesticos/lavadoras/ or /electrodomesticos/lavadoras/. Only elcorteingles.es is accepted. A page number at the end (/lavadoras/3/) starts the walk there. A query string is passed through unchanged; ?sorting=priceAsc (also priceDesc, discountPerDesc, bestSellerQtyDesc, newInAsc) was measured to change the order, other parameters were ignored by the site. Each category keeps its own change-tracking memory. A path the site does not know is refused, because El Corte Inglés answers it with the parent category.

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

Stop reading a category after this many products (12 per page). The count is of products read: with the watch option on, unchanged products are read but neither returned nor charged.

## `changesOnly` (type: `boolean`):

Return (and charge for) only products that are new in a category, or whose price changed, since the last run over that same category; unchanged products are skipped and cost nothing. The memory is kept per category (its path and query) in a key-value store on your own account. The FIRST run has nothing to compare against, so it labels every product new and returns them all. The change column is filled either way; this switch only drops the unchanged rows.

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

Apify datacenter proxy is the default. El Corte Inglés sits behind Akamai, which refuses a Python-shaped TLS handshake outright (HTTP 403 Access Denied) and answers a Chrome-shaped one; the actor presents Chrome's handshake and, when an address is refused anyway, retries from a fresh one - which needs a proxy.

## Actor input object example

```json
{
  "categoryUrls": [
    "https://www.elcorteingles.es/electrodomesticos/lavadoras/"
  ],
  "maxItems": 100,
  "changesOnly": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (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 = {
    "categoryUrls": [
        "https://www.elcorteingles.es/electrodomesticos/lavadoras/"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("superslowsloth/elcorteingles-products").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 = {
    "categoryUrls": ["https://www.elcorteingles.es/electrodomesticos/lavadoras/"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("superslowsloth/elcorteingles-products").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 '{
  "categoryUrls": [
    "https://www.elcorteingles.es/electrodomesticos/lavadoras/"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call superslowsloth/elcorteingles-products --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,superslowsloth/elcorteingles-products"
        }
    }
}
```

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/t3BLhk90xEed8Yxmx/builds/EBoYKdWtuVLmBCzlj/openapi.json
