# Mercadona Scraper — Spain Grocery Prices, EAN, Ingredients (`architekture/mercadona-es-scraper`) Actor

Scrape Mercadona Spain: EUR prices, price per kg/L, previous price, category, plus EAN, ingredients, allergens, origin. Whole catalogue in one run. Misses never charged. Pay per result.

- **URL**: https://apify.com/architekture/mercadona-es-scraper.md
- **Developed by:** [architek.earth](https://apify.com/architekture) (community)
- **Categories:** AI, E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 products

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

## 🛒 Mercadona Scraper — Spain Grocery Prices, EAN, Ingredients

**Recorded 18 Oct 2026 from Apify datacenter IPs, no proxy, no login: Mercadona's whole online catalogue (4,269 products across 152 sub-categories) walked in 158 requests. A "leche" search returned 232 products. With details on, all 40 products checked came back with billable detail fields; 40 detail lookups took 1.6 s, 4 at a time.**

From **Architekture**, makers of the ALDI Ireland, Dunnes Stores, Tesco UK and Woolworths scrapers.

🤖 **Built for AI workflows.** Every row is deterministic, schema-stable JSON — feed it straight to LLM apps, RAG pipelines, or agent tool calls via the Apify API, the Apify MCP server (`mcp.apify.com`), or AI coding agents like Claude Code and Cursor.
| 🏪 Retailer | 📍 Market | 📦 Catalogue | 💰 From | 🔑 Standout fields |
|---|---|---|---|---|
| Mercadona | Spain 🇪🇸 | ~4,300 products, 152 sub-categories | $1.00 / 1,000 products | EAN barcodes, ingredients, allergens |

Get Mercadona products from **tienda.mercadona.es**, Spain's largest supermarket, by keyword (Mercadona's own shop search), by category, by the whole catalogue, or by product URL. Each product is one clean JSON row:

- name, brand, packaging and size
- price in EUR, price per kg/L and previous price
- VAT rate and the full category path
- image and URL

Turn on details to add:

- EAN barcode, ingredients and allergens (as text and as a list)
- origin, suppliers, legal name, and storage and usage instructions

### 🚀 Why choose this actor

- 🗂️ **The whole catalogue in one run.** `categories: ["all"]` walks all 152 sub-categories, about 4,300 products, de-duplicated.
- 🔎 **Search that matches the website.** It uses the same product search the Mercadona shop uses, with accent-tolerant matching and up to 1,000 results per term.
- ⚖️ **Price per kg/L on every row.** `referencePrice` + `referenceUnit` (e.g. 0.83 per L), plus `previousPrice` where Mercadona publishes one.
- 🥜 **Real allergens, not placeholders.** Mercadona marks "no allergens declared" with a code (`x99`). We drop it rather than report it as an allergen, and list the real ones in `allergenList`.
- 💸 **Pay for detail only when it's there.** With `includeDetails`, a row is charged as `product-detail` only when Mercadona returned an EAN, ingredients, allergens, origin or similar.
- 🏭 **Warehouse-aware.** Mercadona's assortment varies by area. Pick the warehouse (`vlc1` Valencia, `mad1` Madrid, `bcn1` Barcelona, `alc1` Alicante, `svq1` Sevilla). An unknown warehouse is rejected up front instead of silently returning Valencia's range.
- 🆓 **Never pay for misses.** An unknown category, a missing product or a blocked request gets one free status row.

### ⚠️ Worth knowing up front

| Question | Answer |
|---|---|
| Is there an "on sale" flag? | ❌ No — Mercadona runs no promotions. You get its own `previousPrice` and `priceDecreased` as published (in our sample of 25, always `false`). |
| Where's the brand on category rows? | Brand comes with search results and details. Category listings don't carry it — turn on `includeDetails` for brand on category runs. |
| Loose fruit & veg? | Priced per piece at an approximate weight (`approxSize: true`), per-kg price in `referencePrice`. |
| Nutrition / stock counts? | ❌ Mercadona's API doesn't publish them. We don't invent them. |
| Do prices differ by region? | In our five-warehouse check: no. Only the assortment differs. |

### ⚡ Try the example

```json
{
  "searchTerms": ["leche"],
  "maxItemsPerQuery": 50
}
```

