# Boulanger.com - Prices, Discounts, Variants & Reviews (`abotapi/boulanger-com-scraper`) Actor

Scrape Boulanger (boulanger.com) electronics and home-appliance products: current price plus strike-through was-price, specials, colour/capacity variant matrix, brand, category, full spec sheet and customer reviews with ratings. Search by keyword or paste links.

- **URL**: https://apify.com/abotapi/boulanger-com-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** E-commerce, Automation, Developer tools
- **Features:** Limited permissions
- **Stats:** 11 total users, 6 monthly users, 100.0% runs succeeded, 1 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/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

## Boulanger Scraper: Prices, Discounts, Variants and Reviews

Boulanger Scraper turns Boulanger (boulanger.com), the French electronics, multimedia and home-appliance retailer, into structured product data. Get the current price alongside the strike-through reference price and computed discount whenever a product is genuinely marked down, the full colour, capacity and size variant matrix, a flattened spec sheet, and the product's own customer reviews. Search by keyword and/or category, or paste product, search-result and specials-collection links (Soldes, Bons plans) directly, then export to JSON, CSV or Excel, or pull results straight into your app through the API.

### Why This Scraper?

- **Was-price and discount, never fabricated.** When a product is marked down against boulanger.com's own reference price, both prices are captured plus the computed discount amount and percentage. With Fetch full product detail on, the exact reference price and the site's own promo label come from the product page; without it, the reference price is estimated back from the search card's rounded discount percent. A full-price product simply carries `null` there, nothing is guessed.
- **Specials, first-class.** Turn on discounted-items-only to keep only products genuinely showing a strike-through discount right now, or paste a specials collection link (Soldes, Bons plans, ventes flash) directly in URL mode.
- **Real narrowing, not a filter that quietly does nothing.** The first requested brand narrows through the site's own search facet; additional brands, a price band and discounted-items-only are re-checked against the site's real values; sort reorders the full server-side result set.
- **Colour, capacity and size variant matrix.** Products sold in more than one configuration return every sibling variant, each with its own id, url, price and availability.
- **Reviews straight from the product page.** Overall rating, rating count, a rating breakdown, and the page's own individual reviews (author, date, rating, title, body, verified-purchase flag where shown).
- **Flattened spec sheet.** Full detail returns every characteristic the product page itself declares (capacity, dimensions, energy class, EAN and whatever else that category carries) rather than a fixed field list, so nothing category-specific is silently dropped.
- **Built for schedules.** Turn on incremental mode to get only new and updated products on recurring runs (plus reappeared ones once Emit expired has completed a full scan), or continue one specific interrupted crawl with a resume id.

### Use Cases

- **Price and deal tracking:** monitor a product line, or a whole specials collection, for price drops and new promotions.
- **Catalogue and spec-sheet aggregation:** build a structured database of specifications across categories for comparison tools.
- **Review and sentiment analysis:** collect ratings and written reviews to spot recurring complaints or standout products.
- **Competitive intelligence:** compare Boulanger's pricing and stock against other French electronics retailers.
- **Recurring monitoring:** schedule the actor to get only new and changed products from the same search or catalogue over time.

### Data You Get

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

