# Bol Reviews Scraper (`codingfrontend/bol-reviews-scraper`) Actor

Extract detailed customer reviews from Bol.com, Netherlands' largest e-commerce platform with 13.6 million users. Get structured review data for market research.

- **URL**: https://apify.com/codingfrontend/bol-reviews-scraper.md
- **Developed by:** [Coding Frontned](https://apify.com/codingfrontend) (community)
- **Stats:** 6 total users, 6 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.99 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

### What this Actor does

This Actor extracts publicly visible customer reviews and product-level rating information from Bol.com product pages and search pages for the Netherlands and Belgium. It is useful for bounded review research, product quality comparisons, sentiment samples, and rating snapshots.

The Actor uses direct HTTPS requests to public `bol.com` pages, parses Product JSON-LD and review markup, and follows a bounded number of public product links from search pages. It does not sign in, submit forms, solve CAPTCHAs, bypass WAFs, use stealth or fingerprint evasion, or access private customer data. A blocked or unusable response becomes an explicitly typed diagnostic row when diagnostics are enabled.

### Input

All inputs are optional. Defaults preserve the original Actor behavior: query `coffee`, one generated search URL, `maxProducts: 5`, `maxReviews: 25`, 30-second timeouts, one transient retry, and sitemap evidence enabled.

#### Search and traversal

- `query` — search term used when no source URL is supplied; default `coffee`.
- `searchQuery` and `searchKeywords` — compatibility aliases for `query`.
- `startUrls` — up to 20 public Bol.com search or product URLs. String URLs are supported for backward compatibility; request-style `{ "url": "..." }` values are also accepted by the runtime.
- `sourceUrls` and `urls` — compatibility aliases for `startUrls`.
- `url` — compatibility alias for one source URL.
- `startPage` — first 1-based search page, from 1 to 100; default `1`.
- `maxPages` — maximum search pages per source, from 1 to 10; default `1`. Product URLs are fetched once.
- `maxProducts` — maximum product links followed from each search page, from 1 to 25; default `5`.
- `maxReviews` — global maximum individual review rows, from 1 to 200; default `25`.

Search pagination preserves the source query and sets the observed `page` parameter. Sources and pages are processed in order, repeated URLs are skipped, and the Actor stops when the review limit or page bound is reached.

#### Requests and fallbacks

- `timeoutSecs` — per-page timeout from 5 to 120 seconds; default `30`.
- `maxRetries` — bounded retries for transient statuses and timeouts, from 0 to 2; default `1`. Access-control responses are not retried indefinitely.
- `useSitemapFallback` — when a page is blocked, collect bounded public sitemap URL evidence in `OUTPUT`; default `true`. Sitemap URLs are provenance counters only and are never emitted as review records.
- `includeDiagnostics` — emit blocked, failed, and empty-response diagnostics; default `true`.

#### Local deterministic QA

- `fixture` — local-only `{ "url": "...", "body": "..." }` response fixture.
- `fixtureFile` — one repository-relative HTML fixture.
- `fixtureFiles` — up to 10 repository-relative fixtures consumed in page order.

Fixture content is limited to 4 MiB. Paths must stay inside the Actor directory and outside `node_modules`. Fixtures are used only when explicitly supplied and never silently replace a failed live response.

### Output

#### Review and summary records

Normal rows contain only consumer-facing review, product, and provenance fields. Individual review rows are identified by their review fields such as `reviewText`, `reviewTitle`, `rating`, `author`, and `reviewDate`. A product summary row has `reviewsFound: 0` and may contain `averageRating` and `reviewCount`.

Possible normal fields are deterministic `reviewId` for individual reviews, `productId`, `productTitle`, `productUrl`, `productImageUrl`, `brand`, `price`, `currency`, `averageRating`, `reviewCount`, `reviewsFound`, `reviewTitle`, `reviewText`, `rating`, `author`, `reviewDate`, `verifiedPurchase`, `source`, `sourceUrl`, `query`, `sourceDomain`, and `scrapedAt`.

Normal dataset rows do not contain request instrumentation such as `recordType`, `httpStatus`, `fetchStatus`, `elapsedMs`, `attempts`, actor metadata, or retry/debug wrappers. Diagnostic rows use exactly `recordType: "diagnostic"`, `diagnosticType`, `url`, `error`, `errorCode`, and `scrapedAt`.

#### Diagnostic records

When `includeDiagnostics` is `true`, a blocked or unusable response produces these seven typed fields:

```json
{
  "url": "https://www.bol.com/nl/nl/p/example-product/9300000000000000/",
  "recordType": "diagnostic",
  "diagnosticType": "access_blocked",
  "sourceUrl": "https://www.bol.com/nl/nl/p/example-product/9300000000000000/",
  "error": "Bol returned an access-control or challenge page; no review data was collected.",
  "errorCode": "ACCESS_BLOCKED",
  "scrapedAt": "2026-09-10T08:47:00.737Z"
}
```

Diagnostic codes include `ACCESS_BLOCKED`, `REDIRECT_BLOCKED`, `HTTP_ERROR`, `REQUEST_FAILED`, and `NO_DATA`.

#### Key-value summary

The `OUTPUT` and compatibility-alias `OUTPUT_SUMMARY` key-value records contain `status`, `runStatus`, `requestedUrls`, `pagesFetched`, `productsSeen`, `reviewRecords`, `summaryRecords`, `blocked`, `errors`, `diagnostics`, `droppedRecordCount`, `dropReasons`, `sitemapRequests`, `sitemapFallbacks`, `sitemapProductsDiscovered`, and `completedAt`. Operational counters stay here instead of entering dataset rows.

### Examples

#### Generated search

```json
{
  "query": "coffee",
  "maxProducts": 5,
  "maxReviews": 25,
  "includeDiagnostics": true
}
```

#### Direct product URL

```json
{
  "url": "https://www.bol.com/nl/nl/p/example-product/9300000000000000/",
  "maxReviews": 10,
  "useSitemapFallback": false
}
```

#### Deterministic local verification

```json
{
  "query": "coffee",
  "maxReviews": 25,
  "fixtureFile": "test/fixtures/synthetic-bol-review.html"
}
```

### Cost and performance

Runtime grows with the number of search pages, followed product pages, retries, and sitemap requests. Defaults are bounded at one page per source, five product pages per search page, 25 review rows, a 30-second request timeout, and one retry. Increase limits only when the additional public requests are justified.

### Responsible use and limitations

Use only publicly accessible data and comply with Bol.com’s terms, robots guidance, copyright, privacy, and applicable laws. Do not use the Actor to collect private customer data or defeat access controls. Direct HTTPS requests may be blocked by Bol.com or the execution environment; a diagnostic is an honest result, not a workaround. No proxy, browser automation, CAPTCHA solving, or stealth transport is provided.

You can download the dataset in various formats such as JSON, HTML, CSV, or Excel.

# Actor input Schema

## `query` (type: `string`):

Search term used when no source URL is supplied.

## `searchQuery` (type: `string`):

Compatibility alias for query; do not provide conflicting query aliases.

## `searchKeywords` (type: `string`):

Compatibility alias for query; do not provide conflicting query aliases.

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

Public Bol.com product or search URLs. Product links found on search pages are followed up to maxProducts.

## `sourceUrls` (type: `array`):

Compatibility alias for startUrls; provide one source-list alias.

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

Compatibility alias for startUrls; provide one source-list alias.

## `url` (type: `string`):

Compatibility alias for one public Bol.com search or product URL.

## `startPage` (type: `integer`):

1-based first search page; product URLs are fetched once.

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

Maximum search pages fetched per source.

## `maxProducts` (type: `integer`):

Maximum product links followed from each search page.

## `maxReviews` (type: `integer`):

Maximum individual review records written to the dataset.

## `timeoutSecs` (type: `integer`):

Per-page public request timeout.

## `maxRetries` (type: `integer`):

Bounded retries for transient statuses and timeouts; access-control responses are not retried indefinitely.

## `useSitemapFallback` (type: `boolean`):

When a page is blocked, collect bounded public sitemap URL evidence in OUTPUT without claiming reviews or bypassing controls.

## `includeDiagnostics` (type: `boolean`):

Emit typed diagnostic rows for blocked, failed, and empty public responses.

## Actor input object example

```json
{
  "query": "coffee",
  "startUrls": [
    "https://www.bol.com/nl/nl/s/?searchtext=coffee"
  ],
  "startPage": 1,
  "maxPages": 1,
  "maxProducts": 5,
  "maxReviews": 25,
  "timeoutSecs": 30,
  "maxRetries": 1,
  "useSitemapFallback": true,
  "includeDiagnostics": true
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset containing public-source records.

## `keyValueStore` (type: `string`):

Key-value store containing the OUTPUT run summary.

# 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 = {
    "query": "coffee",
    "startUrls": [
        "https://www.bol.com/nl/nl/s/?searchtext=coffee"
    ],
    "maxProducts": 5,
    "maxReviews": 25,
    "timeoutSecs": 30,
    "maxRetries": 1,
    "useSitemapFallback": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("codingfrontend/bol-reviews-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 = {
    "query": "coffee",
    "startUrls": ["https://www.bol.com/nl/nl/s/?searchtext=coffee"],
    "maxProducts": 5,
    "maxReviews": 25,
    "timeoutSecs": 30,
    "maxRetries": 1,
    "useSitemapFallback": True,
}

# Run the Actor and wait for it to finish
run = client.actor("codingfrontend/bol-reviews-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 '{
  "query": "coffee",
  "startUrls": [
    "https://www.bol.com/nl/nl/s/?searchtext=coffee"
  ],
  "maxProducts": 5,
  "maxReviews": 25,
  "timeoutSecs": 30,
  "maxRetries": 1,
  "useSitemapFallback": true
}' |
apify call codingfrontend/bol-reviews-scraper --silent --output-dataset

```

## MCP server setup

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