Add `"includeDetails": true` for EAN, ingredients and allergens, or `"categories": ["all"], "maxItemsPerQuery": 0` for the full catalogue.

### ⚙️ Input parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `searchTerms` | array | — | Keywords, e.g. `leche`, `aceite de oliva`. Up to 1,000 results per term. |
| `categories` | array | — | One of: `all`; a category name (`Huevos, leche y mantequilla`); a sub-category name (`Aceite, vinagre y sal`); `Top > Sub` for names that repeat (`Congelados > Carne`); or a sub-category id / `tienda.mercadona.es/categories/112` URL. Accents optional. |
| `productUrls` | array | — | Product URLs (`tienda.mercadona.es/product/4241/…`) or ids (`4241`). Always includes detail where published. Up to 500. |
| `maxItemsPerQuery` | integer | `100` | Cap per term or category. `0` = everything. |
| `includeDetails` | boolean | `false` | One extra request per product: EAN, ingredients, allergens, origin, suppliers, storage, photos. |
| `warehouse` | string | `vlc1` | Assortment to read: `vlc1`, `mad1`, `bcn1`, `alc1`, `svq1`. |
| `language` | select | `es` | `es`, `en` or `ca`. These are Mercadona's own translations (e.g. "Oil, vinegar & salt", "Oli, vinagre i sal"). |
| `proxyConfiguration` | proxy | Apify datacenter | Blocked or rate-limited requests are retried on a fresh IP. |
| `feedback` | string | — | A note to the maintainers. Read by a human. |

Results are de-duplicated by product across every term, category and URL in a run, so a product found twice is delivered and charged once. Product URLs are fetched first.

### 🧱 Stable fields for automations

Product rows have `recordType: "product"`; status rows have `recordType: "query"`. Fields are **omitted, never `null`**, when Mercadona does not publish them. New fields are only ever added.

| Field | Meaning |
|---|---|
| `recordType`, `retailer`, `productId` | `product`; `"Mercadona"`; Mercadona product id. |
| `name`, `brand`, `packaging`, `size` | e.g. `"Leche semidesnatada Hacendado"`, `"Hacendado"`, `"Garrafa"`, `"6 briks · 6 l"`. |
| `price`, `currency` | Price of one sellable unit (pack, bottle, piece), `"EUR"`. |
| `referencePrice`, `referenceUnit` | Price per kg / L / unit, e.g. `0.83` per `"L"`. |
| `previousPrice`, `priceDecreased` | Mercadona's previous price and its own flag, as published. |
| `taxPercentage` | VAT rate (e.g. `4`, `10`, `21`). |
| `approxSize`, `isNew`, `requiresAgeCheck` | Sold by approximate weight; new arrival; age-restricted (alcohol). |
| `unavailableFrom`, `unavailableWeekdays` | Mercadona's availability notes, where set. |
| `category`, `categoryPath` | `"Aceite de oliva"`, `"Aceite, especias y salsas > Aceite, vinagre y sal > Aceite de oliva"`. |
| `image`, `url`, `warehouse` | 600 px image, product page, warehouse read. |
| `source` | `{source: "search" \| "category" \| "product", searchTerm?, category?, subCategoryId?, categoryUrl?, page?}`. |
| Detail: `ean`, `legalName`, `ingredients`, `allergens`, `allergenList`, `origin`, `suppliers`, `storageInstructions`, `usageInstructions`, `mandatoryMentions`, `dangerMentions`, `productionNote`, `alcoholByVolume`, `extraInfo`, `isBulk`, `images` | With `includeDetails` or `productUrls`, where published. |
| Status rows: `query`, `status`, `reason` | `NOT_FOUND`, `BLOCKED` or `ERROR`, and why. |

### 📦 Output example

Recorded 18 Oct 2026; detail fields shown with `includeDetails` on, suppliers abridged.