| Field | Example |
| --- | --- |
| `productId` / `ean` | `"8823410"`, `"9999999999991"` |
| `name` | `"Velunox AeroClean X200 Robot Vacuum"` |
| `brand` | `"Velunox"` |
| `category` / `breadcrumbPath` | `"Aspirateurs robots"`, `["Accueil", "Electromenager", "Aspirateurs", "Aspirateurs robots"]` |
| `url` | `"https://www.boulanger.com/ref/8823410"` |
| `price` / `currency` | `225.00`, `"EUR"` |
| `priceExclTax` / `sellerName` | `187.50`, `"Sample Retailer"` (search and listing results only) |
| `originalPrice` / `discountAmount` / `discountPercent` | `300.00`, `75.00`, `25.0` |
| `isOnSpecial` / `promoLabel` | `true`, `"Ancien prix"` |
| `onlineAvailable` / `condition` | `true`, `"new"` |
| `availability` / `source` | `"InStock"` or `"OutOfStock"` (from `onlineAvailable`), `"boulanger.com"` |
| `searchQuery` / `scrapedAt` | the keyword (search mode) or pasted link (URL mode) that produced the row, and the UTC time it was scraped (both ignored by incremental change detection) |
| `unitPrice` / `storePickupAvailable` / `isBundle` | `"11.25 EUR/kg"`, `true`, `false` |
| `image` / `images` | `"https://static.example.com/000000000.jpg"`, `["https://static.example.com/000000000.jpg"]` |
| `sku` | `"VLX-X200-BLK"` (pasted product link only) |
| `description` | full product description text (when full detail is fetched) |
| `characteristics` | `{"Suction power": "10 000 Pa", "Battery life": "45 min", "Made in": "Sample Country"}` (when full detail is fetched) |
| `variants` | `[{"productId": "8823411", "axis": "couleur", "name": "Sample Colour", "url": "https://www.boulanger.com/ref/8823411", "price": 225.00, "available": true}]` (when full detail is fetched) |
| `rating` / `reviewCount` | `4.6`, `82` |
| `reviews.averageRating` / `reviews.ratingCount` / `reviews.totalReviewCount` | `4.6`, `82`, `82` |
| `reviews.ratingBreakdown` | `{"1": 0, "2": 0, "3": 0, "4": 1, "5": 2}` |
| `reviews.items[]` | `[{"rating": 5, "title": "Sample title", "body": "Sample review text.", "author": "Jane D.", "date": "2026-01-01", "verifiedPurchase": null}]` |

Incremental mode adds `changeType` (`NEW`, `UPDATED` with `changedFields`, `UNCHANGED`, `REAPPEARED` or `EXPIRED`), `firstSeenAt` and `lastSeenAt` to every record; `reviews.ratingBreakdown` and `reviews.items` always reflect only the reviews actually returned this run (capped by Max reviews per product, default 20, at most 1000, `0` means no cap; the product page itself embeds only its most recent reviews, so a product rarely returns more than about 10), while `reviews.averageRating`, `reviews.ratingCount` and `reviews.totalReviewCount` come from the page's own overall rating widget and normally carry the same total. A review's `date` and `verifiedPurchase` never both come back populated: the page's embedded review data supplies a `date` with `verifiedPurchase: null`, while the on-page fallback list (used only when that data is empty) supplies `verifiedPurchase` with `date: null`. `sku`, full `condition` values and a pasted link's own record never appear alongside `priceExclTax` or `sellerName`, which only come from a search or listing card.

#### Output views

The dataset has five views in the Console: **Products** (core columns for every row), **Variant matrix**, **Reviews**, **Product details** (description, EAN, breadcrumb, spec sheet, images; filled when full detail is fetched) and **Changes** (change type, changed fields and first/last seen; incremental mode).

### How to Use

