# E.Leclerc Scraper - Local Prices, Stock & Promotions (`abotapi/leclerc-fr-scraper`) Actor

Scrape E.Leclerc France (e.leclerc) grocery and retail products. Search by keyword or category, or paste product and category links. Pick a store by postal code so prices and stock are the real ones for that store. Returns price, unit price, stock, promotions, rating and images.

- **URL**: https://apify.com/abotapi/leclerc-fr-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** E-commerce, Automation, Developer tools
- **Features:** Limited permissions
- **Stats:** 26 total users, 5 monthly users, 94.1% runs succeeded, 2 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 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

## E.Leclerc Scraper: Product Prices, Promotions, Stock and Ratings

E.Leclerc Scraper turns the E.Leclerc France grocery and general retail catalogue into a clean product data feed. Search by keyword or category, or paste product, category and search-result links, or bare EAN barcodes. Every product comes back as one flat record with title, description, images, price, unit price, a genuine promotion, stock and, where the storefront carries one, an aggregate rating, then export to JSON, CSV or Excel, or pull the results straight into your app through the API.

### Why This Scraper?

- **Two ways to find products.** Search by keyword and/or category, then filter and sort the results, or paste product, category and search-result links, or bare EAN barcodes (an EAN lookup, one barcode per line, no link needed), mixed freely in one list.
- **One flat record, one batched call.** Title, description, images, price, promotion and stock all come from a single price call per product, always included, never gated behind an extra fetch. Unit price and brand come from search and category result pages, so a pasted product link or bare EAN does not carry them.
- **Genuine promotions only.** A "Ticket E.Leclerc" loyalty cashback, paid in full now and credited back later on the loyalty card, is reported separately and never disguised as a discounted price.
- **Reproducible store attribution.** Choose a store by postal code or an exact store sign code, and every record is stamped with it, so a run's results stay attributable and repeatable. Price and stock do not change by store, see the FAQ for how that was measured.
- **Built for schedules.** Incremental mode returns only new and changed products on recurring runs, and a run fails loudly instead of silently returning nothing when a store cannot be resolved.
- **Resume and checkpoint protection.** Continue one interrupted crawl with a pasted run id, and a long run picks up after the last completed keyword, category or link on a platform migration.
- **Send results into the apps you use.** Optional Notion, Linear, Airtable or Apify connectors, on top of the dataset, which is never changed by the export step.

### Use Cases

- **Grocery price monitoring:** track price, promotion and stock changes for a shopping list of products on a recurring schedule.
- **Category and catalogue research:** pull a full category listing, for example laundry detergents, with ratings and unit prices for assortment analysis.
- **Promotion tracking:** filter for products currently on a genuine promotion, kept separate from loyalty cashback offers.
- **Competitive and market intelligence:** compare E.Leclerc's national price and promotion for a list of EANs against other retailers.
- **Product catalogue enrichment:** pull title, description, images and a category breadcrumb for a list of EANs to enrich an existing catalogue.

### Data You Get

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

| Field | Example |
| --- | --- |
| `productId` | `"3700281702286"` |
| `ean` | `"3700281702286"` |
| `title` | `"ULTRA-HYDRATANT Lait Corps 500 ml"` |
| `brand` | `"Topicrem"` (search and category results only, absent for a pasted product link or EAN) |
| `description` | `"L'Ultra-Hydratant lait corps Topicrem est une emulsion onctueuse..."` |
| `images` | `["https://media.e.leclerc/3700281702286_1"]` |
| `categoryPath` | `"Cremes hydratantes > Topicrem > Les Essentiels"` |
| `price` | `11.04` |
| `unitPriceDisplay` | `"4.16 EUR / L"` (search and category results only, absent for a pasted product link or EAN) |
| `wasPrice` | `9.99` (only on a genuine promotion; the key is absent otherwise, never `null`) |
| `discountAmount` | `1.5` (only on a genuine promotion; the key is absent otherwise, never `null`) |
| `onPromotion` | `true` |
| `loyaltyCashbackPercent` | `30.0` |
| `loyaltyCashbackLabel` | `"Ticket E.Leclerc avec la carte"` |
| `inStock` | `true` |
| `stock` | `537` |
| `rating` | `4.8` |
| `reviewCount` | `28` |
| `storeSignCode` | `"1375"` |
| `storeName` | `"Montparnasse"` |
| `storePostalCode` | `"75014"` |
| `canonicalUrl` | `"https://www.e.leclerc/fp/ultra-hydratant-lait-corps-500-ml-3700281702286"` (search and category results only, absent for a pasted product link or EAN) |
| `searchMode` | `"search"` |
| `scrapedAt` | `"2026-09-15T08:00:00Z"` |

