# VidaXL Scraper - Products, Prices, Stock & Change Tracking (`abotapi/vidaxl-com-scraper`) Actor

Scrape the vidaXL storefronts by keyword or URL across 6 European markets. Every row carries price, currency, brand, category path, EAN and stock state. Detail enrichment adds description, specifications and the full image gallery. Recurring change tracking is first-class.

- **URL**: https://apify.com/abotapi/vidaxl-com-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.80 / 1,000 product records

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

## VidaXL Scraper: Products, Prices and Stock

VidaXL Scraper turns the vidaXL storefronts into a clean product data API. Search garden, furniture and home products by keyword or paste store links across six European markets, and get prices in the local currency, brand, category path, EAN identity, image gallery, specifications and live stock state for every row. Export to JSON, CSV or Excel, or pull the results straight into your app through the API.

### Why This Scraper?

- **Two ways in.** Search by keyword across the whole catalogue, or paste product, search and category page links. No need to build URLs by hand.
- **Six markets, local prices.** Germany, the United Kingdom, France, the Netherlands, Spain and Italy each quote their own prices and currency on every row.
- **Product depth on demand.** Switch the product page read on or off per run: description, specification table, full image gallery and live stock with delivery estimate, billed once per enriched record only.
- **Built for price monitoring.** Incremental mode returns only new and changed products on recurring runs, and a refused run fails loudly instead of returning an empty dataset.
- **Honest empty results.** A search that matches nothing succeeds quietly with zero rows; a run the store refused fails with a clear connection message, never a silent empty.

### Use Cases

- **Resellers and dropshippers:** collect current vidaXL prices, variants and stock state to compute margins and keep listings in sync.
- **Price monitoring teams:** schedule the same search daily and collect only NEW, UPDATED and EXPIRED rows with per-field change lists.
- **Catalogue builders:** bulk-collect names, descriptions, specifications and image galleries to populate a store or database.
- **Market researchers:** compare the same product across six European storefronts, each with its own price and currency.
- **Content teams:** reuse product descriptions and specification tables as the base for your own listings.

### Data You Get

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

| Field | Example |
| --- | --- |
| `recordId` | `"8710000000001"` |
| `title` | `"Sample Garden Chair"` |
| `brand` | `"vidaXL"` |
| `price` / `oldPrice` / `onSale` | `49.99` / `69.99` / `true` |
| `currency` | `"EUR"` (also `"GBP"` on the UK storefront) |
| `ean` / `sku` / `masterProductId` | `"8710000000001"` / `"100001"` / `"M1000000"` |
| `categories` / `categoryPath` | `["Furniture", "Garden Chairs"]` / `"Furniture > Garden Chairs"` |
| `variants` | `{"variationAttribute1": "Grey"}` |
| `imageUrl` / `imageUrls` / `imageCount` | tile image / full gallery / `6` |
| `availability` / `stockStatus` / `deliveryEstimate` | `"InStock"` / `"In stock"` / `"2-4 working days"` |
| `description` | `"A sturdy garden chair made from..."` |
| `specifications` | `{"Colour": "Grey", "Material": "Wood"}` |
| `url` | product page link on the market storefront |
| `market` / `marketHost` | `"de"` / `"www.vidaxl.de"` |
| `changeType` / `changedFields` / `firstSeenAt` / `lastSeenAt` | incremental mode: `"UPDATED"` / `["price"]` / timestamps |

### How to Use

1. Pick a **mode**: `search` (by keyword) or `url` (paste links).
2. Pick the **market** whose prices you want, set the ordering and price filters, and switch product details on or off.
3. Set **Max items** to control run size and cost, then click **Start**.
4. Download the dataset as JSON, CSV or Excel, or read it through the API.

**Search for garden chairs:**

```json
{
  "mode": "search",
  "searchTerms": ["garden chair"],
  "market": "de",
  "maxItems": 20,
  "fetchDetails": true
}
```

**Paste links (product, search or category page):**

```json
{
  "mode": "url",
  "urls": [
    "https://www.vidaxl.de/catalogsearch/result?q=gartenstuhl"
  ],
  "maxItems": 20,
  "fetchDetails": true
}
```

**Compare UK prices on a schedule:**

