# bol.com Scraper - Products, Prices, Sellers, EAN & Reviews (`scrapewise/bol-scraper`) Actor

Scrape bol.com (Netherlands and Belgium) by keyword, category or product link: price, reference price, discount, Select price, seller, delivery date, rating, review count, categories, badges and sponsored flag. Optional product pages add EAN and full specs. Error rows are free.

- **URL**: https://apify.com/scrapewise/bol-scraper.md
- **Developed by:** [Scrapewise Data](https://apify.com/scrapewise) (community)
- **Categories:** E-commerce, Lead generation
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.62 / 1,000 product delivereds

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

## bol.com Scraper: products, prices, sellers, delivery, ratings and EAN from bol.com NL and BE

bol.com Scraper collects **products from bol.com, the largest online store in the Netherlands and Belgium**, without
an account or cookies. Give it keywords, category links or product links and every product comes back as
one clean row: title, brand, current price, reference price and discount, bol Select price, who sells it (bol itself or
a marketplace partner), delivery promise and date, rating and number of reviews, full category path, image,
description, key specs, awards and whether the listing is a sponsored ad.

Switch on product pages and each row also gets the **EAN barcode, the complete specification table, the star
distribution, the review topics bol.com summarises, the seller rating, shipping cost and every product image**.

It is used for price monitoring and repricing on bol.com, assortment and competitor research for marketplace sellers,
EAN matching against your own catalogue, tracking which brands buy sponsored positions, and building product datasets
for the Dutch and Belgian market.

### At a glance

| What | Price |
|---|---|
| Product delivered from a search or category page | **US$ 1.90 per 1,000 products** (US$ 0.0019 each) |
| Detailed product (product page opened: EAN, full specs) | US$ 6.00 per 1,000 products (US$ 0.006 each, instead of the one above) |
| Run start | US$ 0.005 per run |

- 500 airfryers from one search = **US$ 0.96**. The same 500 with EAN and specifications = US$ 3.01.
- Error rows, duplicates, filtered products and skipped sponsored products are never charged. `maxItems` is a hard cap,
  and a spending limit set on the run stops it cleanly with everything delivered so far.
- The proxy is included in the price.

### Quick start

```json
{
  "searchTerms": ["airfryer"],
  "maxItems": 100
}
```

Cheapest first, Belgian store, no ads:

```json
{
  "searchTerms": ["lego", "robot vacuum"],
  "country": "be-nl",
  "sortBy": "price_asc",
  "skipSponsored": true,
  "maxItems": 200
}
```

A category with the full product page for each product (EAN and specifications):

```json
{
  "startUrls": [{ "url": "https://www.bol.com/nl/nl/l/airfryers/18336/" }],
  "scrapeDetails": true,
  "maxItems": 50
}
```

Product links work too: paste `https://www.bol.com/nl/nl/p/<name>/<id>/` and the Actor returns that product with every
detail. Search links keep any filter you set on bol.com (brand, price range, delivery time, rating).

### Input

| Field | What it does |
|---|---|
| `searchTerms` | Keywords, one per line. Each keyword is one bol.com search. Dutch or English both work (`airfryer`, `stofzuiger`, `iphone 16`). |
| `startUrls` | bol.com links: search pages (`/s/?searchtext=...`), category pages (`/l/<name>/<id>/`) or product pages (`/p/<name>/<id>/`), from `/nl/nl/`, `/be/nl/` or `/be/fr/`. |
| `country` | Store for the keyword searches: `nl` (default), `be-nl` or `be-fr`. Links keep their own store. |
| `sortBy` | `relevance` (default), `popularity`, `price_asc`, `price_desc`, `release_date`, `rating`, `wishlist`. The same options as the sort menu on bol.com. |
| `scrapeDetails` | Open each product page to add EAN, all specifications, star distribution, review topics, seller rating, shipping cost and all images (default off). |
| `skipSponsored` | Leave out sponsored products (default off). |
| `minPrice` / `maxPrice` | Keep only products in this price range, in EUR. |
| `onlyNew` | For schedules: skip products this search already delivered in earlier runs of your account. |
| `maxItems` | Cap for the whole run (default 50; 0 = no cap). |
| `proxyConfiguration` | Apify residential proxy in the Netherlands by default. bol.com refuses most other IPs. |

`searchQuery`, `keyword`, `queries`, `urls`, `maxResults` and `limit` are accepted too, so inputs written for other
bol.com tools keep working.

### Output

| Field | Description |
|---|---|
| `productId` | bol.com product id (the number at the end of the product link) |
| `title`, `url` | Product title and clean product link |
| `brand`, `brandUrl` | Brand name and its bol.com brand page |
| `price`, `currency` | Current price of the best offer, in EUR |
| `referencePrice`, `referencePriceType` | Price bol.com compares against (`Adviesprijs` = recommended retail price, `Meestal` = usual price) |
| `discountPercent` | Saving versus the reference price, in percent |
| `selectPrice`, `selectDiscountPercent` | Price and discount for bol Select members, when the product has one |
| `sellerName`, `sellerId`, `isSoldByBol` | Who sells the best offer: bol itself (`sellerId` 0) or a marketplace partner company |
| `availability` | `InStock` or the stock state bol.com publishes |
| `deliveryText`, `deliveryDate`, `deliveredWithin48Hours` | Delivery promise as shown on the site, the latest delivery date and the 48-hour flag |
| `isScarce`, `offerType` | Low-stock flag and offer type |
| `secondHandPrice` | Cheapest second-hand offer, when there is one |
| `rating`, `reviewCount` | Average stars (1 to 5) and number of reviews |
| `categories`, `mainCategory` | Every category path the product is listed in, and the main one |
| `imageUrl`, `description`, `keySpecs` | Main image, description as plain text, and the short spec highlights shown on the list |
| `awards`, `promotions` | Badges such as "Keuze van bol" or consumer test awards, and active deals, cashback or Select labels |
| `sustainabilityLabels` | Sustainability labels bol.com shows (for example PFAS-free) |
| `isSponsored`, `sponsoredBy` | Whether the row is a sponsored ad, and the advertiser company |
| `familyId`, `offersUrl` | Product family (colour/size variants) and the page with all offers |
| `position`, `page` | Rank of the product on the results page and the page number |
| `store`, `sourceQuery` | Store (`nl-nl`, `be-nl`, `be-fr`) and the keyword or link that found it |

With `scrapeDetails` (and for product links) each row also has:

| Field | Description |
|---|---|
| `ean` | EAN / GTIN barcode |
| `specifications` | Every specification as name: value (dimensions, weight, power, material, age, ...) |
| `descriptionFull` | Full description from the product page |
| `images` | URLs of all product images in the largest size |
| `ratingDistribution` | Number of reviews with 5, 4, 3, 2 and 1 stars |
| `reviewTopics` | Topics bol.com extracts from the reviews, with mentions, positive and negative counts and bol.com's summary |
| `sellerRating`, `sellerTopRetailer` | Marketplace seller score (out of 10) and top-retailer flag (empty when bol sells it) |
| `shippingCost`, `offerCount` | Shipping cost of the best offer and number of offers on the product |

#### Example (real row, 16 Oct 2026, trimmed)

```json
{
  "type": "product",
  "productId": "9300000233139234",
  "title": "Ninja Dubbele Airfryer XXL - 9.5 L - Nieuw 2026 Model - 40 tot 240 Graden - 2470 W Vermogen - Dual Zone Technologie - DZ400EU",
  "url": "https://www.bol.com/nl/nl/p/ninja-dubbele-airfryer-xxl-9-5-liter-twee-kookvakken-2470-watt-dual-zone-technologie-dz400eu/9300000233139234/",
  "brand": "Ninja",
  "price": 161.37,
  "currency": "EUR",
  "referencePrice": 229.99,
  "referencePriceType": "Adviesprijs",
  "discountPercent": 29.8,
  "sellerName": "bol",
  "isSoldByBol": true,
  "availability": "InStock",
  "deliveryText": "Op voorraad. Voor 23:59 uur besteld, maandag in huis",
  "deliveryDate": "2026-10-19",
  "rating": 4.7,
  "reviewCount": 801,
  "mainCategory": "Airfryers",
  "keySpecs": ["Max. bakcapaciteit 2800 g", "Receptenboek inbegrepen", "Inhoud: 9.5 l"],
  "isSponsored": true,
  "sponsoredBy": "Elmarc B.V.",
  "position": 1,
  "page": 1,
  "store": "nl-nl",
  "ean": "0622356322676",
  "specifications": { "Inhoud": "9.5 l", "Product hoogte": "32.50 cm", "Product gewicht": "12.4 kg" },
  "ratingDistribution": { "5": 651, "4": 109, "3": 20, "2": 12, "1": 9 },
  "reviewTopics": [
    { "topic": "schoonmaken", "mentions": 549, "positive": 548, "negative": 1,
      "summary": "Kopers zijn uiterst tevreden over het schoonmaakgemak; de bakken, roosters en anti-aanbaklaag laten zich vlot afwassen of kunnen in de vaatwasser." }
  ],
  "shippingCost": 0.0,
  "offerCount": 3,
  "scrapedAt": "2026-10-17T01:11:57Z"
}
```

Texts come in the language of the store (Dutch on `/nl/nl/` and `/be/nl/`, French on `/be/fr/`).

### Errors you may see

Every problem becomes one free row with `type: "error"`, an `errorCode` and a readable `error`, so the run never ends
with an empty dataset and no explanation.

| errorCode | Meaning |
|---|---|
| `INVALID_INPUT` | A field has a value the Actor cannot use (for example an unknown `sortBy`). |
| `INVALID_URL` | The link is not a bol.com search, category or product link. |
| `NO_RESULTS` | bol.com has no product for this keyword and filters. |
| `NOT_FOUND` | The product or category link no longer exists. |
| `BLOCKED` | bol.com refused every attempt for this source; running again a few minutes later usually works. |

### Integrations, scheduling and AI agents

- **Schedules:** run a search every morning with `onlyNew` on to receive only products that appeared since the last
  run, or without it to log prices day by day for repricing.
- **Export:** download the dataset as JSON, CSV, Excel or XML, or read it from the Apify API
  (`/datasets/{id}/items?format=csv`).
- **Make, n8n and Zapier:** start the Actor and pick up the rows with the official Apify integrations, then push them to
  Google Sheets, Airtable, a database or Slack.
- **Webhooks:** call your own endpoint when a run finishes.
- **AI agents:** the Actor works through the Apify MCP server, so Claude, ChatGPT and other MCP clients can call it to
  look up bol.com prices, sellers or EANs on demand.

### FAQ

**Do I need a bol.com account?** No. The Actor reads the same public pages any visitor sees.

**Why a residential proxy?** bol.com refuses most visitors from outside the Benelux, so the default proxy setting
uses Apify residential IPs in the Netherlands. The proxy is included in the price.

**How many products can one search return?** bol.com shows up to about 10 pages per keyword search (around 300
products, more for some categories). For more, split the search by category link or by price range with `minPrice`
and `maxPrice`.

**Are sponsored products included?** Yes, marked with `isSponsored: true` and the advertiser company in `sponsoredBy`.
Turn on `skipSponsored` to leave them out; skipped ads are not charged.

**Why do some products have no EAN?** The EAN is on the product page only. Turn on `scrapeDetails` or paste the
product links.

**Does it return reviewer names?** No. It returns review counts, the star distribution and the topic summaries bol.com
writes itself, never the name, age or region of the people who wrote the reviews.

**Can I monitor the Belgian store?** Yes, set `country` to `be-nl` or `be-fr`, or paste `/be/nl/` and `/be/fr/` links.

### Limitations

- Prices, delivery promises and sellers are the ones bol.com shows to a visitor in the Netherlands (or Belgium for
  `/be/` links) at the time of the run; Select prices are shown as published, without logging in.
- bol.com caps keyword searches at about 10 pages. Category links and price ranges help reach more of a large
  category.
- Only the best offer of each product comes with the list; the number of offers and the shipping cost need
  `scrapeDetails`.
- Text fields stay in the store's language; nothing is translated.
- The seller rating is empty when bol itself sells the product.

### Changelog

- **16 Oct 2026:** first version. Keyword, category and product links for bol.com NL and BE, seven sort orders,
  price filters, sponsored flag and filter, product pages with EAN and full specifications, `onlyNew` for schedules,
  free error rows.

# Actor input Schema

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

Products to search on bol.com, one per line, for example airfryer, robot vacuum or lego. Each keyword is one search. Leave empty when you paste links below.

## `startUrls` (type: `array`):

Search result pages (/s/?searchtext=...), category pages (/l/<name>/<id>/) or product pages (/p/<name>/<id>/) copied from bol.com, with any filter you applied on the site. Product links always return the full product page (EAN and specifications).

## `country` (type: `string`):

Which bol.com store the keyword searches run in. Prices, sellers and delivery can differ between the Dutch and the Belgian store. Links keep the store they point to.

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

Order of the results, the same options as the sort menu on bol.com.

## `scrapeDetails` (type: `boolean`):

Also opens every product page to add the EAN barcode, the full specification table, star distribution, review topics, seller rating, shipping cost and all image URLs. Charged as a detailed product instead of a list product.

## `skipSponsored` (type: `boolean`):

Leave out the sponsored (ad) products that bol.com mixes into the results. They are not charged.

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

Keep only products at or above this price. Filtered products are not charged.

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

Keep only products at or below this price. Filtered products are not charged.

## `onlyNew` (type: `boolean`):

For schedules: skip products this search already delivered in earlier runs of your account (kept in a named key-value store). Skipped products are not charged.

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

Maximum number of products in total, across all keywords and links. 0 = no limit (bol.com shows up to about 10 to 50 pages per search).

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

Apify residential proxy in the Netherlands by default. The proxy cost is included in the price.

## Actor input object example

```json
{
  "searchTerms": [
    "airfryer"
  ],
  "startUrls": [
    {
      "url": "https://www.bol.com/nl/nl/l/airfryers/18336/"
    }
  ],
  "country": "nl",
  "sortBy": "relevance",
  "scrapeDetails": false,
  "skipSponsored": false,
  "onlyNew": false,
  "maxItems": 50,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "NL"
  }
}
```

# Actor output Schema

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

No description

## `resultsCsv` (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 = {
    "searchTerms": [
        "airfryer"
    ],
    "startUrls": [
        {
            "url": "https://www.bol.com/nl/nl/l/airfryers/18336/"
        }
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "NL"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapewise/bol-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchTerms": ["airfryer"],
    "startUrls": [{ "url": "https://www.bol.com/nl/nl/l/airfryers/18336/" }],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "NL",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapewise/bol-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchTerms": [
    "airfryer"
  ],
  "startUrls": [
    {
      "url": "https://www.bol.com/nl/nl/l/airfryers/18336/"
    }
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "NL"
  }
}' |
apify call scrapewise/bol-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapewise/bol-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/5hcGulwvW5DPmuSPg/builds/mvAigkzRco3MlYcDg/openapi.json