A record can also carry `lastUpdateDate` (when the catalogue entry was last updated), `logisticClassCode` (E.Leclerc logistics class, when carried), `creationDate` and `publicationDate` (catalogue timestamps), `color` (only for products that declare a variant colour), `currency` (always `"EUR"`), `unitPrice` and `unitPriceUnit` as the numeric parts of `unitPriceDisplay` (same search/category-only scope), `discountPercent` alongside `discountAmount`, `availabilityStatus` and `warehouse` from the price call, `descriptionHtml` alongside `description`, `source` (`"listing"` or `"product"`, showing which kind of page the record was built from), `promotionStartDate` and `promotionEndDate` on any promotion including a loyalty cashback (the promotion's first and last day), `offersTotal` (the number of seller offers the storefront lists for the product; `0` when it is not sold here), `promotionLabel` on a non-loyalty promotion only, and a `reviews` array on any product that has a rating: up to **Max reviews per product** reviews, each with `reviewId`, `reviewer`, `reviewRating`, `reviewDate`, `reviewTitle`, `reviewText`, `reviewVerifiedBuyer`, `reviewRecommended`, `reviewSource`, `reviewHelpfulVotes`, `reviewUnhelpfulVotes` and `reviewReplies` (shop answers). The array is empty when reviews are off or the product has none. When Incremental mode is on, every record also carries `changeType` (`NEW`, `UPDATED`, `UNCHANGED`, `REAPPEARED` or `EXPIRED`), `changedFields` (populated only for `UPDATED`), `firstSeenAt` and `lastSeenAt`.

### How to Use

1. Choose your **store**: enter a French postal code, or paste an exact store sign code copied from `storeSignCode` in a previous run's output. The run picks the nearest Hyper, Super or Express E.Leclerc store to a postal code.
2. Pick a **mode**: `search` (keywords and/or category links, with filters and sorting) or `url` (paste product, category or search-result links, or bare EAN barcodes).
3. Fill in the fields for that mode, and set **Max products** to control run size and cost.
4. Click **Start**. Download the dataset as JSON, CSV or Excel, or read it through the API.

**Search by keyword:**

```json
{
  "postalCode": "75014",
  "mode": "search",
  "queries": ["lait"],
  "maxItems": 20
}
```

**Browse a category, filtered to promotions in stock, cheapest first:**

```json
{
  "postalCode": "75014",
  "mode": "search",
  "categories": ["https://www.e.leclerc/cat/lessives-marque-repere"],
  "onPromotionOnly": true,
  "inStockOnly": true,
  "sortBy": "price_asc"
}
```

**Paste product links and a bare EAN:**

```json
{
  "postalCode": "75014",
  "mode": "url",
  "urls": [
    "https://www.e.leclerc/fp/physiolac-lait-infantile-bio-1er-age-800g-3518646124167",
    "3700281702286"
  ]
}
```

**Daily monitoring of the same store and search:**

```json
{
  "postalCode": "75014",
  "mode": "search",
  "queries": ["lait"],
  "incrementalMode": true
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/leclerc-fr-scraper").call(run_input={"postalCode": "75014", "mode": "search", "queries": ["lait"]})
for product in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(product["title"], product.get("price"), product.get("onPromotion"))
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/leclerc-fr-scraper').call({ postalCode: '75014', mode: 'search', queries: ['lait'] });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

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

#### Store selection and national pricing

Price, promotion and stock returned by this actor are the storefront's national reference offer: measured live against two deliberately distant stores, every one of 30 checked products came back identical, and an invalid store code returns the same response as a valid one. Choosing a store still matters because it stamps every record with `storeSignCode`, `storeName` and `storePostalCode` for a reproducible run, and because it keys the incremental baseline. If both **Postal code** and **Store sign code** are filled, **Store sign code** wins.

#### Resume and recurring updates

Two different things, pick the one that matches what you are doing.

**Resume** (`resumeFromRunId`) continues one specific interrupted or previous large crawl: paste a run id or dataset id and the run skips products already collected there. An automatic checkpoint also protects a long run against a platform migration without any input needed.

**Incremental mode** (`incrementalMode`) is for a recurring schedule over the same store and search: the actor remembers the previous run by itself, so no run id is pasted. The first run returns every product as `NEW`. Later runs return `NEW` and `UPDATED` products (`changedFields` lists what changed). A product is `UPDATED` when any returned field changed (price, was-price, promotion, stock, title, brand, images, description and so on); these fields are returned but never compared: the scrape timestamp (`scrapedAt`), the catalogue metadata `lastUpdateDate`, `logisticClassCode`, `color`, `creationDate` and `publicationDate`, the seller-offer count `offersTotal`, and `promotionStartDate` (a promotion change still shows through `promotionEndDate`, price and was-price); you can also list any field in `ignoreFieldsForChanges` (a top-level name such as `reviewCount`, or a dotted path such as `name.key` for a key nested inside an object or list). Ignored fields are still returned; they just never trigger `UPDATED` or appear in `changedFields`. Unchanged products are suppressed and not billed unless `emitUnchanged` is on. A product is only classified `REAPPEARED` if an earlier run had **Emit expired products** on, completed a full scan of the tracked search, and marked that product `EXPIRED`, and this run finds it again. Without ever turning **Emit expired products** on, `REAPPEARED` is never produced. `EXPIRED` rows themselves are only added when **Emit expired products** is on and the run reached the natural end of every tracked search, not when Max products or Max pages capped it, not when a link could not be read, and not on a Resume run. When Max products leaves only a few rows of headroom, at most that many `EXPIRED` rows are produced per run and the remaining ones are kept for the next run. Set `stateKey` to name or deliberately share a monitoring campaign; a different store gets its own state automatically unless you set the same `stateKey` for both.

### 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 have not authorized a connector yet.
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `postalCode` | string | none (prefill: `"75014"`) | A French postal code. The run picks the nearest Hyper, Super or Express store to it. One of `postalCode` or `storeSignCode` must be set for the run to start. |
| `storeSignCode` | string | none | Optional exact-store override. Wins over `postalCode` when both are set. |
| `mode` | string | `"search"` | `search` or `url`. |
| `queries` | array | none (prefill: `["lait"]`) | Search keywords (search mode). |
| `categories` | array | none | E.Leclerc category page links (search mode). |
| `urls` | array | 5 distinct examples (a category, a `lait` search, a `chocolat` search page 2, a product link, an EAN-only product link); the prefill offers all 9 link shapes, including page, sort and no-`www` variants that resolve to the same rows | Product links, bare EANs, category links and search-result links (url mode). A url-mode run with this field left untouched scrapes those 5 examples; clear the field and add your own links to scrape something else. |
| `sortBy` | string | `"relevance"` | `relevance`, `price_asc`, `price_desc`, `unit_price_asc`, `unit_price_desc`, or `name`. |
| `minRating` | string | `"0"` | Minimum average rating: `0`, `3`, `4`, or `5`. |
| `onPromotionOnly` | boolean | `false` | Keep only products with a genuine reduced price. |
| `inStockOnly` | boolean | `false` | Keep only products currently in stock. |
| `minPrice` | integer | none | Keep only products priced at or above this amount, in EUR. |
| `maxPrice` | integer | none | Keep only products priced at or below this amount, in EUR. |
| `maxReviewsPerProduct` | integer | `20` | Customer reviews collected per product that has reviews (each stored in the `reviews` array as `reviewId`, `reviewer`, `reviewRating`, `reviewDate`, `reviewTitle`, `reviewText`, `reviewVerifiedBuyer`, `reviewRecommended`, `reviewSource`, `reviewHelpfulVotes`, `reviewUnhelpfulVotes`, `reviewReplies`), 10 per page. `0` turns reviews off; the maximum is `1000`, which bounds run time and cost. |
| `fetchDetails` | boolean | `true` | Fetch each product's page for a human-readable category breadcrumb. Price, stock, promotion and rating are always fetched regardless of this toggle. The product page is also always fetched for a pasted product link or EAN, and whenever **Minimum rating** is set, even with this off. |
| `maxItems` | integer | `20` | Stop after this many products (`0` = no limit; maximum 100000). |
| `maxPages` | integer | `0` | Result pages walked per keyword, category or link (`0` = unlimited). |
| `resumeFromRunId` | string | none | Continue one interrupted or previous run. |
| `incrementalMode` | boolean | `false` | Return only new and changed products on recurring runs of the same store and search. |
| `stateKey` | string | none | Name or share an incremental-mode state. |
| `emitUnchanged` | boolean | `false` | Also return (and bill) unchanged products. |
| `emitExpired` | boolean | `false` | Also return (and bill) products no longer found, once a run has fully scanned the search. |
| `ignoreFieldsForChanges` | array | `[]` | Field names (or dotted `name.key` paths) left out of change detection in incremental mode. |
| `proxy` | object | `{"useApifyProxy": true}` | Connection settings. A datacenter connection is used by default. |
| `mcpConnectors` | array | none | Optional: send a summary of each product to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | none | Notion connector only: page under which item pages are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. |

### Output Example

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

```json
{
  "productId": "3661112223334",
  "ean": "3661112223334",
  "title": "Lessive liquide 40 lavages",
  "brand": "Marque Repere",
  "description": "Lessive liquide concentree pour un lavage efficace au quotidien.",
  "descriptionHtml": "<p>Lessive liquide concentree pour un lavage efficace au quotidien.</p>",
  "images": ["https://media.e.leclerc/3661112223334_1"],
  "categoryPath": "Entretien > Lessives > Lessives liquides",
  "price": 8.49,
  "currency": "EUR",
  "unitPrice": 0.21,
  "unitPriceUnit": "lavage",
  "unitPriceDisplay": "0.21 EUR / lavage",
  "wasPrice": 9.99,
  "discountAmount": 1.5,
  "discountPercent": 15.0,
  "onPromotion": true,
  "availabilityStatus": "in-stock",
  "inStock": true,
  "stock": 214,
  "rating": 4.3,
  "reviewCount": 112,
  "reviews": [],
  "storeSignCode": "1375",
  "storeName": "Montparnasse",
  "storePostalCode": "75014",
  "canonicalUrl": "https://www.e.leclerc/fp/lessive-liquide-40-lavages-3661112223334",
  "searchMode": "search",
  "source": "listing",
  "scrapedAt": "2026-09-15T08:00:00Z"
}
```

The Console dataset has four views: **Products** (overview), **Product details** (breadcrumb, description, stock and logistics fields), **Reviews (one row per review, with the product title and link next to the `review...` columns)** and **Changes (incremental mode)** (change type, changed fields, first and last seen). Every field is also available through the API and in all export formats.

### Plan Requirement

A proxy connection is required. The default proxy setting (a datacenter exit through the Apify proxy) was measured to load E.Leclerc search, category and product pages with real product data and no challenge page, so residential is optional and not needed. Apify free plans do not include any proxy access: on a free plan a run stops with a message saying a paid plan is required, or, when only residential was selected, continues on a backup connection with a warning that some pages may be unavailable. If every connection fails the run ends with "Could not load any results page (the requests failed)" rather than an empty dataset. You can also supply your own proxy URLs. The **Pricing** tab shows the current per-event rates for this actor: a charge per product returned, plus a detail-enrichment charge for pages fetched and for each 10 reviews delivered.

### FAQ

#### How much does it cost?

You pay per product returned, plus a detail-enrichment charge only for a product that is actually returned in the dataset and whose page was fetched and read. The page is fetched when **Fetch product details** is on, for every pasted product link or EAN, and whenever **Minimum rating** is set. The **Pricing** tab shows the current rates. Reviews add a detail-enrichment charge of one unit per 10 reviews delivered (rounded up across the run), only for reviews inside rows that are actually returned. Use **Max products** and **Max reviews per product** to cap the cost of any run.

#### Is it legal to scrape E.Leclerc?

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

#### Why does the price and stock look the same for every store I try?

This is expected, not a bug. The price, promotion and stock this actor returns are the storefront's national reference offer, verified live to be identical across distant stores. Choosing a store still stamps every record with `storeSignCode`, `storeName` and `storePostalCode`, and keeps a separate incremental baseline per store unless you set the same `stateKey` for both, but it does not change the price or stock values.

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

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental changes for scheduled runs**. Each run then returns only new and updated products, and unchanged ones are not billed. A product only comes back as `REAPPEARED` if an earlier run had **Emit expired products** on and had already marked it `EXPIRED`; turning `emitExpired` on for the first time will not retroactively produce `REAPPEARED` rows.

#### Why did my run fail instead of returning an empty dataset?

If the postal code or store sign code you provided cannot be resolved to a real E.Leclerc store, the run stops with a clear message instead of silently returning nothing. Check the code and try again. If the store resolves but no products match your keywords, links or filters, the run finishes normally with an empty dataset and a status message explaining why.

#### 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 grocery data?

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

<table>
<tr><td>🥦 <a href="https://apify.com/abotapi/auchan-fr-scraper"><b>Auchan France Scraper</b></a><br>Scrape Auchan.fr products: grocery, drinks, household and general merchandise. Real price...</td><td>🥦 <a href="https://apify.com/abotapi/carrefour-es-scraper"><b>Carrefour Spain Scraper</b></a><br>Scrape Carrefour Spain (carrefour.es) products. Search by keyword, browse a category, or...</td></tr>
<tr><td>🥦 <a href="https://apify.com/abotapi/rewe-de-scraper"><b>REWE.de Scraper</b></a><br>Scrape REWE Germany (rewe.de) grocery products: current price, was-price and discount...</td><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></tr>
<tr><td>🥦 <a href="https://apify.com/abotapi/instacart-grocery-price-scraper"><b>Instacart Scraper</b></a><br>Scrape Instacart grocery catalogs with per-store prices. Run keywords across several...</td><td>🥦 <a href="https://apify.com/abotapi/flashfood-grocery-deals-scraper"><b>Flashfood Scraper</b></a><br>Scrape Flashfood discounted groceries and stores by location or store. Extract current...</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/leclerc-fr-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

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

A French postal code (e.g. 75014). The run picks the nearest Hyper/Super/Express E.Leclerc store to this postal code and stamps it onto every result. Note: price, promotion and stock are the storefront's national values and do NOT change with the store (measured live 2026-09-04). Ignored if Store sign code is also set.

## `storeSignCode` (type: `string`):

Optional. Pin an exact E.Leclerc store by its 4-digit sign code instead of resolving one from a postal code -- useful to reproduce a previous run exactly (every result carries its store's sign code as storeSignCode, so you can copy it from there). It changes the stamp and the incremental baseline, not the price/stock values. Overrides Postal code when set.

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

Choose 'search' for keywords and/or category links with filters, or 'url' to scrape pasted E.Leclerc links -- product links, category links, search-result links and bare product barcodes (EAN) are all accepted.

## `queries` (type: `array`):

One or more keywords to search, for example 'lait' (milk) or 'lessive' (laundry detergent). Each keyword is searched separately. A keyword that matches one of the storefront's own categories is served that category's full listing automatically (e.g. 'lessive' resolves to the Laundry detergents category).

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

One or more E.Leclerc category page links, each walked separately across its result pages. To get one: open e.leclerc, browse to the category you want, and copy the address -- a category address looks like https://www.e.leclerc/cat/lessives-marque-repere .

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

Only used when mode = url -- ignored in search mode. Mix freely: product pages under /fp/<slug>-<ean>, category pages under /cat/<slug>, search-result pages under /recherche?q=..., or bare EAN barcodes such as 3518646124167 (EAN lookup: one barcode per entry, no link needed). Multiple entries supported.

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

How to order the collected products. Applied over the products gathered in the run before they are returned.

## `minRating` (type: `string`):

Optional. Only keep products whose average rating is at least this many stars. Products with no rating are excluded when this is set.

## `onPromotionOnly` (type: `boolean`):

Optional. Keep only products with a genuine reduced price versus a real prior price. A "Ticket E.Leclerc" loyalty cashback (paid back later on your card, not a price cut) never counts as a promotion here.

## `inStockOnly` (type: `boolean`):

Optional. Keep only products the storefront reports as currently in stock.

## `minPrice` (type: `integer`):

Optional. Only keep products priced at or above this amount.

## `maxPrice` (type: `integer`):

Optional. Only keep products priced at or below this amount.

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

Fetch each product's detail page for a human-readable category breadcrumb. Title, description, images, price, unit price, promotion, stock and rating are always included regardless of this toggle -- they come from the same call as price, at no extra cost. Turn on for the friendlier breadcrumb; turn off for a faster run when you don't need it.

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

Customer reviews to collect for each product that has reviews (reviewer, rating, date, title, text, shop replies, helpful votes), 10 per page. 0 turns reviews off. Capped at 1000 per product to bound run time.

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

Maximum number of products to return across the whole run. This is the run's cap. Use 0 for unlimited. Capped at 100000.

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

Maximum result pages walked per keyword / category / link. 0 = unlimited (walk all pages) -- the run then stops only at Max products or when a page repeats no new products.

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

Apify Proxy configuration. The actor connects through a datacenter exit by default; residential is optional and not required.

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

Paste a previous run ID or dataset ID to continue a large crawl of products without returning or charging for products already collected there. Use this after an interrupted run, or when continuing a product catalogue pull in another run. For recurring daily 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 any returned field changed (price, was-price, promotion, stock, title, brand, images, description and so on); the Changed fields column names which. These fields are returned but never compared: the scrape timestamp (scrapedAt), the catalogue metadata lastUpdateDate, logisticClassCode, color, creationDate and publicationDate, the seller-offer count offersTotal, and promotionStartDate (a promotion change still shows through promotionEndDate, price and was-price); also any field names you list in "Ignore fields when detecting changes" below. Turn on "Emit unchanged" or "Emit expired" only when you also want those products returned (and billed). State is kept separately per store, search/category/URL and detail-mode setup -- use State key 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 store/search/category/URL and detail settings -- different stores or 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`):