```json
{
  "recordType": "product",
  "retailer": "Mercadona",
  "productId": "10381",
  "name": "Leche semidesnatada Hacendado",
  "brand": "Hacendado",
  "size": "6 briks · 6 l",
  "price": 4.98,
  "currency": "EUR",
  "referencePrice": 0.83,
  "referenceUnit": "L",
  "previousPrice": 5.04,
  "priceDecreased": false,
  "taxPercentage": 4.0,
  "category": "Leche semidesnatada",
  "categoryPath": "Huevos, leche y mantequilla > Leche y bebidas vegetales > Leche semidesnatada",
  "url": "https://tienda.mercadona.es/product/10381/leche-semidesnatada-hacendado-pack-6",
  "warehouse": "vlc1",
  "ean": "8402001002090",
  "legalName": "Leche semidesnatada UHT de vaca",
  "ingredients": "Leche semidesnatada de vaca. Origen de la leche: España",
  "allergens": "Contiene leche y sus derivados (incluida la lactosa).",
  "allergenList": ["leche y sus derivados (incluida la lactosa)"],
  "origin": "España",
  "suppliers": ["Capsa", "Iparlat, S.A."],
  "storageInstructions": "Mantener en lugar fresco y seco. Una vez abierto el envase, conservar en el frigorífico y consumir antes de 3 días.",
  "source": { "source": "search", "searchTerm": "leche", "page": 1 }
}
```

A category that does not exist:

```json
{ "recordType": "query", "query": "category:nope", "status": "NOT_FOUND", "reason": "no Mercadona category matches 'nope' (use a name, 'Top > Sub', an id or 'all')" }
```

### 🧠 Advanced usage

- 📈 **Daily price tracking:** schedule `categories: ["all"]` and join on `productId`; watch `price` and `referencePrice`.
- 🔗 **Barcode matching:** with `includeDetails`, join `ean` against other retailers (e.g. our ALDI or Tesco scrapers) or your own range.
- 🥗 **Allergen and ingredient audits:** filter `allergenList`, or search `ingredients`.
- 🗺️ **Regional assortment:** run the same categories with different `warehouse` values and compare which `productId`s appear.
- ✅ **Completion check:** read `RUN_SUMMARY`, which has `status` (`COMPLETE`, `PARTIAL`, `NO_PRODUCTS`, `BUDGET_LIMIT`, `TIME_LIMIT`, `STATE_SAVE_FAILED`, `INVALID_INPUT`, `NOT_CONFIGURED`), `products`, `charged`, `warehouse` and `queries[]` (each with `status`, `delivered`, `listed`, `warning`).

### 🔌 Integrations

Export as JSON, CSV or Excel; push to Google Sheets; trigger from Make, Zapier or n8n; get a webhook when a run finishes; schedule with the Apify Scheduler.

### 💸 How much does it cost?

1,000 products ≈ **$1.00**. The full catalogue (~4,300 products) ≈ **$4.30**. With details, rows that come back with detail fields are $4.00 / 1,000. Platform usage is included.

### 💰 Pricing

| Event | Price | Covers |
|---|---|---|
| `product` | $1.00 / 1,000 | One product row |
| `product-detail` | $4.00 / 1,000 | One product row that came back with detail fields (EAN, ingredients, allergens, origin, suppliers…) |

Platform usage is included. Status rows are free, and nothing is charged for failed requests. A run stops cleanly at your spending limit, with charges equal to rows delivered. The whole catalogue without details is about 4,300 products, about $4.30.

### ❓ FAQ

**Why does a category name come back NOT_FOUND?** Some sub-category names repeat in two departments (e.g. `Marisco` under both Congelados and Marisco y pescado). Use `Congelados > Marisco`, or the id from the category URL. A department name always wins: `Carne` means the whole Carne department.

**Do prices differ by region?** In our check, no; the assortment does. Use `warehouse` for the area you care about.

### 🤖 For AI agents & LLM apps

`architekture/mercadona-es-scraper` returns Mercadona (Spain) products as deterministic, schema-stable JSON, one object per product. Each object has name, brand, size, EUR price, price per kg/L, previous price, VAT and category path; EAN, ingredients, allergens, origin and suppliers are optional. Invoke it from any agentic workflow via the Apify API or the Apify MCP server (`mcp.apify.com`) as a tool call. Its cost is bounded before it starts: $1 per 1,000 products, or $4 per 1,000 with details. `maxItemsPerQuery` caps each query, and a spending limit stops the run cleanly. Every query gets a machine-readable status in `RUN_SUMMARY`, and misses come back as free `recordType: "query"` rows. Honest limits: no promotions, no nutrition table, no stock counts, up to 1,000 search results per term.