```json
{
  "mode": "search",
  "searchTerms": ["garden chair"],
  "market": "uk",
  "maxItems": 50,
  "fetchDetails": false,
  "incrementalMode": true
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/vidaxl-com-scraper").call(run_input={"mode": "search", "searchTerms": ["garden chair"], "market": "de", "maxItems": 5})
for product in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(product["title"], 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/vidaxl-com-scraper').call({ mode: 'search', searchTerms: ['garden chair'], market: 'de', maxItems: 5 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

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

#### Resume and recurring updates

- **Resume** (`resumeFromRunId`) continues one interrupted run: paste its run or dataset ID and the actor skips everything already collected there, so you don't pay twice.
- **Incremental mode** (`incrementalMode`) is for scheduled runs over the same scope. Each record is classified `NEW`, `UPDATED` (with `changedFields`), `UNCHANGED` (suppressed and not billed unless `emitUnchanged` is on), `REAPPEARED` or `EXPIRED` (only after a run that scanned the whole scope, and only with `emitExpired`). `stateKey` names or shares the stored state. With incremental mode off, output is exactly as before.
- **Ignore fields for changes** (`ignoreFieldsForChanges`) names extra record fields that must never mark a row `UPDATED`. The image gallery is ignored by default because its framing rotates without the product changing.

### 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 record, 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 haven't 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` or `url`. |
| `searchTerms` | array | `["garden chair"]` | Keywords to search (search mode). |
| `urls` | array | sample | Product, search or category page links (url mode). |
| `market` | string | `de` | Storefront: `de`, `uk`, `fr`, `nl`, `es` or `it`. |
| `sortBy` | string | `relevance` | Store ordering: `relevance`, `topseller`, `priceasc`, `pricedesc` or `newest`. |
| `minPrice` | number | (none) | Keep rows priced at or above this (market currency). |
| `maxPrice` | number | (none) | Keep rows priced at or below this (market currency). |
| `onSaleOnly` | boolean | `false` | Keep only discounted rows. |
| `fetchDetails` | boolean | `true` | Read each product page (description, specifications, gallery, stock). Billed once per enriched record. |
| `maxItems` | integer | `20` | Stop after this many records (`0` = no limit). |
| `maxPages` | integer | `0` | Stop after this many listing pages per search (`0` = no page cap). |
| `resumeFromRunId` | string | (none) | Continue one interrupted run. |
| `incrementalMode` | boolean | `false` | Return only new and changed records on scheduled runs. |
| `stateKey` | string | (none) | Name or share an incremental-mode state. |
| `emitUnchanged` | boolean | `false` | Also return (and bill) unchanged records. |
| `emitExpired` | boolean | `false` | Also return (and bill) expired records. |
| `ignoreFieldsForChanges` | array | (none) | Extra record fields that never mark a row UPDATED. |
| `proxyConfiguration` | object | Apify Proxy | Connection settings. |
| `mcpConnectors` | array | (none) | Optional: send a summary of each record to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | (none) | Notion connector only: page under which records are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. |

### Output Example

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

**A detail-enriched row (details on):**

```json
{
  "recordId": "8710000000001",
  "title": "Sample Garden Chair",
  "brand": "vidaXL",
  "price": 49.99,
  "oldPrice": 69.99,
  "onSale": true,
  "currency": "EUR",
  "ean": "8710000000001",
  "sku": "100001",
  "categoryPath": "Furniture > Garden Chairs",
  "imageUrl": "https://example.com/image_1.jpg",
  "imageUrls": ["https://example.com/image_1.jpg", "https://example.com/image_2.jpg"],
  "imageCount": 2,
  "description": "A sturdy garden chair made from treated wood...",
  "specifications": {"Colour": "Grey", "Material": "Wood", "EAN": "8710000000001"},
  "availability": "InStock",
  "stockStatus": "In stock",
  "deliveryEstimate": "2-4 working days",
  "url": "https://www.vidaxl.de/e/sample-garden-chair/8710000000001.html",
  "market": "de",
  "scrapedAt": "2026-10-10T00:00:00Z"
}
```

### Plan Requirement

The default proxy setting works out of the box, and the actor rotates connections automatically when one is refused. For large or frequent runs, the residential proxy group gives more headroom; pick it under Connection. If your Apify plan does not include proxy access, the run fails with a clear message instead of returning an empty result; a paid plan with proxy access is required.

### FAQ

#### How much does it cost?

You pay per record returned, with product details billed only when Fetch product details is on. The **Pricing** tab shows the current rates. Use **Max items** to cap the cost of any run.

