# Mercadona Scraper - Prices, Unit Price & Postal Code (`abotapi/mercadona-es-scraper`) Actor

Scrape Mercadona products by delivery-area postal code. Search by keyword or category, or paste product and category URLs. Get local availability, current price, site-calculated price per kg, litre or unit, plus original price and discount when an item is on sale.

- **URL**: https://apify.com/abotapi/mercadona-es-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** E-commerce, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — 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

## Mercadona Product Scraper

Pull product data from Mercadona (tienda.mercadona.es), Spain's largest supermarket chain. Mercadona's catalogue, availability, and prices are zoned by delivery area, so every run starts from a postal code. Search by keyword and/or a real Mercadona category, or paste product/category links directly. Every record includes the current price, the real per-kg/L/unit price the site itself computes, and a genuine was-price with discount whenever one actually exists.

### Why This Scraper?

- **Zoned by real delivery area.** Give it a Spanish postal code and it resolves the same delivery warehouse the live site would use for that address, so catalogue, availability, and prices match what a shopper there actually sees.
- **Real per-unit price, not computed after the fact.** Mercadona's own API returns a per-kg/L/unit reference price alongside the package price -- captured as-is, not derived from a guessed pack size.
- **Was-price captured only when it's real.** Mercadona is a low-price retailer with very few promotions, so most products simply have no was-price at all -- this scraper checks the site's own previous-price field per item and only ever sets a was-price/discount when that field is genuinely populated and higher than the current price. It never fabricates a markdown from a unit-price spread.
- **Own-brand aware.** Hacendado, Deliplus, Bosque Verde and every other brand come through as the site's own structured brand field (detail mode), not guessed from the product name.
- **Two ways in.** Keyword + category browse with brand/price/discount filters and sort, or paste any product/category link.
- **Optional export to your apps.** Send results into Notion, Linear, Airtable, or any Apify MCP connector alongside the dataset.

### A note on search and reviews

Mercadona's own keyword-search service sits behind a separate, harder-gated endpoint that this scraper could not clear without a browser-minted challenge. "Search" here means: resolve your postal code, then walk the site's own real category tree (by name or id), narrowing client-side by keyword, brand, price, and was-price -- a real substitute, not a guess, but not a full free-text index. Combine a keyword with a specific `category` to keep runs fast and precise. Mercadona also has no product review system, so `reviews` is always an empty array -- this is a real absence on the site itself, not a scraping gap.

### Data You Get

| Field | Example value |
|---|---|
| productId / slug | `123456`, `sample-product-hacendado` |
| name / brand | `Sample Product Hacendado`, `Hacendado` (brand needs `fetchDetails`) |
| category / categoryPath | `Sample Aisle`, `["Sample Group", "Sample Aisle", "Sample Subcategory"]` |
| url / image | `https://tienda.mercadona.es/product/123456/sample-product-hacendado`, image URL |
| packaging | `Botella` |
| price / currency | `1.90`, `EUR` |
| unitPrice / unitPriceFormat | `11.52`, `kg` |
| packageSize / packageSizeFormat | `0.165`, `kg` |
| previousPrice / discountAmount / discountPercent | `2.65`, `0.75`, `28.3` |
| isOnSpecial | `true` |
| onlineAvailable | `true` |
| ean / origin / suppliers | `8400000000000`, `España`, `["Sample Supplier S.L."]` |
| description / ingredients / allergens | legal name text, ingredients text, allergens text |
| images | full-resolution photo gallery |
| reviews | `[]` (Mercadona has no review system) |

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

### How to Use

**1. Keyword + category browse for a delivery zone (default):**

```json
{
  "mode": "search",
  "postalCode": "28001",
  "searchTerm": "aceite",
  "category": "Aceite, vinagre y sal",
  "maxItems": 20
}
```

**2. Only Hacendado products currently on special, sorted by price:**

```json
{
  "mode": "search",
  "postalCode": "28001",
  "category": "Aceite, vinagre y sal",
  "brands": ["Hacendado"],
  "specialsOnly": true,
  "sortBy": "PRICE_ASC",
  "maxItems": 50
}
```

**3. Full product detail (brand, EAN, ingredients, image gallery) for a pasted product link:**

```json
{
  "mode": "url",
  "postalCode": "28001",
  "urls": ["https://tienda.mercadona.es/product/123456/sample-product-hacendado"],
  "fetchDetails": true
}
```