1. Pick a **mode**: `search` (keyword and/or category) or `url` (paste product, search-result or specials links).
2. For search mode, fill in the keyword and/or category and any filters (brand, price band, discount-only, sort). For URL mode, paste one or more links. In URL mode, leaving the links field untouched scrapes eight example links (keyword search with page, sort and brand variants, category and parent category pages, and a product page), so clear it and paste your own. Mobile-host m.boulanger.com links you paste are normalised to the main site. Unsupported pages such as editorial `/evenement/` pages are skipped with a warning in the run log. Brands accept the name as typed (for example iRobot or De'Longhi) and are converted to the site's brand token; a brand that boulanger.com does not recognise (the site ignores it and serves the unfiltered list) logs a warning naming the brand, and that search stops without keeping any products. Category is free text combined with the keyword.
3. Turn on **Fetch full product detail** and/or **Fetch reviews** for the fields you need, and set **Max products** to control run size and cost.
4. Click **Start**, then download the dataset as JSON, CSV or Excel, or read it through the API.

**1. Keyword search, first page, with reviews (default):**

```json
{
  "mode": "search",
  "searchTerm": "aspirateur robot",
  "maxItems": 20
}
```

**2. Keyword + category, filtered to a brand, discounted items only, sorted by price:**

```json
{
  "mode": "search",
  "searchTerm": "smartphone",
  "category": "reconditionne",
  "brands": ["Apple"],
  "specialsOnly": true,
  "sortBy": "PRICE_ASC",
  "maxItems": 50,
  "maxPages": 5
}
```

**3. Full product detail (spec sheet and variants) and reviews for a pasted product link:**

```json
{
  "mode": "url",
  "urls": ["https://www.boulanger.com/ref/8823410"],
  "fetchDetails": true,
  "fetchReviews": true
}
```

**4. Daily monitoring: only what changed since the last run:**

```json
{
  "mode": "search",
  "searchTerm": "aspirateur robot",
  "incrementalMode": true,
  "maxItems": 50
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/boulanger-com-scraper").call(run_input={"mode": "search", "searchTerm": "aspirateur robot"})
for product in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(product["name"], product["price"], product["currency"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/boulanger-com-scraper').call({ mode: 'search', searchTerm: 'aspirateur robot' });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

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

#### How far pagination goes

With **Max pages per search** left empty, a search or listing walk continues until it has covered the result total boulanger.com itself states for that query, counted by page number against the site's own fixed 40-slot result grid. A page is not treated as the end just because it returned fewer than 40 products: the site seeds sponsored tiles into that grid, so an ordinary page in the middle of a large search can come back short. Only a walk that reached the site's own stated total, or hit a genuinely empty page, counts as a complete scan, which is also the condition incremental mode requires before it will mark anything `EXPIRED`. A stop from the filter rule below, a repeated page, or a page that failed to load is reported in the run log; a stop from Max pages or Max products is not logged separately, since it is simply the cap you set being reached.

#### How filtering is bounded

Minimum/maximum price, any brand beyond the first, and discounted-items-only have no equivalent on boulanger.com's own search request (only the first requested brand is a real search facet), so they are checked after each result page is fetched rather than sent as a query parameter. A product with no brand at all still passes a brand filter, since there is nothing to check it against. To stop a very selective combination from paging through an entire search or category looking for survivors, a search stops after 5 consecutive result pages that contained new (not yet seen) products, none of which matched your filters. Pages with nothing new on them at all, for example the early pages of a resumed run being re-walked to confirm nothing changed, never count toward this limit.

The run log line "Stopping this search after page N" means the run gave up on that 5-page rule, more matches may exist further down, so narrow the search or widen the filters to see more.

#### Resume and recurring updates

- **Resume from a previous run** continues one specific interrupted crawl. Paste a previous run ID or dataset ID and this run skips every product that run already collected, picking up where it left off.
- **Incremental mode** is for running the same search or URL set again and again (daily, weekly, on a schedule) and getting only what changed. Every product is classified `NEW` (first time seen), `UPDATED` (a comparable field changed, see `changedFields`), or `UNCHANGED` (tracked but not returned unless Emit unchanged is on). `EXPIRED` is only produced once a run has fully scanned every tracked search/URL and Emit expired is on. `REAPPEARED` is produced only for a product that a prior run already marked `EXPIRED` through that same mechanism and a later run finds again, it is never produced on a first run, and never produced at all unless Emit expired has been used in an earlier, complete, non-resumed run. State is kept per mode and search/URL set; in search mode the filters and sort are also part of that key, in URL mode they are not, so two URL-mode runs against the same links share one baseline even with different filters. Use State key to name or deliberately share a monitoring campaign. Every output field is compared (price, promo price, availability, rating and review data, seller, images and so on); `scrapedAt` (the run timestamp) and `searchQuery` (which keyword or link reached the product first, which can shift between runs) are excluded by default, and Ignore fields when detecting changes lets you exclude more, using dotted paths for nested fields (for example `reviews.items.date`). Excluded fields stay in the output, they are only skipped when comparing. Incremental mode defaults to off, so a normal run's output is unchanged until you opt in. Combining it with Resume bootstraps a new monitoring campaign from that resumed crawl's results; if a baseline already exists for that state key, the run fails rather than silently mixing the two.

### 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.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` (keyword and/or category) or `url` (paste product, search-result or specials links), continuing pagination automatically. |
| `searchTerm` | string | (none; prefill `"aspirateur robot"`) | Free-text keyword, uses boulanger.com's own product search. Search mode only. |
| `searchQueries` | array | (none) | Optional extra keywords, one per line, searched in turn after Search keyword (which keeps working alone). Filters, sort and category apply to each; Max products is the total across all keywords and a product found twice is returned once. Search mode only. |
| `category` | string | (none) | A category/department name, combined with Search keyword above into boulanger.com's own free-text search. Search mode only. |
| `sortBy` | string | `RELEVANCE` | `RELEVANCE`, `PRICE_ASC`, `PRICE_DESC`, or `TOP_RATED`, boulanger.com's own sort order, reordering the full server-side result set. Search mode only. |
| `urls` | array | eight example links (search with page, sort and brand variants, category and parent category pages, and a product link; mobile-host m.boulanger.com links are also accepted and read as the www page) | Product, search-result, category or specials-listing links to scrape; clear the field and paste your own. Pagination continues forward automatically. URL mode only. |
| `brands` | array | (none) | Exact brand names to keep. In search mode, the first is narrowed server-side through the site's own search facet and the rest checked client-side; in URL mode, every brand is checked client-side. Applies in both modes. |
| `minPrice` | number | (none) | Only return products priced at or above this amount, in euros. Checked after each page is fetched, not sent as a search parameter. Applies in both modes. |
| `maxPrice` | number | (none) | Only return products priced at or below this amount, in euros. Checked after each page is fetched, not sent as a search parameter. Applies in both modes. |
| `specialsOnly` | boolean | `false` | Keep only products currently showing a genuine strike-through discount against the site's own reference price. Applies in both modes. |
| `fetchDetails` | boolean | `false` | Add full description, EAN/GTIN, breadcrumb path, flattened spec sheet, image gallery and the colour/capacity/size variant matrix from the product page. |
| `fetchReviews` | boolean | `true` | Fetch the product's own customer reviews and rating breakdown. On by default, so most runs also fetch the product page and incur the detail & review enrichment charge per product, see Plan Requirement. |
| `maxReviewsPerProduct` | integer | `20` | Cap on written reviews fetched per product when Fetch reviews is on (maximum 1000). `0` means no cap. Does not disable the fetch or its charge, only trims how many reviews are returned. |
| `maxPages` | integer | (none; empty = no page cap) | Stop after this many result pages per search/URL entry, bounded by Max products and the guards described above. |
| `maxItems` | integer | `20` | Hard cap on total products returned across every search/URL entry. `0` = unlimited. |
| `resumeFromRunId` | string | (none) | Previous run ID or dataset ID to continue one interrupted crawl without re-returning or re-charging products it already collected. |
| `incrementalMode` | boolean | `false` | Recurring monitoring of the same search/URL set: returns only new and updated products against a remembered baseline by default (reappeared products need Emit expired too, plus a prior complete scan). |
| `stateKey` | string | (none) | Name a monitoring campaign, or deliberately share state across differently-configured runs. Derived automatically when left empty. |
| `emitUnchanged` | boolean | `false` | Also return (and bill) unchanged products. Incremental mode only. |
| `emitExpired` | boolean | `false` | Also return (and bill) expired products, only once a run fully scans every tracked search/URL. Incremental mode only. |
| `ignoreFieldsForChanges` | array | `[]` | Fields (dotted paths for nested ones) skipped when deciding UPDATED; they stay in the output. Incremental mode only. |
| `proxy` | object | `{"useApifyProxy": true}` | Proxy & connection settings; your own proxy URLs are used exactly as given. |
| `mcpConnectors` | array | (none) | Optional MCP connectors to export results into (Notion, Linear, Airtable, Apify). |
| `notionParentPageUrl` | string | (none) | Notion connector only: page under which item pages are created. |
| `maxNotifyListings` | integer | `50` | Cap on items exported to each connector per run. Does not affect the dataset. |

### Output Example

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

```json
{
  "productId": "8823410",
  "sku": "VLX-X200-BLK",
  "ean": "9999999999991",
  "name": "Velunox AeroClean X200 Robot Vacuum",
  "brand": "Velunox",
  "category": "Aspirateurs robots",
  "breadcrumbPath": ["Accueil", "Electromenager", "Aspirateurs", "Aspirateurs robots"],
  "url": "https://www.boulanger.com/ref/8823410",
  "price": 225.00,
  "priceExclTax": null,
  "currency": "EUR",
  "originalPrice": 300.00,
  "discountAmount": 75.00,
  "discountPercent": 25.0,
  "isOnSpecial": true,
  "promoLabel": "Ancien prix",
  "onlineAvailable": true,
  "condition": "new",
  "sellerName": null,
  "unitPrice": "11.25 EUR/kg",
  "storePickupAvailable": true,
  "isBundle": false,
  "image": "https://static.example.com/000000000.jpg",
  "images": ["https://static.example.com/000000000.jpg"],
  "description": "Sample product description as shown on the site.",
  "characteristics": { "Suction power": "10 000 Pa", "Battery life": "45 min", "Made in": "Sample Country" },
  "variants": [
    { "productId": "8823411", "axis": "couleur", "name": "Sample Colour", "url": "https://www.boulanger.com/ref/8823411", "price": 225.00, "available": true }
  ],
  "rating": 4.6,
  "reviewCount": 82,
  "reviews": {
    "averageRating": 4.6,
    "ratingCount": 82,
    "totalReviewCount": 82,
    "ratingBreakdown": { "1": 0, "2": 0, "3": 0, "4": 1, "5": 2 },
    "items": [
      { "rating": 5, "title": "Sample title", "body": "Sample review text.", "author": "Jane D.", "date": "2026-01-01", "verifiedPurchase": null },
      { "rating": 4, "title": "Sample title two", "body": "Another sample review text.", "author": "A. M.", "date": "2025-12-20", "verifiedPurchase": null },
      { "rating": 5, "title": "Sample title three", "body": "A third sample review text.", "author": "L. B.", "date": "2025-12-05", "verifiedPurchase": null }
    ]
  }
}
```

### Plan Requirement

The default proxy setting works on datacenter connections: in testing, Apify's default proxy served search and product pages with real content, and no residential proxy was needed. The run keeps its own record of which connection types your account may use and moves on to the next one when a connection is refused. On a plan without proxy access it continues on a backup connection and logs this notice: "The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable." If no page can be read at all, the run fails with a message such as "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required" instead of finishing empty. Upgrade at https://apify.com/pricing or supply your own proxy URLs if you see it. For large or frequent runs the residential proxy group gives more headroom; pick it under Proxy & connection.

### FAQ

#### How much does it cost?

Three things can be charged: a small run-start fee, a per-product charge for every product returned in the dataset, and a detail & review enrichment charge for every product returned in the dataset whose page was actually fetched for full detail and/or reviews. Fetch reviews is **on by default**, so on a default run almost every returned product also fetches its own page and incurs the enrichment charge, even for a product that turns out to have zero reviews, the charge is for the fetch and enrichment attempt, not for finding reviews. Turn off both Fetch full product detail and Fetch reviews to pay only the base per-product price. The Pricing tab shows the current rates; use Max products to cap the cost of any run.

#### Is it legal to scrape Boulanger?

This actor collects only publicly available product data. You are responsible for how you use it: follow Boulanger's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Product descriptions, images and customer reviews may be subject to third-party rights.

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

Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only new and updated products (plus reappeared ones once Emit expired has completed a full, uncapped scan), and unchanged ones are not returned or billed unless you turn on Emit unchanged.

#### Why did I get an empty dataset instead of an error?

A run that could not load a single page ends as failed with a clear message, never as a successful run with zero items, so a connection problem is never confused with "no matches for this search". A search that loads and finds nothing, or a pasted product link that no longer exists (logged as not found), still finishes normally. Two other situations also stop the run with a failure message: an invalid Resume from a previous run id, and combining Incremental mode with Resume when a baseline already exists for that state key. If you see the connection failure, run it again in a few minutes.

#### Why did a search return fewer products than expected?

A very selective combination of brand, price range or discounted-items-only can discard every product on several pages in a row. To avoid paging through an entire catalogue looking for survivors, the run gives up on that search after 5 consecutive pages whose new products all failed your filters, and says so in the log. Narrow the search or category, or widen the filters, to see more. Max pages and Max products can also cap a run before it reaches the natural end of the results.

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

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

<table>
<tr><td>🔌 <a href="https://apify.com/abotapi/fnac-com-scraper"><b>Fnac Scraper</b></a><br>Scrape Fnac (fnac.com) products: current price plus strike-through discount, colour/model...</td><td>🔌 <a href="https://apify.com/abotapi/darty-com-scraper"><b>Darty Scraper</b></a><br>Scrape Darty (darty.com) products: current price plus strike-through reference price and...</td></tr>
<tr><td>👗 <a href="https://apify.com/abotapi/otto-de-scraper"><b>OTTO.de Scraper</b></a><br>Scrape OTTO.de products by search or URL. Extract prices, discounts, variants...</td><td>🔌 <a href="https://apify.com/abotapi/dienmayxanh-scraper"><b>Dienmayxanh Scraper</b></a><br>Scrape dienmayxanh.com home appliances and electronics with full specifications, current...</td></tr>
<tr><td>🔌 <a href="https://apify.com/abotapi/biccamera-com-scraper"><b>Bic Camera Scraper</b></a><br>Scrape Bic Camera (biccamera.com) products: JPY price, list price and discount, Bic Point...</td><td>🔌 <a href="https://apify.com/abotapi/appliancesonline-com-au-scraper"><b>Appliances Online Scraper</b></a><br>Scrape appliancesonline.com.au by category, keyword, or URL. Extract title, brand, AUD...</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/boulanger-com-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

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

'search' finds products by keyword and/or category using boulanger.com's own search. 'url' scrapes any product or search-result-listing page URL you paste, walking pagination forward automatically.

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

Free-text keyword, e.g. 'aspirateur robot' or 'iphone'. Uses boulanger.com's own product search.

## `searchQueries` (type: `array`):

Optional extra keywords, one per line. Each is searched in turn after the Search keyword above (which keeps working on its own); the filters, sort and category apply to every one. Max products is the total across all keywords and a product found by several keywords is returned once. Search mode only.

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

A category/department name, e.g. 'smartphone', 'electromenager', 'tv'. Combined with Search keyword above into boulanger.com's own free-text search (boulanger.com does not expose a separate path-based category browse the way some catalog sites do). Search mode only: it is written into boulanger.com's own search URL, so a pasted URL keeps whatever it already carries.

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

boulanger.com's own sort order, verified live to reorder the full server-side result set. Search mode only: it is written into boulanger.com's own search URL, so a pasted URL keeps whatever it already carries.

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

Paste one or more boulanger.com product pages or search-result-listing pages (including specials collections such as the Soldes page). Pagination continues forward automatically from any numPage= already in the URL. Search-mode fields are ignored here. The fields in the Filters section still apply, including to a pasted product URL. Left untouched, URL mode scrapes the 8 example links shown (keyword search with page, sort and brand variants, category and parent category pages, and a product page). Pasted m.boulanger.com links are normalised to www.

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

Only return products from this brand, e.g. 'Apple'. Narrows via boulanger.com's own search brand facet server-side. Only the first brand in the list is applied server-side; additional brands are still narrowed client-side against the site's own real brand values. Leave empty for all brands. Applies in BOTH search mode and URL mode, including a pasted product URL.

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

Only return products priced at or above this amount, in euros. Applies in BOTH search mode and URL mode, including a pasted product URL.

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

Only return products priced at or below this amount, in euros. Applies in BOTH search mode and URL mode, including a pasted product URL.

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

Only return products currently showing a genuine strike-through discount against boulanger.com's own reference price (e.g. Soldes / Bons plans) -- verified live against real price data, never a guess. Combines with the keyword/category above. To scrape a specials collection directly, paste its listing URL (e.g. the Soldes page) in URL mode instead. Applies in BOTH search mode and URL mode, including a pasted product URL.

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

Adds full description, EAN/GTIN, breadcrumb category path, complete flattened characteristics (spec sheet -- every field the product page shows, not a fixed list), image gallery, and the colour/capacity/size variant matrix from the product page. Current price, was-price/discount, rating and card image are already included without this toggle.

## `fetchReviews` (type: `boolean`):

Fetch the product's own customer reviews: overall rating, total rating count, a rating breakdown (1-5 stars, computed from the reviews returned in this run -- boulanger.com does not expose a full-population breakdown widget), and the page's own recent individual written reviews (rating, title, body, author, date, verified-purchase where shown). boulanger.com only ever surfaces its most recent reviews per product page; products with no reviews simply return an empty list.

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

Cap on written reviews fetched per product when 'Fetch reviews' is on, up to the site's own per-page limit.

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

Stop after this many result pages per search/URL entry. Leave empty for no page limit -- the run stops at Max products total.

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

Hard cap on total products returned across every search/URL entry. 0 = unlimited (still bounded by Max pages per search).

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

Proxy configuration. The default works on every Apify plan, including the free tier. Change it only if you need a specific country or are running very large jobs.

## `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 pull in another run. For recurring monitoring of the same search, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring. Compared: every output field of a product (price, promo price, availability, rating and review data, seller, images, etc.). A change in any of them makes a product UPDATED and lists it in changedFields. Excluded by default: scrapedAt (the run timestamp) and searchQuery (which keyword or link reached the product first, which can shift between runs). Excluded fields stay in the output. The first run returns all matching products as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED products. Turn on "Emit unchanged" or "Emit expired" only when you also want those products returned (and billed). State is kept separately for each mode/search-or-URL/filter setup; use State key when you want 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 mode, search/URL and filter settings -- different 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 every tracked search/URL -- 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`):

Field names that must NOT make a product count as UPDATED, in addition to the built-in exclusions (scrapedAt and searchQuery). Use dotted paths for nested fields, e.g. reviews.averageRating or reviews.items.date (a list level is applied to every element). The fields stay in the output; they are only skipped when comparing. Incremental mode only.

## `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",
  "searchTerm": "aspirateur robot",
  "sortBy": "RELEVANCE",
  "urls": [
    "https://www.boulanger.com/resultats?tr=aspirateur+robot",
    "https://www.boulanger.com/resultats?tr=aspirateur+robot&numPage=2",
    "https://www.boulanger.com/resultats?tr=aspirateur+robot&sorting_price_f905=asc",
    "https://www.boulanger.com/resultats?tr=aspirateur+robot&brand~irobot",
    "https://www.boulanger.com/c/aspirateur-robot",
    "https://www.boulanger.com/c/aspirateur-robot?numPage=2",
    "https://www.boulanger.com/c/aspirateur",
    "https://www.boulanger.com/ref/1243539"
  ],
  "brands": [],
  "specialsOnly": false,
  "fetchDetails": false,
  "fetchReviews": true,
  "maxReviewsPerProduct": 20,
  "maxItems": 20,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

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

No description

## `variants` (type: `string`):

No description

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

No description

## `detail` (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 = {
    "mode": "search",
    "searchTerm": "aspirateur robot",
    "urls": [
        "https://www.boulanger.com/resultats?tr=aspirateur+robot",
        "https://www.boulanger.com/resultats?tr=aspirateur+robot&numPage=2",
        "https://www.boulanger.com/resultats?tr=aspirateur+robot&sorting_price_f905=asc",
        "https://www.boulanger.com/resultats?tr=aspirateur+robot&brand~irobot",
        "https://www.boulanger.com/c/aspirateur-robot",
        "https://www.boulanger.com/c/aspirateur-robot?numPage=2",
        "https://www.boulanger.com/c/aspirateur",
        "https://www.boulanger.com/ref/1243539"
    ],
    "brands": [],
    "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/boulanger-com-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",
    "searchTerm": "aspirateur robot",
    "urls": [
        "https://www.boulanger.com/resultats?tr=aspirateur+robot",
        "https://www.boulanger.com/resultats?tr=aspirateur+robot&numPage=2",
        "https://www.boulanger.com/resultats?tr=aspirateur+robot&sorting_price_f905=asc",
        "https://www.boulanger.com/resultats?tr=aspirateur+robot&brand~irobot",
        "https://www.boulanger.com/c/aspirateur-robot",
        "https://www.boulanger.com/c/aspirateur-robot?numPage=2",
        "https://www.boulanger.com/c/aspirateur",
        "https://www.boulanger.com/ref/1243539",
    ],
    "brands": [],
    "proxy": { "useApifyProxy": True },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/boulanger-com-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 '{
  "mode": "search",
  "searchTerm": "aspirateur robot",
  "urls": [
    "https://www.boulanger.com/resultats?tr=aspirateur+robot",
    "https://www.boulanger.com/resultats?tr=aspirateur+robot&numPage=2",
    "https://www.boulanger.com/resultats?tr=aspirateur+robot&sorting_price_f905=asc",
    "https://www.boulanger.com/resultats?tr=aspirateur+robot&brand~irobot",
    "https://www.boulanger.com/c/aspirateur-robot",
    "https://www.boulanger.com/c/aspirateur-robot?numPage=2",
    "https://www.boulanger.com/c/aspirateur",
    "https://www.boulanger.com/ref/1243539"
  ],
  "brands": [],
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/boulanger-com-scraper --silent --output-dataset

```

## MCP server setup

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