Optional. Field names to leave out of change detection, so a field that moves without the product really changing does not mark it UPDATED. Always ignored: the scrape timestamp (scrapedAt), the catalogue metadata lastUpdateDate, logisticClassCode, color, creationDate and publicationDate, the seller-offer count offersTotal, and promotionStartDate (a promotion change still shows through promotionEndDate, price and was-price). Use a top-level name such as reviewCount or rating, or a dotted path (name.key) for a key nested inside an object or list. Ignored fields are still returned in the output; they just never trigger UPDATED or appear in Changed fields.

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

Optionally send results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize one under Apify, Settings, API & Integrations, then select it here. Notion gets a rich page-per-item export; other connectors get a best-effort write/digest. Leave empty to skip; never changes the dataset output. 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
{
  "postalCode": "75014",
  "mode": "search",
  "queries": [
    "lait"
  ],
  "urls": [
    "https://www.e.leclerc/cat/lessives-marque-repere",
    "https://www.e.leclerc/cat/lessives-marque-repere?page=2",
    "https://www.e.leclerc/cat/lessives-marque-repere?sort=price",
    "https://www.e.leclerc/recherche?q=lait",
    "https://www.e.leclerc/recherche?q=lait&page=2",
    "https://www.e.leclerc/recherche?q=chocolat&page=2",
    "https://www.e.leclerc/fp/lessive-liquide-pure-fraicheur-40-lavages-2-l-epsil-3564706670389",
    "https://www.e.leclerc/fp/produit-3518646124167",
    "https://e.leclerc/cat/lessives-marque-repere"
  ],
  "sortBy": "relevance",
  "minRating": "0",
  "onPromotionOnly": false,
  "inStockOnly": false,
  "fetchDetails": true,
  "maxReviewsPerProduct": 20,
  "maxItems": 20,
  "maxPages": 0,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

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

No description

## `detail` (type: `string`):

No description

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

No description

## `changes` (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 = {
    "postalCode": "75014",
    "mode": "search",
    "queries": [
        "lait"
    ],
    "urls": [
        "https://www.e.leclerc/cat/lessives-marque-repere",
        "https://www.e.leclerc/cat/lessives-marque-repere?page=2",
        "https://www.e.leclerc/cat/lessives-marque-repere?sort=price",
        "https://www.e.leclerc/recherche?q=lait",
        "https://www.e.leclerc/recherche?q=lait&page=2",
        "https://www.e.leclerc/recherche?q=chocolat&page=2",
        "https://www.e.leclerc/fp/lessive-liquide-pure-fraicheur-40-lavages-2-l-epsil-3564706670389",
        "https://www.e.leclerc/fp/produit-3518646124167",
        "https://e.leclerc/cat/lessives-marque-repere"
    ],
    "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/leclerc-fr-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 = {
    "postalCode": "75014",
    "mode": "search",
    "queries": ["lait"],
    "urls": [
        "https://www.e.leclerc/cat/lessives-marque-repere",
        "https://www.e.leclerc/cat/lessives-marque-repere?page=2",
        "https://www.e.leclerc/cat/lessives-marque-repere?sort=price",
        "https://www.e.leclerc/recherche?q=lait",
        "https://www.e.leclerc/recherche?q=lait&page=2",
        "https://www.e.leclerc/recherche?q=chocolat&page=2",
        "https://www.e.leclerc/fp/lessive-liquide-pure-fraicheur-40-lavages-2-l-epsil-3564706670389",
        "https://www.e.leclerc/fp/produit-3518646124167",
        "https://e.leclerc/cat/lessives-marque-repere",
    ],
    "proxy": { "useApifyProxy": True },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/leclerc-fr-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 '{
  "postalCode": "75014",
  "mode": "search",
  "queries": [
    "lait"
  ],
  "urls": [
    "https://www.e.leclerc/cat/lessives-marque-repere",
    "https://www.e.leclerc/cat/lessives-marque-repere?page=2",
    "https://www.e.leclerc/cat/lessives-marque-repere?sort=price",
    "https://www.e.leclerc/recherche?q=lait",
    "https://www.e.leclerc/recherche?q=lait&page=2",
    "https://www.e.leclerc/recherche?q=chocolat&page=2",
    "https://www.e.leclerc/fp/lessive-liquide-pure-fraicheur-40-lavages-2-l-epsil-3564706670389",
    "https://www.e.leclerc/fp/produit-3518646124167",
    "https://e.leclerc/cat/lessives-marque-repere"
  ],
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/leclerc-fr-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/leclerc-fr-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/Mq73lY3udUUw2y4sF/builds/9etsi4SWTjHdb5u2N/openapi.json