**4. Paste a category link and walk the whole aisle:**

```json
{
  "mode": "url",
  "postalCode": "28001",
  "urls": ["https://tienda.mercadona.es/categories/112"],
  "maxItems": 100
}
```

### Input Parameters

| Parameter | Type | Description |
|---|---|---|
| `mode` | string | `search` or `url`. |
| `postalCode` | string | A Spanish postal code -- determines the delivery zone (catalogue, availability, prices). Applies to both modes. |
| `searchTerm` | string | Keyword (search mode only); narrows client-side within the category/categories walked. |
| `category` | string | A real Mercadona category or group name/id (search mode only); empty browses the whole catalogue. |
| `specialsOnly` | boolean | Keep only products carrying a genuine was-price higher than the current price (search mode only). |
| `brands` | array | Exact brand names to keep; automatically enables `fetchDetails` since brand is a detail-only field (search mode only). |
| `minPrice` / `maxPrice` | number | EUR price band to keep (search mode only). |
| `sortBy` | string | `RELEVANCE`, `PRICE_ASC`, or `PRICE_DESC` (search mode only). |
| `urls` | array | Product or category URLs to scrape (url mode only). |
| `fetchDetails` | boolean | Fetch brand, EAN, origin, suppliers, ingredients/allergens, storage/usage instructions, and the full image gallery. Always on for a pasted product URL. |
| `maxPages` | integer | Cap on category aisles walked per search; empty = unlimited. |
| `maxItems` | integer | Cap on total products returned; `0` = unlimited. |
| `proxy` | object | Apify proxy configuration. Works on every plan by default. |
| `mcpConnectors` | array | Optional MCP connectors to export results into (Notion, Linear, Airtable, Apify). |
| `notionParentPageUrl` | string | Notion connector only: page under which item pages are created. |
| `maxNotifyListings` | integer | Cap on items exported to each connector per run. Does not affect the dataset. |

### Output Example

```json
{
  "productId": "123456",
  "slug": "sample-product-hacendado",
  "name": "Sample Product Hacendado",
  "brand": "Hacendado",
  "category": "Sample Subcategory",
  "categoryPath": ["Sample Group", "Sample Aisle", "Sample Subcategory"],
  "url": "https://tienda.mercadona.es/product/123456/sample-product-hacendado",
  "image": "https://prod-mercadona.imgix.net/images/sample.jpg",
  "packaging": "Botella",
  "price": 1.9,
  "currency": "EUR",
  "unitPrice": 11.52,
  "unitPriceFormat": "kg",
  "packageSize": 0.165,
  "packageSizeFormat": "kg",
  "previousPrice": 2.65,
  "discountAmount": 0.75,
  "discountPercent": 28.3,
  "isOnSpecial": true,
  "onlineAvailable": true,
  "ean": "8400000000000",
  "origin": "España",
  "suppliers": ["Sample Supplier S.L."],
  "description": "Sample legal product description as shown on the site.",
  "ingredients": "Sample ingredients text.",
  "allergens": "Sample allergens text.",
  "images": ["https://prod-mercadona.imgix.net/images/sample-zoom.jpg"],
  "reviews": []
}
```

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

### A note on unit pricing and was-price

The package price (`price`) and the real per-kg/L/unit reference price (`unitPrice`/`unitPriceFormat`) both come straight from Mercadona's own product data, not computed from a guessed pack size. `previousPrice`/`discountAmount`/`discountPercent`/`isOnSpecial` are only ever set when the product's own previous-price field is genuinely populated and higher than the current price -- most Mercadona products (an every-day-low-price retailer) simply carry no was-price, and this scraper reports that honestly rather than inventing one.

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

Optionally pipe results into the apps you already use through Model Context Protocol (MCP) connectors. Authorize a connector once under Apify, Settings, Integrations, then select it in the `mcpConnectors` field. Each connector receives a condensed, human-readable summary per product (name plus key fields), while the complete record always stays in the Apify dataset. For Notion, set `notionParentPageUrl` to the page the item pages should be created under, and use `maxNotifyListings` to cap how many items are exported per connector per run. Supported connectors: Notion, Linear, Airtable, and Apify. Leave the field empty to skip; it never changes the dataset output.

### A note on plans

The default connection works on every Apify plan, including the free tier. A residential proxy connection is optional and only worth turning on for very large or sustained runs.

# Actor input Schema

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