#### Is it legal to scrape vidaXL?

This actor collects only publicly available product data. You are responsible for how you use it: follow the store's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Prices and specifications are generally facts, but descriptions and photos may be subject to third-party rights.

#### Can I monitor prices on a schedule?

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. Each run then returns only new, updated and expired records, and unchanged ones are not billed.

#### Why did a run return fewer products than the store shows?

The run stops at **Max items** (or **Max pages**), cheapest walk order first under your sort. Set them higher, or to `0`, to collect the whole search.

#### Why did my run fail instead of returning an empty dataset?

If the store refuses every request, the run stops with a clear message so "no products today" is never confused with "nothing could be read". Run it again in a few minutes.

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

### 🔗 More scrapers you might like

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

<table>
<tr><td>🧩 <a href="https://apify.com/abotapi/galaxus-marketplace-scraper"><b>Digitec Galaxus Scraper</b></a><br>Scrape Digitec Galaxus, Switzerland's largest online retailer: products with local...</td><td>🏷️ <a href="https://apify.com/abotapi/bol-com-scraper"><b>bol.com Scraper</b></a><br>Scrape bol.com products: title, price, list price and discount, EAN, brand, images...</td></tr>
<tr><td>🧩 <a href="https://apify.com/abotapi/miravia-es-scraper"><b>Miravia Spain Scraper</b></a><br>Scrape Miravia Spain by keyword, seller store or URL. Every row carries the selling price...</td><td>🧩 <a href="https://apify.com/abotapi/falabella-marketplace-scraper"><b>Falabella Scraper</b></a><br>Scrape Falabella, Latin America's leading retail marketplace: products with local prices...</td></tr>
<tr><td>🔌 <a href="https://apify.com/abotapi/currys-co-uk"><b>Currys UK Scraper</b></a><br>Scrape Currys UK electronics by keyword or URL. Extract prices, brands, stock status...</td><td>🧩 <a href="https://apify.com/abotapi/ebay-com-scraper"><b>eBay Scraper</b></a><br>Scrape eBay by keyword, category, seller or pasted link across 16 country storefronts...</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/vidaxl-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`):

How to start the scrape. 'search' runs store searches. 'url' processes the store URLs you paste: a product URL is fetched as a single product, and a search or category page URL is paged like a search, its own scope deciding what is read. The filters below apply to every walk in both modes; a pasted product URL is the exception and returns that single product in full.

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

One or more things to search the store for, for example 'garden chair', 'greenhouse', 'teak bench'. Each term is scraped independently. Search mode only: in URL mode the scope is read out of each pasted URL instead. Leave this empty and the run browses the whole catalogue of the selected market.

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

Paste product URLs (https://www.vidaxl.de/e/vidaxl-gartenstuhl-holz-grau/8719883557076.html), search URLs (https://www.vidaxl.de/catalogsearch/result?q=gartenstuhl) or category page URLs (https://www.vidaxl.de/g/4299/gartenmobel) from any supported market. A pasted URL decides its own scope and market. A product URL returns that one product in full. The filters below narrow what listing walks read.

## `market` (type: `string`):

The storefront search-mode walks read. Each market quotes its own prices and currency. URL mode reads the market out of each pasted URL instead.

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

The store's own server-side ordering, sent with every page of every walk. This orders the walk itself (and therefore which rows a capped run keeps), not just the output. Applies in BOTH search mode and URL mode listing walks.

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

Only keep rows whose selling price is at least this amount, in the market currency. The store has no parameter for this, so it is applied to the rows this run reads. Applies in BOTH search mode and URL mode.

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

Only keep rows whose selling price is at most this amount, in the market currency. The store has no parameter for this, so it is applied to the rows this run reads. Applies in BOTH search mode and URL mode.

## `onSaleOnly` (type: `boolean`):

Only keep rows the store sells below their original price. Applied to the rows this run reads. Applies in BOTH search mode and URL mode.

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

Read each product's page for the description, specification table, full image gallery and live stock/delivery state. Charged once per enriched record (the detail-enrichment event); rows whose page could not be read keep their listing fields and are never charged. Turn it off for a fast price-only pull.

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

The maximum number of product records this run returns across all searches and URLs. Set to 0 for no limit.

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

The maximum number of listing pages read per search or pasted listing URL. Each page holds 20 products. Set to 0 for no page cap; Max items still bounds the run.

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

Proxy for the run. The default uses the standard pool, which serves every storefront page; Residential is recommended for the most reliable results. When the default pool is refused, the run automatically tries Apify residential, then a backup connection. Free plans refuse every Apify pool at connect time and continue on the backup connection.

## `resumeFromRunId` (type: `string`):

Continue an interrupted run: paste a run id or dataset id and this run skips every record it already collected. Leave empty for a fresh run. Cannot be combined with Incremental mode when that mode already tracks this search; pick one.

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

Schedule this actor against the same search and collect only what changed: every product is classified NEW, UPDATED, UNCHANGED, REAPPEARED or EXPIRED against the remembered baseline for this exact search scope. UNCHANGED rows are skipped unless Emit unchanged rows is on.

## `stateKey` (type: `string`):

Name one monitoring campaign so several schedules keep separate baselines. Empty means the baseline is keyed on a hash of this exact search scope.

## `emitUnchanged` (type: `boolean`):

In Incremental mode, also return rows classified UNCHANGED (marked as such) instead of suppressing them. Off by default: a scheduled monitor then returns only NEW, UPDATED, REAPPEARED and EXPIRED rows.

## `emitExpired` (type: `boolean`):

In Incremental mode, after a complete scan also return rows that disappeared from the store, marked EXPIRED. Only runs after a proven complete scan; capped or interrupted runs never tombstone.

## `ignoreFieldsForChanges` (type: `array`):

Extra top-level record fields that must never mark a row UPDATED in Incremental mode. The image gallery is ignored by default (its framing rotates without the product changing); name further fields here when a scheduled monitor bills noise as changes.

## `mcpConnectors` (type: `array`):

Connector ids that receive new records as the run collects them (for example a Notion database). Empty means no export; the dataset always holds every record.

## `notionParentPageUrl` (type: `string`):

When exporting to Notion, the parent page new database entries are created under. Only used when MCP connectors includes a Notion connector.

## `maxNotifyListings` (type: `integer`):

The maximum number of records forwarded to MCP connectors per run. Only used when MCP connectors is non-empty.

## Actor input object example

```json
{
  "mode": "search",
  "searchTerms": [
    "garden chair"
  ],
  "urls": [
    "https://www.vidaxl.de/e/vidaxl-gartenstuhl-holz-grau/8719883557076.html",
    "https://www.vidaxl.de/catalogsearch/result?q=gartenstuhl",
    "https://www.vidaxl.de/g/4299/gartenmobel"
  ],
  "market": "de",
  "sortBy": "relevance",
  "onSaleOnly": false,
  "fetchDetails": true,
  "maxItems": 20,
  "maxPages": 0,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "mcpConnectors": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (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",
    "searchTerms": [
        "garden chair"
    ],
    "urls": [
        "https://www.vidaxl.de/e/vidaxl-gartenstuhl-holz-grau/8719883557076.html",
        "https://www.vidaxl.de/catalogsearch/result?q=gartenstuhl",
        "https://www.vidaxl.de/g/4299/gartenmobel"
    ],
    "market": "de",
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    },
    "ignoreFieldsForChanges": [],
    "mcpConnectors": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/vidaxl-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",
    "searchTerms": ["garden chair"],
    "urls": [
        "https://www.vidaxl.de/e/vidaxl-gartenstuhl-holz-grau/8719883557076.html",
        "https://www.vidaxl.de/catalogsearch/result?q=gartenstuhl",
        "https://www.vidaxl.de/g/4299/gartenmobel",
    ],
    "market": "de",
    "maxItems": 20,
    "proxyConfiguration": { "useApifyProxy": True },
    "ignoreFieldsForChanges": [],
    "mcpConnectors": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/vidaxl-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",
  "searchTerms": [
    "garden chair"
  ],
  "urls": [
    "https://www.vidaxl.de/e/vidaxl-gartenstuhl-holz-grau/8719883557076.html",
    "https://www.vidaxl.de/catalogsearch/result?q=gartenstuhl",
    "https://www.vidaxl.de/g/4299/gartenmobel"
  ],
  "market": "de",
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "ignoreFieldsForChanges": [],
  "mcpConnectors": []
}' |
apify call abotapi/vidaxl-com-scraper --silent --output-dataset

```

## MCP server setup

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