bol.com Scraper - Products, Prices, Offers, Reviews
Pricing
from $1.00 / 1,000 product scrapeds
bol.com Scraper - Products, Prices, Offers, Reviews
Scrape bol.com products: title, price, list price and discount, EAN, brand, images, condition, delivery, full specifications, ratings with distribution, individual reviews, every offer with seller name and rating, and refurbished prices. Search by keyword with filters, or paste product URLs.
Pricing
from $1.00 / 1,000 product scrapeds
Rating
5.0
(1)
Developer
Abot API
Maintained by CommunityActor stats
2
Bookmarked
112
Total users
48
Monthly active users
4 days ago
Last modified
Categories
Share
Bol.com Scraper: Products, Prices, Offers and Reviews
Bol.com Scraper turns Bol.com, the largest online store in the Netherlands and Belgium, into a clean product data feed. Get price, list price and discount, brand, EAN, full specifications, images, condition, delivery estimate, rating with the full review distribution, individual reviews, every seller offer, and refurbished or second-hand pricing. Search by keyword with price, rating, condition and sort filters, or paste product and result-page links, then export to JSON, CSV or Excel, or pull the results straight into your app through the API.
Why This Scraper?
- One record, most of what you need. Every product carries price, list price, discount, currency, brand, category path, condition, delivery estimate, rating summary, image and seller without turning anything on.
- Full detail on demand. Turn on Fetch full product details to add the complete specification list (including EAN), the full description, every product image, the full review list with star distribution, seller rating, product weight, and refurbished or second-hand offers.
- Six filters before anything is collected. Price band, minimum rating, condition, sponsored-only and in-stock-only narrow results before they are scraped or charged, on top of six sort orders.
- Two ways in. Search mode builds queries from keywords, or paste product and search-result URLs directly in URL mode.
- Bol.com's own deal pages. An optional specials source adds the storewide deals page or the clearance page alongside your keywords, and each product carries real was-price, savings and promo-label fields straight from that listing.
- One finite cap. Max items bounds a run's size and cost; page depth is unlimited unless you set it.
- Built for schedules. Incremental mode returns only new, updated and reappeared products on repeat runs, and Resume continues one interrupted crawl without re-collecting or re-paying for what it already has.
Use Cases
- Price trackers and deal hunters: watch prices, discounts and Bol.com's own specials pages on a recurring schedule.
- E-commerce and marketplace analysts: compare pricing, ratings and seller performance across categories.
- Retail catalog teams: pull full specifications, EAN codes and every product image to enrich a catalog.
- Review and reputation research: collect the full review list and star distribution per product.
- Resellers and price-comparison tools: track refurbished and second-hand offers alongside new-condition pricing.
Data You Get
Sample shape: values are illustrative placeholders, not from a live listing.
| Field | Example |
|---|---|
productId | "9300000012345" |
title | "Voorbeeld Draadloze Hoofdtelefoon met Noise Cancelling, Zwart" |
brand | "Voorbeeldmerk" |
price / currency | 279.0 / "EUR" |
list_price / discount_percentage | 349.99 / 20 |
condition | "new" |
ean | "0000000000000" |
category / categoryHierarchy | "Koptelefoons" / ["Elektronica", "Audio", "Koptelefoons"] |
rating / reviewCount | 4.6 / 812 |
ratingDistribution | {"fiveStar": 600, "fourStar": 150, "threeStar": 40, "twoStar": 12, "oneStar": 10} |
image / images | product photo URL(s) |
availability / delivery | "Voor 23:59 uur besteld, morgen in huis" (both carry the delivery estimate) |
seller | {"id": "0000000", "name": "Voorbeeldverkoper", "rating": 9.2, "topSeller": true} |
weight | "254 g" (when full details are on) |
sponsored | false |
offers | every selling offer: price, condition and seller (when full details are on) |
specifications | full spec list, group and name/value pairs incl. EAN (when full details are on) |
reviews | individual reviews with rating, text, nickname and verified flag (when full details are on) |
isOnSpecial / savingsAmount / savingsPercent / promoLabel / specialsCategory | Bol.com's own was-price, savings and promo-badge fields |
has_refurbished / in_stock | false / true |
url | link to the product page |
In incremental mode, every returned record also carries changeType, changedFields, firstSeenAt and lastSeenAt; these four fields are absent from a normal run.
How to Use
- Pick a mode:
search(keyword and filters) orurl(paste product or result-page links). - In search mode, add one or more keywords and, optionally, narrow with the price, rating, condition, sponsored and in-stock filters and a sort order. In URL mode, paste Bol.com product or result-page links; the same filters still apply.
- Turn on Fetch full product details to add specifications, EAN, full reviews, seller rating, weight and refurbished offers, and set Max items to bound the run.
- Click Start, then download the dataset as JSON, CSV or Excel, or read it through the API.
Search by keyword with a price ceiling:
{"mode": "search","searchTerms": ["laptop"],"sortBy": "price_asc","maxPrice": 400,"maxListings": 50}
Search with full product detail enrichment:
{"mode": "search","searchTerms": ["koffiezetapparaat"],"fetchDetails": true,"maxReviews": 20,"minRating": 4,"maxListings": 30}
Bol.com's own deals page as an extra source:
{"mode": "search","searchTerms": [],"specialsCategory": "aanbiedingen","maxListings": 50}
Scrape pasted URLs (product pages and result pages):
{"mode": "url","urls": ["https://www.bol.com/nl/nl/p/example-laptop/9300000000000/","https://www.bol.com/nl/nl/s/?searchtext=boek"],"maxListings": 100}
Supported URL shapes
In URL mode, leaving urls untouched now scrapes one example of each shape below (earlier versions scraped nothing). Set your own list to scrape only what you need.
| URL shape | Example | Result |
|---|---|---|
| Keyword search | https://www.bol.com/nl/nl/s/?searchtext=laptop | listed products |
| Search, page N | .../s/?searchtext=laptop&page=2 | starts at that page (never marks products EXPIRED) |
| Search, sorted | .../s/?searchtext=laptop&sort=price&sortOrder=asc&page=2 | sort read from the URL |
| Search slug | https://www.bol.com/nl/nl/s/laptop/ | keyword taken from the slug |
| Search data link | https://www.bol.com/nl/nl/s/.data?searchtext=laptop&page=1 | same as keyword search |
| Product | https://www.bol.com/nl/nl/p/<slug>/<id>/ | full detail record, matched on the numeric id |
| Product with tracking parameters | .../<id>/?bltgh=abc&promo=x | same as product |
| Category | https://www.bol.com/nl/nl/l/laptops/4770/ | listed products |
| Brand | https://www.bol.com/nl/nl/b/acemagic/607284936/ | listed products |
| Deals | https://www.bol.com/nl/nl/l/alle-artikelen-aanbiedingen/58334/ | listed products |
| Outlet | https://www.bol.com/nl/nl/ra/outlet/392102/ (also ?page=2) | listed products |
| Older search link | https://www.bol.com/nl/s/zoekresultaten.html?searchtext=laptop | keyword read from the link |
| Mobile host | https://m.bol.com/nl/nl/s/?searchtext=laptop | treated as www.bol.com |
Any other bol.com link is skipped with a warning in the log.
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/bol-com-scraper").call(run_input={"mode": "search", "searchTerms": ["laptop"], "maxListings": 20})for product in client.dataset(run["defaultDatasetId"]).iterate_items():print(product["title"], product["price"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/bol-com-scraper').call({ mode: 'search', searchTerms: ['laptop'], maxListings: 20 });const { items } = await client.dataset(run.defaultDatasetId).listItems();
Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.
Bol.com's specials pages and was-price fields
Set specialsCategory to aanbiedingen (Bol.com's storewide deals page) or outlet (its clearance page) to add that page as an extra product source alongside, or instead of, your search keywords. Every product, whichever source it came from, carries isOnSpecial, originalPrice, savingsAmount, savingsPercent and promoLabel: these are only ever set from Bol.com's own reference price and promo badge on the listing, never estimated. originalPrice, savingsAmount and savingsPercent are null when there is no real reduction, promoLabel is null when there is no badge, and isOnSpecial is true when either is present. They never change or gate any other field, and they add no extra cost since the data comes from the same listing page the actor already fetches.
Resume and recurring updates
- Resume (
resumeFromRunId) continues one specific interrupted run: paste its run or dataset ID and this run skips products already collected there, so you don't pay twice. - Incremental mode (
incrementalMode) is for scheduled runs of the same search. The actor remembers the previous run itself, so you never paste an ID. The first run returns everything asNEW; later runs return onlyNEW,UPDATED(withchangedFields) andREAPPEAREDproducts by default, while unchanged products are suppressed and not billed unlessemitUnchangedis on.EXPIREDproducts (previously tracked, no longer found) are only produced after a run that scanned the whole search, and only withemitExpiredon.stateKeynames or shares one monitoring campaign; leave it empty and the actor derives a key from the search and filter setup automatically. - What counts as a change. A product is
UPDATEDwhen a tracked field moves: price, list price, discount, availability and delivery, condition, title, offers, seller, specifications, description or categories. Reviews and every rating or review counter, the rotating image host, URL tracking parameters and the order of offers never make a productUPDATED; they stay in the output. Add more field names toignoreFieldsForChanges(title: "Ignore fields when detecting changes (incremental mode only)") to exclude them too. Changing that list does not trigger a one-time wave ofUPDATEDrows. EXPIREDis never produced from a partial scope: a pasted result URL that starts at?page=2or later, a run stopped by Max items or Max pages, or a failed page.- Turning on Incremental mode together with Resume is only allowed to bootstrap a search's very first incremental baseline. Once that search already has saved incremental state, combining the two stops the run with a message asking you to drop Resume or pick a different
stateKey.
Output views
The dataset has four views in the Apify Console: Products (core listing fields), Product details (specifications, EAN, offers, images; filled when Fetch full product details is on), Reviews (one row per review, unwound from each product) and Changes (incremental) (change type, changed fields and timestamps).
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.
- Authorize a connector once under Apify β Settings β Integrations (Notion, Linear, Airtable, or Apify).
- Select it in the "Export to your apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
- For Notion, also set
notionParentPageUrlto 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 filters) or url (paste product or result URLs). |
searchTerms | array | [] | Keywords to search. Search mode only. |
specialsCategory | string | "" | Optional: add aanbiedingen (storewide deals page) or outlet (clearance page) as an extra source alongside your search keywords. Off by default. |
sortBy | string | relevance | relevance, popularity, price_asc, price_desc, rating, or newest. Applies in both search and URL mode. |
minPrice | integer | (none) | Keep products at or above this price (EUR). |
maxPrice | integer | (none) | Keep products at or below this price (EUR). |
minRating | integer | (none) | Keep products with an average rating at or above this value (1-5). |
condition | string | any | any, new, secondhand, or refurbished. Keeps only products whose best offer matches. |
sponsoredOnly | boolean | false | Keep only sponsored (advertised) products. |
inStockOnly | boolean | false | Keep only products with a buyable offer. |
productIds | array | [] | Look up specific products directly: bol.com product ids (16 digits), EAN/GTIN barcodes (8, 12, 13 or 14 digits), or product page URLs. Works in either mode and alongside keywords; each entry returns one product. A bol.com id opens that exact product page and returns nothing if it no longer exists; an EAN returns the top search hit for that barcode. A bol.com id or product URL always returns full detail fields; the detail enrichment event is billed for it only when Fetch full product details is on. |
includeSponsored | boolean | true | Keep sponsored (advertised) listings. Turn off to drop them. Ignored when sponsoredOnly is on. |
urls | array | one example per shape (see below) | Product, search, category, brand, deals or outlet URLs to scrape. Belgian search links (/be/nl/s/, /be/fr/s/) are scraped on the Belgian storefront with country BE; Belgian category, brand and outlet links are skipped with a warning. URL mode only; the price, rating, condition, sponsored and in-stock filters still apply. A product link always returns full detail fields; the detail enrichment event is billed for it only when Fetch full product details is on. |
fetchDetails | boolean | false | Adds full specifications (incl. EAN), full description, every image, full reviews with rating distribution, seller rating, weight, and refurbished/second-hand offers. |
maxReviews | integer | 30 | Maximum individual reviews captured per product when full details are on (maximum 1000). Bol.com's product page serves only its first slice of reviews (about 6 per product in testing; a product with 11 reviews returned 6) and exposes no way to page further, so fewer reviews than the cap may come back. |
maxPages | integer | (none) | Safety bound on result pages walked per keyword. Empty or 0 is unlimited; this does not cap the number of products. |
totalPages | integer | (none) | Same as maxPages, kept for compatibility with other bol.com scrapers. maxPages wins when both are set. |
maxListings | integer | 20 | Maximum products for the whole run. The single finite cap. 0 is unlimited. |
resumeFromRunId | string | (none) | Continue one previous interrupted run or crawl, skipping products it already collected. |
incrementalMode | boolean | false | Turn on for daily or recurring monitoring: later runs return only new, updated and reappeared products. |
stateKey | string | (none) | Name or share an incremental-mode monitoring campaign. Auto-derived from the search and filter setup when left empty. |
emitUnchanged | boolean | false | Incremental mode only. Also return, and bill for, products unchanged since the last run. |
emitExpired | boolean | false | Incremental mode only. Also return, and bill for, products no longer found after a complete scan. |
ignoreFieldsForChanges | array | [] | Incremental mode only. Top-level field names that never make a product UPDATED or appear in changedFields (still returned in the output). |
proxy | object | {"useApifyProxy": true} | Proxy configuration. Keep Apify Proxy enabled with a Netherlands exit for the most complete results. |
residentialCountries | array | [] | Preferred exit countries. Falls back to Netherlands, then Belgium, Germany, France and the United Kingdom when left empty. |
backupProxyUrl | string | (none) | Optional backup proxy gateway used when full Apify Proxy access is unavailable on your plan. |
maxResidentialRequests | integer | (none) | Cost guard limiting how many requests a run may make before switching to the backup gateway. 0 is unlimited; falls back to 5000 when left empty. |
trafficBudgetMb | integer | (none) | Soft transfer budget in MB. When exceeded, the run switches to the backup gateway. 0 is unlimited; falls back to 1500 when left empty. |
preferDatacenter | boolean | false | No effect: Bol.com refuses datacenter connections, so the actor always uses a residential connection. Kept so saved tasks keep working. |
autoDowngradeProxy | boolean | true | No effect: Bol.com refuses datacenter connections, so the actor always uses a residential connection. Kept so saved tasks keep working. |
mcpConnectors | array | [] | Optional connected apps (Notion, Linear, Airtable, Apify) to also receive each product. |
notionParentPageUrl | string | (none) | When a Notion connector is selected, create one page per product under this parent page. |
maxNotifyListings | integer | (none) | Optional cap on how many products are pushed to the selected connectors. Falls back to 50 when left empty. |
Output Example
Sample shape: values are illustrative placeholders, not from a live listing.
{"productId": "9300000012345","title": "Voorbeeld Draadloze Hoofdtelefoon met Noise Cancelling, Zwart","brand": "Voorbeeldmerk","price": 279.0,"list_price": 349.99,"discount_percentage": 20,"currency": "EUR","condition": "new","ean": "0000000000000","category": "Koptelefoons","categoryHierarchy": ["Elektronica", "Audio", "Koptelefoons"],"rating": 4.6,"reviewCount": 812,"ratingDistribution": {"fiveStar": 600, "fourStar": 150, "threeStar": 40, "twoStar": 12, "oneStar": 10},"reviews": [{"rating": 5, "title": "Prima aankoop", "text": "Snel geleverd en werkt goed.", "nickname": "Klant2024", "location": "Nederland", "verified": true}],"image": "https://media.s-bol.com/0000/image-550x461.jpg","images": ["https://media.s-bol.com/0000/image-550x461.jpg"],"availability": "Voor 23:59 uur besteld, morgen in huis","delivery": "Voor 23:59 uur besteld, morgen in huis","offers": [{"price": 279.0, "currency": "EUR", "condition": "new", "seller": {"id": "0000000", "name": "Voorbeeldverkoper", "rating": 9.2, "topSeller": true}}],"seller": {"id": "0000000", "name": "Voorbeeldverkoper", "rating": 9.2, "topSeller": true},"specifications": [{"group": "Algemeen", "name": "EAN", "value": "0000000000000"},{"group": "Algemeen", "name": "Kleur", "value": "Zwart"}],"description": "Voorbeeldbeschrijving van dit product voor studie, werk of thuisgebruik.","weight": "254 g","weightGrams": 254,"sponsored": false,"has_refurbished": false,"in_stock": true,"isOnSpecial": true,"originalPrice": 349.99,"savingsAmount": 70.99,"savingsPercent": 20,"promoLabel": "deal","specialsCategory": null,"products_found": 4210,"search_query": "koptelefoon","country": "NL","url": "https://www.bol.com/nl/nl/p/voorbeeld-hoofdtelefoon/9300000012345/","scrapedAt": "2026-01-01T00:00:00+00:00"}
Plan Requirement
Residential proxies are required. Bol.com refuses datacenter connections outright: in testing, Apify datacenter proxies (automatic and datacenter groups) and a plain home connection all received an HTTP 403 "access blocked" page, while a Netherlands residential exit returned the full results. Keep Apify Proxy enabled and leave the connection on its default. A selected datacenter group is replaced by residential (billed to your plan), because Bol.com refuses datacenter connections; the actor uses a Netherlands residential exit first, then Belgium, Germany, France and the United Kingdom (change the order with residentialCountries). Residential exits are also uneven: an occasional exit is refused for a valid page, and the actor simply rolls to a fresh exit and retries.
Apify free plans do not include residential proxies. On a free plan the run logs this warning and continues on the backup connection (backupProxyUrl, or the fallback gateway), where some or all pages may stay unavailable:
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. For residential access, upgrade at https://apify.com/pricing or supply your own proxy URLs.
When pages do load through the backup, the log says: "Served over the backup connection (the default connection was unavailable)." If nothing could be read, the run ends with one of these messages instead of a silent empty result:
- Proxy refused for the account: "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: https://apify.com/pricing. If you entered your own proxy URLs, check that their credentials are valid."
- Residential unavailable, backup could not load pages: "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested bol.com pages. Upgrade at https://apify.com/pricing or supply your own proxy URLs, then try again."
- Requests failed: "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later."
preferDatacenter and autoDowngradeProxy have no effect, because Bol.com refuses datacenter connections. maxResidentialRequests and trafficBudgetMb bound how much a run uses before it moves to the backup gateway.
FAQ
How much does it cost?
Pay-per-event pricing; the current prices for every plan are on the Pricing tab. The events are:
| Event | When |
|---|---|
| Actor Start | once per run |
| Product scraped | once per product returned to the dataset |
| Product detail enrichment | once per product pushed with its detail page fetched, only when Fetch full product details is on (reviews included); pasted product URLs and bol.com ids in Product IDs always read the detail page for full fields, and with Fetch full product details off they are billed only as Product scraped |
Reviews ride the detail enrichment event; there is no separate review charge. Max items caps how large any run can get.
Is it legal to scrape Bol.com?
This actor collects only publicly available product listings. You are responsible for how you use the data: follow Bol.com's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Prices and specifications are generally factual, but images, descriptions and 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, updated and reappeared products, and unchanged ones are not billed unless Emit unchanged is on.
What's the difference between Resume and Incremental mode?
This is the most common mix-up. Resume (resumeFromRunId) continues one specific interrupted run by skipping products it already collected. Incremental mode remembers the previous run of the same search by itself, for recurring scheduled runs, so you never paste an ID. The two can be combined only to bootstrap a search's very first incremental baseline from a resumed dataset; combining them again once that search already has saved incremental state stops the run with a message telling you to drop Resume or set a different stateKey.
Why did my run fail instead of returning an empty dataset?
A run fails when nothing could be read because of the connection: the proxy was refused for your account, residential proxies are unavailable and the backup connection could not load pages, or every request failed. The failure message is one of the three quoted under Plan Requirement, and nothing is charged for pages that never loaded. A run also fails when resumeFromRunId points to a run or dataset it cannot read, or when Incremental mode is combined with Resume while that search already has saved incremental state. A keyword that genuinely matches nothing still completes with an empty dataset and a log note.
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 also push each product to Notion, Linear or Airtable.
π Want more classifieds data?
Pair this actor with these related scrapers from the same team:
| π·οΈ Vinted.fr Scrape Vinted.fr, France's C2C second-hand fashion marketplace: search with... | π·οΈ Chrono24 Scraper Scrape Chrono24 luxury watch listings by keyword, filters or URL. Extract 50+ fields... |
| π·οΈ Daangn Scraper Scrape Daangn (λΉκ·Ό) marketplace listings by keyword and region. Returns 35+ fields per... | π·οΈ Gumtree.com Scraper Scrape Gumtree.com classifieds across motors, property, jobs, services, pets and goods... |
| π·οΈ 2dehands & 2ememain Scrape classifieds from 2dehands.be and 2ememain.be by keyword, filters, or URLs. Returns... | π·οΈ Wildberries Scraper Scrape Wildberries marketplace: product search with prices (wallet vs retail), discounts... |
π Browse all abotapi scrapers
π¬ Support
- π Found a bug or a missing field? Open a ticket on the Issues tab. 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.