Works out of the box with LangChain, LlamaIndex and AI coding agents — call it as a tool from Claude Code, Cursor, or any MCP client via `mcp.apify.com`.

### 🧰 More Architekture scrapers

- 🇪🇺 **Europe:** Mercadona Spain (you are here) · [ALDI Ireland](https://apify.com/architekture/aldi-ie-scraper) · [Dunnes Stores Ireland](https://apify.com/architekture/dunnes-ie-scraper) · [Tesco UK](https://apify.com/architekture/tesco-uk-scraper) · [Sainsbury's UK](https://apify.com/architekture/sainsburys-uk-scraper)
- 🛍️ **E-commerce:** [Shopify Products](https://apify.com/architekture/shopify-products-scraper) · [AliExpress Search](https://apify.com/architekture/aliexpress-search-scraper)

### 🙋 Support

Open an Issue on this actor's page; a human replies within a day.

### ⚖️ Legal compliance

This actor reads publicly available product data from tienda.mercadona.es and the public product search the shop itself uses, without logging in. You are responsible for how you use the data, including Mercadona's terms, intellectual property in product content and images, and data-protection law where it applies. Not affiliated with Mercadona.

### 📝 Changelog

- **2026-10-18**: 1.0. Search, category, whole-catalogue and product modes; warehouse and language; detail charged only when published; allergen placeholder handling; free status rows; clean stops at spending limit and timeout; resumable after platform migration.

# Actor input Schema

## `searchTerms` (type: `array`):

Keywords searched with Mercadona's own shop search, e.g. leche or aceite de oliva. Up to 1,000 results per term. Results are de-duplicated across all terms, categories and product URLs.

## `categories` (type: `array`):

A category name (Huevos, leche y mantequilla), a sub-category name (Aceite, vinagre y sal), 'Top > Sub' (Congelados > Carne), an id or tienda.mercadona.es/categories/112 URL, or all for the whole catalogue (about 4,300 products). Accents optional. A category that does not exist gets one free status row.

## `productUrls` (type: `array`):

tienda.mercadona.es product URLs (…/product/4241/aceite-oliva-…) or product ids (4241). Always include detail fields where Mercadona publishes them. Up to 500.

## `maxItemsPerQuery` (type: `integer`):

0 = everything (search terms: up to 1,000).

## `includeDetails` (type: `boolean`):

One extra request per product. Rows that come back with detail fields are charged as product-detail; rows Mercadona publishes no detail for stay product.

## `warehouse` (type: `string`):

Mercadona serves each area from a warehouse, and the assortment differs slightly between them. vlc1 Valencia (default), mad1 Madrid, bcn1 Barcelona, alc1 Alicante, svq1 Sevilla.

## `language` (type: `string`):

Language of product names and categories, as Mercadona translates them.

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

Apify datacenter proxy is the tested default. Blocked or rate-limited requests are retried on a fresh IP.

## `feedback` (type: `string`):

Tell the maintainers what you needed and did not get. Read by a human; does not change the output.

## Actor input object example

```json
{
  "searchTerms": [
    "leche"
  ],
  "maxItemsPerQuery": 50,
  "includeDetails": false,
  "warehouse": "vlc1",
  "language": "es",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

One row per product; misses add one free recordType: query row.

## `summary` (type: `string`):

RUN_SUMMARY: status per query and charges.

# 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 = {
    "searchTerms": [
        "leche"
    ],
    "maxItemsPerQuery": 50,
    "warehouse": "vlc1",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("architekture/mercadona-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 = {
    "searchTerms": ["leche"],
    "maxItemsPerQuery": 50,
    "warehouse": "vlc1",
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("architekture/mercadona-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 '{
  "searchTerms": [
    "leche"
  ],
  "maxItemsPerQuery": 50,
  "warehouse": "vlc1",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call architekture/mercadona-es-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,architekture/mercadona-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/AsTnTdtM0fHD7h2Dl/builds/WhY8gg3vxfHGEc2vO/openapi.json