'search' browses the catalogue for your delivery zone by keyword and/or a real category (aisle) name. 'url' scrapes any product or category link you paste.

## `postalCode` (type: `string`):

Spanish postal code Mercadona uses to pick your delivery zone. It scopes prices, availability and which products the zone's warehouse stocks, in every mode - so it changes the result set, not just the prices. Five digits, e.g. 28001 for Madrid. A code with no delivery zone stops the run rather than returning nothing.

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

Free-text keyword, e.g. 'aceite de oliva' or 'leche'. Mercadona's own text-search service is not reachable without a browser challenge, so this narrows client-side against the product names in the category/categories being walked below -- combine with Category to keep it fast and precise, or leave Category empty to sweep the whole catalogue (slower).

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

A real Mercadona catalogue category or group, exactly as the site shows it (e.g. 'Aceite, vinagre y sal', or a whole group like 'Aceite, especias y salsas'). Leave empty to browse every category, aisle by aisle, bounded by Max pages/Max products.

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

Mercadona is a low-price retailer with few promotions, but a real was-price does appear on some items (verified live, e.g. a markdown from 2.65 EUR to 1.90 EUR) -- this narrows to only products currently carrying a genuine previous price higher than the current one. Off by default since most Mercadona products have no was-price at all.

## `brands` (type: `array`):

Only return products from these brands, e.g. 'Hacendado', 'Deliplus', 'Bosque Verde'. Brand is only present on the product-detail page, so setting this automatically enables product-detail fetching (billed via the detail-enrichment event) for every candidate product so it can be checked. Leave empty for all brands.

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

Only return products priced at or above this amount, in euros.

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

Only return products priced at or below this amount, in euros.

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

Sorts the products collected within the category/categories being walked. For a fully sorted result, combine with a specific Category (a single aisle).

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

Paste one or more Mercadona product pages (tienda.mercadona.es/product/<id>/...) or category pages (tienda.mercadona.es/categories/<id>). Filter fields above are ignored in this mode.

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

Adds brand, EAN barcode, country of origin, suppliers, full legal description, ingredients/allergens, storage/usage instructions, and the full image gallery from the product page. Current price, unit price, was-price, and card image are already included without this toggle. Always on for a pasted product URL and automatically enabled whenever a Brand filter is set.

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

Stop after walking this many category aisles (one 'page' = one real Mercadona aisle). Leave empty for no limit -- the run stops at Max products total. Ignored for a single pasted product URL.

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

Hard cap on total products returned across the run. 0 = unlimited (still bounded by Max categories per search).

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

The default connection works on every Apify plan, including the free tier. A residential connection is optional and only needed for very large or sustained runs.

## `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",
  "postalCode": "28001",
  "searchTerm": "aceite de oliva",
  "category": "Aceite, vinagre y sal",
  "specialsOnly": false,
  "brands": [],
  "sortBy": "RELEVANCE",
  "urls": [
    "https://tienda.mercadona.es/product/4241/aceite-oliva-04o-hacendado-garrafa"
  ],
  "fetchDetails": false,
  "maxItems": 20,
  "proxy": {
    "useApifyProxy": true
  },
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (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",
    "postalCode": "28001",
    "searchTerm": "aceite de oliva",
    "category": "Aceite, vinagre y sal",
    "brands": [],
    "urls": [
        "https://tienda.mercadona.es/product/4241/aceite-oliva-04o-hacendado-garrafa"
    ],
    "proxy": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/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 = {
    "mode": "search",
    "postalCode": "28001",
    "searchTerm": "aceite de oliva",
    "category": "Aceite, vinagre y sal",
    "brands": [],
    "urls": ["https://tienda.mercadona.es/product/4241/aceite-oliva-04o-hacendado-garrafa"],
    "proxy": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/mercadona-es-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 '{
  "mode": "search",
  "postalCode": "28001",
  "searchTerm": "aceite de oliva",
  "category": "Aceite, vinagre y sal",
  "brands": [],
  "urls": [
    "https://tienda.mercadona.es/product/4241/aceite-oliva-04o-hacendado-garrafa"
  ],
  "proxy": {
    "useApifyProxy": true
  }
}' |
apify call abotapi/mercadona-es-scraper --silent --output-dataset

```

## MCP server setup

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

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/7egzkdcKolkKi1yCr/builds/Z8DqV2JbDqG67u9C7/openapi.json
