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
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
0
Bookmarked
28
Total users
13
Monthly active users
3 days ago
Last modified
Categories
Share
bol.com Scraper
Scrape products from bol.com, the largest online store in the Netherlands and Belgium. Search by keyword with price, rating, condition and sort filters, or paste product and result URLs. Every record carries the title, price, list price and discount, brand, EAN, images, condition, delivery estimate, rating with full review distribution, individual reviews, all offers with seller name and rating, and refurbished and second-hand prices.
Why This Scraper?
- One product record carries price, list price, discount, currency, brand, category path, condition, delivery, rating summary, image and seller.
- Turn on full details to add the complete specification list (incl. EAN), the full description, every product image, the full review list with star distribution, seller rating, product weight, and refurbished and second-hand offers.
- Search-mode filters for price band, minimum rating, condition, sponsored-only and in-stock-only, plus six sort orders, narrow results before anything is collected.
- URL mode accepts both product pages and result pages.
- One finite cap (Max items) keeps the run bounded; page depth is unlimited by default.
- Optional export of each product to Notion, Airtable, Linear or Apify through MCP connectors, in addition to the dataset.
Data You Get
Sample shape - values are illustrative placeholders, not from a live listing.
| Field | Example |
|---|---|
| productId | 9300000000000 |
| title | Example 15.6 Inch Laptop - 16GB RAM - 512GB SSD |
| brand | ExampleBrand |
| price | 329.5 |
| list_price | 399 |
| discount_percentage | 17 |
| currency | EUR |
| condition | new |
| ean | 0000000000000 |
| category | Windows laptops |
| categoryHierarchy | ["Computer", "Laptops", "Windows laptops"] |
| rating | 4.3 |
| reviewCount | 15 |
| ratingDistribution | {"fiveStar": 11, "fourStar": 0, "threeStar": 2, "twoStar": 1, "oneStar": 1} |
| image | https://media.example.com/0000/image-550x461.jpg |
| availability | In stock |
| delivery | Delivered by tomorrow |
| seller | {"id": "0000000", "name": "Example Store", "rating": 8.1} |
| weight | 2.2 kg |
| sponsored | false |
| url | https://www.bol.com/nl/nl/p/example-laptop/9300000000000/ |
How to Use
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}
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}
Input Parameters
| Parameter | Type | Description |
|---|---|---|
| mode | select | search (keyword + filters) or url (paste product or result URLs). |
| searchTerms | array | Keywords to search. Search mode only. |
| sortBy | select | Best match, Most popular, Price low to high, Price high to low, Highest rated, or Newest. Applies in both search and URL mode. |
| minPrice | integer | Keep products at or above this price (EUR). Applies in both search and URL mode. |
| maxPrice | integer | Keep products at or below this price (EUR). Applies in both search and URL mode. |
| minRating | integer | Keep products with an average rating at or above this value (1-5). Applies in both search and URL mode. |
| condition | select | Any, New, Second-hand, or Refurbished. Applies in both search and URL mode. |
| sponsoredOnly | boolean | Keep only sponsored (advertised) products. Applies in both search and URL mode. |
| inStockOnly | boolean | Keep only products with a buyable offer. Applies in both search and URL mode. |
| specialsCategory | select | Optional. Add aanbiedingen (bol.com's storewide deals page) or outlet (bol.com's clearance page) as an extra source alongside your search keywords. Off by default. |
| urls | array | Product or result URLs to scrape. URL mode only; filter fields are ignored. |
| fetchDetails | boolean | Adds full specifications, EAN, description, all images, full reviews, seller rating, weight and refurbished offers. |
| maxReviews | integer | Maximum individual reviews captured per product when details are on. |
| maxPages | integer | Safety bound on result pages per keyword. Empty or 0 = unlimited; does not cap product count. |
| maxListings | integer | Maximum products for the whole run. The single finite cap. 0 = unlimited. Default 20. |
| resumeFromRunId | string | Continue one specific interrupted or previous large crawl; skips products already collected by that run/dataset. See Resume & recurring updates. |
| incrementalMode | boolean | Recurring/scheduled monitoring: returns only NEW/UPDATED/REAPPEARED products against the actor's own remembered state for this search. Off by default. |
| stateKey | string | Optional. Name or share an incremental-mode monitoring campaign. Auto-derived from the search/URL/filter setup when left empty. |
| emitUnchanged | boolean | Incremental mode only. Also return (and bill for) products with no change since the last run. Off by default. |
| emitExpired | boolean | Incremental mode only. Also return (and bill for) products no longer found after a complete scan. Off by default. |
| proxy | object | Proxy configuration. Keep Apify Proxy enabled with a Netherlands exit. |
| residentialCountries | array | Preferred exit countries. Defaults to NL then BE, DE, FR, GB. |
| backupProxyUrl | string | Optional backup proxy gateway for plans without full Apify Proxy access. |
| maxResidentialRequests | integer | Cost guard limiting requests per run before switching to the backup gateway. 0 = unlimited. Default 5000. |
| trafficBudgetMb | integer | Soft transfer budget in MB; switches to the backup gateway when exceeded. 0 = unlimited. Default 1500. |
| preferDatacenter | boolean | Cost-saving connection preference. Off by default. |
| autoDowngradeProxy | boolean | Cost-saving connection preference. On by default. |
| mcpConnectors | array | Optional connected apps (Notion, Airtable, Linear, Apify) to also receive each product. |
| notionParentPageUrl | string | When a Notion connector is selected, create one page per product under this parent page. |
| maxNotifyListings | integer | Optional cap on how many products are pushed to the selected connectors. |
Export to your apps (MCP connectors)
Optionally pipe results into the apps you already use through Model Context Protocol (MCP) connectors. Authorize a connector once under Apify then Settings then Integrations, select it in mcpConnectors, and each scraped product is also sent to that app as a condensed, human-readable summary. The full record always stays in the dataset. Supported connectors: Notion, Linear, Airtable and Apify. Leave mcpConnectors empty to write only to the dataset.
Specials & Was-Price
Set specialsCategory to add one of bol.com's own promo browse pages as an extra source for the run, alongside (or instead of) your search keywords:
aanbiedingen- bol.com's storewide deals page.outlet- bol.com's clearance page.
{"mode": "search","searchTerms": [],"specialsCategory": "aanbiedingen","maxListings": 50}
Every product, whether it came from a keyword search, a specials category, or a pasted URL, carries these fields:
| Field | Example | Notes |
|---|---|---|
| isOnSpecial | true | True when bol.com marks the product with a genuine price reduction or an active promo label. |
| originalPrice | 74.99 | The pre-discount ("van") price. Only set when bol.com's own reference price is actually above the current selling price - never estimated or fabricated. null when there is no real reduction. |
| savingsAmount | 16.00 | originalPrice - price. null when there is no real reduction. |
| savingsPercent | 21 | bol.com's own reported discount percentage. null when there is no real reduction. |
| promoLabel | "deal" | bol.com's own promo badge text, e.g. "deal", "Bliksemdeal", "tot 30% stapelkorting". null when absent. |
| specialsCategory | "aanbiedingen" | Which specials category the product came from. null for keyword-search and URL-mode results. |
Sample shape - values are illustrative placeholders, not from a live listing.
{"productId": "9300000000000","title": "Example Robot Vacuum Cleaner","price": 89.99,"list_price": 109,"discount_percentage": 17,"isOnSpecial": true,"originalPrice": 109,"savingsAmount": 19.01,"savingsPercent": 17,"promoLabel": "deal","specialsCategory": "aanbiedingen"}
These six fields are purely additive: they never change, gate, or null out price, list_price, discount_percentage, or any other existing field, and they cost nothing extra to collect - the data already comes from the same listing page the actor fetches for every run.
Output Example
Sample shape - values are illustrative placeholders, not from a live listing.
{"productId": "9300000000000","title": "Example 15.6 Inch Laptop - 16GB RAM - 512GB SSD - Windows 11","brand": "ExampleBrand","price": 329.5,"list_price": 399,"discount_percentage": 17,"currency": "EUR","condition": "new","ean": "0000000000000","category": "Windows laptops","categoryHierarchy": ["Computer", "Laptops", "Windows laptops"],"rating": 4.3,"reviewCount": 15,"ratingDistribution": {"fiveStar": 11, "fourStar": 0, "threeStar": 2, "twoStar": 1, "oneStar": 1},"reviews": [{"rating": 5, "title": "Great value", "text": "Fast and quiet.", "nickname": "Jane Doe", "location": "Amsterdam", "verified": true}],"image": "https://media.example.com/0000/image-550x461.jpg","images": ["https://media.example.com/0000/image-550x461.jpg"],"availability": "In stock","delivery": "Delivered by tomorrow","offers": [{"price": 329.5, "currency": "EUR", "condition": "new", "seller": {"id": "0000000", "name": "Example Store", "rating": 8.1}}],"seller": {"id": "0000000", "name": "Example Store", "rating": 8.1, "topSeller": false},"specifications": [{"group": "General", "name": "Type computer processor", "value": "Example N-series processor"},{"group": "General", "name": "EAN", "value": "0000000000000"}],"description": "Example laptop for study, work and home use.","weight": "2.2 kg","weightGrams": 2200,"sponsored": false,"has_refurbished": false,"in_stock": true,"products_found": 300001,"search_query": "laptop","country": "NL","url": "https://www.bol.com/nl/nl/p/example-laptop/9300000000000/","scrapedAt": "2026-01-01T00:00:00+00:00"}
When incrementalMode is on, every returned record additionally carries changeType (NEW / UPDATED / UNCHANGED / REAPPEARED / EXPIRED), changedFields (list of field names that differ from the last run — empty for NEW/REAPPEARED/UNCHANGED), firstSeenAt, and lastSeenAt. These four fields are absent in a normal (non-incremental) run.
Resume & recurring updates
There are two different things here — pick the one that matches what you're doing:
| Need | Use |
|---|---|
| A crawl stopped and should continue | resumeFromRunId |
| Run the same search every day and receive only changes | incrementalMode |
| Keep separate daily campaigns for similar searches | distinct stateKey values |
| Run a normal full snapshot | leave both off |
Resume (resumeFromRunId) continues one specific interrupted or previous large crawl: paste a run ID or dataset ID and this run skips products already collected there, returning only the remaining new products.
Incremental mode (incrementalMode) is for a schedule (for example, daily): the actor remembers the previous run of the same search by itself, so you never paste a run ID. The first run returns everything as NEW. Later runs return only NEW, UPDATED, and REAPPEARED products by default — duplicates and unchanged products are suppressed (and not charged, even though their detail page may still be fetched to check for changes). Turn on emitUnchanged or emitExpired only when you also want those rows returned (and billed for). State is isolated per mode/search-terms/URLs/specials-category and filter/sort/detail setup automatically; set stateKey to name or deliberately share a monitoring campaign. maxListings and maxPages never affect which state a run reads — tightening or loosening either keeps the same baseline.
Every emitted product carries an EAN when detail is on, but incremental mode's own identity key is the bol.com product ID (the productId field, stable regardless of fetchDetails).
Scheduled-run example — same search, run daily:
Day 1 (first run ever for this search):
{ "mode": "search", "searchTerms": ["koffiezetapparaat"], "incrementalMode": true }
→ every product comes back with "changeType": "NEW".
Day 2 (the schedule fires again, identical input):
{ "mode": "search", "searchTerms": ["koffiezetapparaat"], "incrementalMode": true }
→ products whose price/rating/availability/etc. changed come back as "changeType": "UPDATED" with changedFields listing what changed, brand-new products come back as "changeType": "NEW", products that vanished and came back come back as "changeType": "REAPPEARED" — and products that are still there, unchanged, are not returned at all (suppressed, not charged) unless emitUnchanged is on. A previously-tracked product no longer found after a complete scan of the search (not capped by Max items, not a Resume run) is returned as "changeType": "EXPIRED" only when emitExpired is on.
Two fields are stripped when deciding UPDATED vs UNCHANGED and never make the actor over-report changes: scrapedAt/scraped_at (stamped fresh on every run), and the token order inside multi-value specifications[].value rows (e.g. "Laptop opties") — bol.com renders the same set of attribute chips in a different order between fetches; the fields you actually see are unaffected, only the change-detection comparison ignores the reordering. A field that goes missing on one fetch and returns on the next (or vice versa) is also never treated as a change — only a genuine value-to-different-value transition is.
Plan Requirement
bol.com serves shoppers in the Netherlands and Benelux, so keep Apify Proxy enabled with a Netherlands exit for the most complete results. The residentialCountries field lets you set the preferred exit countries (NL then BE, DE, FR, GB by default). If your plan does not include full Apify Proxy access, set a backupProxyUrl and the actor uses that gateway instead.
For predictable cost the run honours a per-run request cap (maxResidentialRequests) and a soft transfer budget (trafficBudgetMb); once either is reached it uses the backup gateway. The preferDatacenter and autoDowngradeProxy toggles are cost-saving connection preferences.