# Mercadona ES - Product prices (`scrapingbuddy/mercadona-es-products`) Actor

Scrapes products and prices from the Mercadona online supermarket by search term, brand or category, for any Spanish postal code or warehouse.

- **URL**: https://apify.com/scrapingbuddy/mercadona-es-products.md
- **Developed by:** [Giacomo Pigani](https://apify.com/scrapingbuddy) (community)
- **Categories:** E-commerce
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.80 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/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

Scrapes products and prices from [Mercadona's online supermarket](https://tienda.mercadona.es).
You can search the same way as on the website, filter by brand or category, or download the
whole catalogue. Prices come from the warehouse that serves any Spanish postal code.

### Input

Every parameter is optional. Give a search term or categories to scrape part of the catalogue,
or leave both empty to get every product in the catalog. Without a postal code or warehouse, prices come
from Mercadona's default warehouse, `vlc1` (Valencia).

| Parameter | Options | Description |
|---|---|---|
| `searchTerm` | (optional) Text to search for | One search, like the search box on the website. A phrase like `leche condensada` is searched as a whole. Returns the same products, in the same order, as the website, up to 100. |
| `brands` | (optional) A brand, e.g. `"Hacendado"`, or a list, e.g. `["Hacendado", "Campina"]` | Keeps only the search results of these brands. Capitalisation is ignored. Needs a search term. |
| `categories` | (optional) Category or subcategory IDs, as strings, e.g. `["112"]` | Only these categories. A top-level category includes all its subcategories. With a search term, keeps only the results in these categories. Get the current list of IDs with the [Mercadona Category IDs](https://apify.com/scrapingbuddy/mercadona-es-category-ids) Actor. |
| `postalCode` | (optional) 5-digit Spanish postal code | Uses the warehouse that serves this postal code. Don't combine with `warehouse`. |
| `warehouse` | (optional) Warehouse code, see [Warehouses](#warehouses) | Uses this warehouse. Don't combine with `postalCode`. The run fails if Mercadona doesn't serve the code. |
| `language` | • `es` (Spanish, default)<br>• `vai` (Valencian)<br>• `ca` (Catalan)<br>• `gl` (Galician)<br>• `eu` (Basque)<br>• `en` (English)<br>• `de` (German) | Language of product and category names. |
| `includeEan` | (optional) `true` (default) or `false` | Whether category and whole-catalogue rows include `ean` and `brand`, fetched from each product's page (one extra request per product). Set `false` for listings and prices only, much faster for a whole-catalogue run. Searches always include the EAN. |

Example using a search query:

```json
{
  "searchTerm": "leche condensada",
  "postalCode": "08012"
}
```

Example keeping only some brands:

```json
{
  "searchTerm": "leche condensada",
  "brands": ["Campina", "Hacendado"]
}
```

Example using a category:

```json
{
  "categories": ["112"],
  "warehouse": "bcn1"
}
```

Example to get all products for a postal code:

```json
{
  "postalCode": "08012"
}
```

#### Warehouses

| Code | Provinces |
|------|-----------|
| alc1 | Alicante, Murcia |
| bcn1 | Barcelona |
| mad1 | Madrid |
| mad2 | Madrid |
| mad3 | Madrid |
| svq1 | Sevilla, Cádiz, Huelva |
| vlc1 | Valencia, Alicante, Castellón |

Other regions use warehouses with numeric codes, for example `4391` for Bilbao (48001) or
`4493` for Zaragoza (50001). For those, it's easiest to pass the postal code.

### Output

- **Dataset**: one item per product, as Mercadona returns it, plus `subcategory` and `group`
  (`id` and `name`). Every row carries `ean` and `brand`. Search results also carry
  `search_term` and more detail than category browsing: origin, suppliers, ingredients,
  allergens, photos and names in every language. A product listed in several places appears
  only once per run.
- **`WAREHOUSE`** (key-value store): what was requested and which warehouse served the data,
  e.g. `{"requested_postal_code": "08012", "requested_warehouse": null, "warehouse": "bcn1"}`.
- **`FAILED_REQUESTS`** (key-value store, only when some requests failed): the URL and error
  of each failed page. If any page failed, the run fails rather than returning an incomplete
  dataset as a success.

#### Product fields

Values come as Mercadona sends them. For example, prices are text such as `"3.35"`. Fields
inside an object are written with a dot, e.g. `price_instructions.unit_price`.

| Field | Description |
|---|---|
| `id` | Mercadona product ID. |
| `display_name` | Product name, in the chosen language. |
| `slug`, `share_url` | URL slug and product page on tienda.mercadona.es. |
| `thumbnail` | Product image (300×300). |
| `packaging` | How it is sold, e.g. `Bote`, `Paquete`, `Pack-6`. Can be empty. |
| `main_feature` | Mercadona's short qualifier, e.g. `0% MG 0% azúcares añadidos`. Often empty. |
| `categories` | Category rows: only the top-level category. Search rows: the full category path. |
| `subcategory`, `group` | Second- and third-level category (`id`, `name`), added by the Actor. |
| `published` | Whether the product is listed. |
| `limit` | Most units per order. `999` means no practical limit. |
| `unavailable_from` | Date from which the product can no longer be delivered. Usually empty. |
| `unavailable_weekdays` | Weekdays the product can't be delivered (Mercadona's numbering). Usually empty. |
| `badges.requires_age_check` | Alcohol: the age is checked at delivery. |
| `badges.is_water` | Bottled water. |
| `brand` | Brand. In category browsing this and `ean` come from one extra request per product, so a whole-catalogue run makes about one request per product on top of the category pages. |
| `ean` | EAN barcode. Can be empty for a few products. |
| `is_new_arrival` | Category rows only: the product is in Mercadona's *Novedades*. |
| `search_term` | Search rows only: the search that returned the product. |

**Prices and sizes** (`price_instructions`):

| Field | Description |
|---|---|
| `unit_price` | Price in EUR of one sales unit, i.e. what goes in the cart. For products sold by weight, the price of the minimum amount. |
| `previous_unit_price` | The previous price, when Mercadona shows one. Padded with spaces, e.g. `"        1.40"`. Usually empty. |
| `bulk_price` | Price per kg, litre or unit, rounded to cents. |
| `reference_price`, `reference_format` | The shelf-label price per unit and its unit, e.g. `"3.350"` per `kg`. |
| `unit_size`, `size_format` | Content of one sales unit and its unit (`kg`, `l`, `ud`). |
| `approx_size` | The size is approximate (fresh products sold per piece). |
| `is_pack`, `total_units`, `pack_size`, `unit_name` | Multipacks, e.g. 8 units (`ud.`) of 0.125 kg, or 6 `briks`. |
| `drained_weight` | Drained weight in kg, for tins and frozen products. |
| `tax_percentage` | VAT rate: `"4.000"`, `"10.000"` or `"21.000"`. |
| `selling_method` | `0` sold by unit, `1` sold by weight (a few fresh products). |
| `unit_selector`, `bunch_selector`, `min_bunch_amount`, `increment_bunch_amount` | How the website lets you choose the quantity. For products sold by weight: the minimum and step, in kg. |

**Search rows only.** The search returns the full product page:

| Field | Description |
|---|---|
| `origin` | Country of origin. |
| `is_bulk`, `is_variable_weight` | Sold loose / sold by variable weight. |
| `photos` | Every product photo, each in `zoom`, `regular` and `thumbnail` size. |
| `details.legal_name`, `details.description` | Legal name and description. |
| `details.suppliers` | Suppliers (`name`). |
| `details.storage_instructions`, `details.usage_instructions` | How to store and use the product. |
| `details.mandatory_mentions`, `details.danger_mentions`, `details.alcohol_by_volume` | Legal mentions, hazard warnings and alcohol content. |
| `details.is_prepared_by_mercadona` | Made in Mercadona's stores or kitchens (bakery, *Listo para Comer*). It is not about own brands such as Hacendado. Can be empty. |
| `nutrition_information.ingredients`, `nutrition_information.allergens` | Ingredients and allergens, as HTML with allergens in `<strong>`. |
| `translations` | Name and categories in every language (`ca`, `en`, `eu`, `vai`). |

**Fields not to trust.** Mercadona sends these, but they are wrong or mostly empty. Figures
are from the Barcelona catalogue (4,306 products):

- `price_instructions.price_decreased` is always `false`, even for the 153 products whose
  `previous_unit_price` is higher than `unit_price`. Compare the two prices instead.
- `price_instructions.is_new` is always `false`. Use `is_new_arrival` instead.
- `price_instructions.iva` is empty for 3,118 products. Use `tax_percentage` instead.
- `status` is always empty.

# Actor input Schema

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

One search, like the search box on the Mercadona website. A phrase like "leche condensada" is searched as a whole. Returns the same products, in the same order, as the website, up to 100. Combine with categories to keep only results in those categories.

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

Only keep search results of these brands, e.g. "Hacendado" or \["Hacendado", "Campina"]; capitalisation is ignored. Filtered like the Mercadona website, from the at most 100 products a search returns. Needs a search term.

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

Only scrape these categories. Picking a top-level category includes all its subcategories. Leave empty to scrape the whole catalogue.

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

Spanish postal code (5 digits). Mercadona uses it to pick the warehouse that serves that area, which sets prices and availability. Set either a postal code or a warehouse, not both.

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

Mercadona warehouse code, e.g. mad1, bcn1 or vlc1 (the README lists them by province). Sets prices and availability. Set either a warehouse or a postal code, not both.

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

Language of the product and category names returned by Mercadona.

## `includeEan` (type: `boolean`):

When scraping categories or the whole catalogue, fetch each product's EAN barcode and brand from its page. This is one extra request per product, so a whole-catalogue run is much slower. Turn it off to get listings and prices only. Searches always include the EAN regardless.

## Actor input object example

```json
{
  "searchTerm": "leche condensada",
  "brands": [],
  "categories": [],
  "language": "es",
  "includeEan": true
}
```

# Actor output Schema

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

No description

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

Requested postal code or warehouse, and the warehouse that served the data.

# 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 = {
    "searchTerm": "leche condensada"
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapingbuddy/mercadona-es-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 = { "searchTerm": "leche condensada" }

# Run the Actor and wait for it to finish
run = client.actor("scrapingbuddy/mercadona-es-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 '{
  "searchTerm": "leche condensada"
}' |
apify call scrapingbuddy/mercadona-es-products --silent --output-dataset

```

## MCP server setup

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