# zooplus Pet Supplies Price Watch (12 countries) (`superslowsloth/zooplus-products`) Actor

Search zooplus pet supplies by keyword and get one row per product variant: price in local currency, unit price (per kg or litre), was-price and discount, extra-discount and subscription prices, rating, reviews, stock, brand and URL. Germany, UK, France, Italy, Spain, Poland and more.

- **URL**: https://apify.com/superslowsloth/zooplus-products.md
- **Developed by:** [Superslow Sloth](https://apify.com/superslowsloth) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.62 / 1,000 product scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## zooplus Scraper: Pet Supplies Price Watch (12 countries)

Search [zooplus](https://www.zooplus.com) for one or more terms and get one flat
row per **product variant** (one pack size or flavour, with its own price and
stock): the price in the site's own currency, the **unit price per kg or litre**,
the was-price and discount percentage, zooplus's **"Extra-Rabatt" price** and its
**subscription (Abo) price**, brand, rating, review count, stock, marketplace
seller, image and product URL. Run it on a schedule and diff the dataset to get a
**pet-food and pet-supply price-change watch**, or read a whole brand on several
country sites to compare what the same bag of cat food costs in Germany, France,
the UK and Poland.

No account, no API key, no login, no browser. One request returns 48 variants.

### Input

| Field | Notes |
|---|---|
| `searchTerms` | One or more search terms, one per line, in the language of the site you chose (`katzenfutter` on zooplus.de, `cat food` on zooplus.co.uk, `croquettes chat` on zooplus.fr). Required. A variant found by two terms is returned once, under the first. zooplus matches loosely: `katzenfutter` finds 22,000 products, so name the brand or pack you watch (`royal canin sterilised 37`). |
| `site` | Country shop: `de` (default), `at`, `ch`, `fr`, `it`, `es`, `nl`, `pl`, `co.uk`, `ie`, `se`, `dk`. |
| `maxItems` | Variants to read per search term (default 100, up to 10,000). 48 per page. |
| `sort` | `relevance` (default), `price-asc`, `price-desc`, `unit-price-asc`, `unit-price-desc`, `rating`. |
| `proxyConfiguration` | Apify proxy by default (see "How it fetches"). |

#### Sites

Twelve country sites were fetched and parsed live on 2026-10-14. All answer with
the same page state and the same fields; only the language of the text and the
currency change.

| `site` | Shop | Currency on every money field |
|---|---|---|
| `de` | zooplus.de | EUR |
| `at` | zooplus.at | EUR |
| `ch` | zooplus.ch | CHF |
| `fr` | zooplus.fr | EUR |
| `it` | zooplus.it | EUR |
| `es` | zooplus.es | EUR |
| `nl` | zooplus.nl | EUR |
| `pl` | zooplus.pl | PLN |
| `co.uk` | zooplus.co.uk | GBP |
| `ie` | zooplus.ie | EUR |
| `se` | zooplus.se | SEK |
| `dk` | zooplus.dk | DKK |

zooplus runs more shops than these. They were not tested and are not offered.

### Output

Prices are numbers in the site's currency; `currency` is on every row. They are
read from the number zooplus puts in its page data, not parsed out of text like
`12,29 €`; the printed text is kept in `price_text` so you can check it.

| Field | Notes |
|---|---|
| `query`, `site`, `page`, `position` | The search term that first found the row, the country site, and where it sat in the results (page and place on the page). |
| `product_id`, `variant_id`, `variant_number`, `variant_key` | zooplus's product number (shared by all sizes of one product), the variant's own article id, its index, and `<product_id>.<variant_number>` (`173156.4`), the key the rows are de-duplicated on. |
| `name`, `variant_name`, `brand`, `brands`, `description`, `pet_types` | As zooplus shows them. `variant_name` is what separates sizes (`3 kg`, `Thunfisch mit Pazifik-Garnelen`). `brands` is empty on some own-label and veterinary lines, and then `brand` is null. `description` is plain text. |
| `url`, `image_url` | The variant's product page and its photo. |
| `currency`, `price`, `price_text` | The shelf price of one pack and how zooplus prints it. |
| `price_type` | zooplus's own class: `REGULAR` (no reference price), `RETAIL` (reference is the manufacturer's recommended price), `SET` (a bundle; reference is the same items bought one by one), `OFFER` (reference is the earlier price). |
| `was_price`, `was_price_label`, `was_price_kind`, `price_note`, `discount_percent` | The crossed-out reference price, the word zooplus prints beside it (`UVP`, `RRP`, `Einzeln`, `Was`), what it means (`rrp`, `single_items`, `previous_price`) and zooplus's footnote ("Lowest price in the 30 days prior to the discount"). `discount_percent` is worked out here from the two prices. **Null when zooplus shows no reference price**, never 0. See "Which reference price is real?". |
| `unit_price`, `unit_measure` | The price per `kg`, `l` or piece that zooplus prints under the price, and the unit in the site's language. Null on products it gives no figure for (large items, most marketplace goods). |
| `extra_discount_percent`, `extra_discount_price` | zooplus's "Extra-Rabatt": a percentage off that the customer switches on in the cart, and the price it gives. Null where there is none. |
| `subscription_price`, `subscription_discount_percent` | The price with a zooplus subscription ("Abo"), usually 5% off. Null where there is none. |
| `promotion_text`, `badge`, `is_new`, `is_limited_deal`, `is_sponsored` | Card wording (`Gratis!`, `2kg free`), zooplus's own pick label, and its flags. `is_sponsored` marks paid placements. |
| `is_available` | False when zooplus lists the variant as out of stock. |
| `is_marketplace`, `seller_id`, `seller_name` | True when a third-party seller on zooplus's marketplace sells it. |
| `delivery_days_min`, `delivery_days_max` | Delivery estimate in working days. |
| `rating`, `review_count`, `variant_count` | Star rating out of 5 and review count for the **product** (all variants together, not this size alone), and how many variants the product has. zooplus sends 0 and 0 for an unreviewed product; that is **null** `rating` here with `review_count` 0. |

#### Which reference price is real?

`was_price` is whatever zooplus crosses out, and the three kinds mean different
things; filter on `was_price_kind` before reading it as "the old price":

- `rrp`: the manufacturer's recommended price (UVP/RRP). A list price, not a
  price zooplus ever charged. 198 of 960 rows sampled across six sites (de, pl, ch, se, co.uk, dk).
- `single_items`: for a bundle, the price of the same items bought separately
  (310 of the same 960 rows).
- `previous_price`: an actual earlier price; zooplus footnotes it as the lowest
  price in the 30 days before the discount. The only kind that is a was-price in
  the shopper's sense, and the rarest (1 of the same 960 rows).

`extra_discount_price` and `subscription_price` are separate from `price`:
`price` is what a customer pays by default, the other two need an action
(activate the Extra-Rabatt in the cart, or subscribe).

#### Honest nulls

A field zooplus did not send is `null`, never `0` or `""`. A price of exactly
`0.0` is real: zooplus lists `Gratis!` free samples at 0,00 €, and `unit_price` is
0.0 on them too.

### Pricing

Pay per event:

- **$0.002** per run (`actor-start`), charged only after your input is
  validated, so a run that fails on bad input costs nothing.
- **$0.00062** per product row written (`product-scraped`), i.e. $0.62 per
  1,000 products. A row that is not written is not charged.

### How it fetches

It requests the search page the shop itself serves
(`www.zooplus.<tld>/search/results?q=<term>&p=<page>&sorting=<sort>`) and reads the
product state the Next.js page embeds in its HTML, not the visible cards. 48
variants per request, paged with `p`. Measured 2026-10-14: HTTP 200 and the full
state from a plain residential connection with a Chrome TLS fingerprint
(curl_cffi), no key or login. A refusal (403, 429, 5xx, or a 200 without the
product state) is treated as the address being blocked, and the actor rotates to
a fresh one and retries; any other error status is permanent and is not retried.

Things worth knowing, all measured:

- **Pace.** zooplus sits behind CloudFront, which answered HTTP 403 "Request
  blocked" to a home connection after about 150 page fetches in ten minutes, and
  kept doing so for a few minutes. The actor asks one page at a time through
  Apify's rotating proxy.
- **Depth ceiling.** A search pages to about 10,000 matches (209 pages) however
  many it reports; `maxItems` stops there, and the log names any term that had
  more behind it. Make the term more specific to see the rest.
- **Past the end.** zooplus does not stop where a search stops: asking for page 3
  of a 2-page search returns a different, *looser* search with its own page
  count. The actor stops at the page count page 1 reported and never reads
  past it. A search that fits on one page has no paging block at all.
- **The count wobbles.** zooplus answers different visitors with slightly
  different result sets (922 against 889 matches for the same search), fixed by
  its visitor cookie. The actor keeps one cookie for the run, so pages of one
  search agree with each other; a retry from a new address starts a new visitor,
  and the de-duplication on `variant_key` covers any overlap that causes.
- **Currency.** The page's own `currency` reads EUR on every site, even the pound,
  franc, zloty and krona shops, whose prices are printed in another currency. The
  actor reports the currency the site prints (its `alternativeCurrency`), checked
  against the printed symbol on all twelve (`kr` is shared by SEK and DKK, so
  there the sites' own currency field decides).
- **Sorts.** The sorts are zooplus's own and the actor checks each one: the page
  says which sort it applied, and the actor fails a term (at no charge) rather
  than return rows in an order that was not asked for. zooplus accepts unknown
  sort names with HTTP 200 and its default order. Paid `is_sponsored` rows are
  mixed in whatever the sort.
- **Not offered.** Category browsing, product-detail pages and review text are
  not read; the search page does not carry them.

# Actor input Schema

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

What you would type into the zooplus search box, one term per line, in the language of the site you chose (for example katzenfutter on zooplus.de, cat food on zooplus.co.uk, croquettes chat on zooplus.fr). Each term is searched and read separately. A product variant that two terms both find is returned once, under the first. zooplus matches loosely: a broad word such as hundefutter returns tens of thousands of products, so name the brand or pack size you watch (royal canin sterilised 37).

## `site` (type: `string`):

Which zooplus country shop to search. Prices come back in that shop's currency (the currency field on every row), and search terms must be in that shop's language. Twelve sites were tested live; zooplus runs more that are not offered here.

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

Read at most this many product variants for each search term. zooplus serves 48 per page and stops paging at about 10,000 matches per search, so up to 10,000 per term are accepted.

## `sort` (type: `string`):

The order zooplus lists the results in. 'Relevance' is its own default (popularity). The unit-price sorts rank by price per kilo, litre or piece, which is how pack sizes are compared. zooplus mixes its paid 'Gesponsert' placements into the top of any order, marked is_sponsored in the output.

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

Apify proxy (automatic datacenter exits) is the default. A refused request (403 from zooplus's CloudFront edge, 429, 5xx) is retried from a fresh address, which is what the proxy is for. zooplus answered Apify datacenter exits in testing; a plain home connection is also answered, but is blocked for several minutes after about a hundred requests in quick succession.

## Actor input object example

```json
{
  "searchTerms": [
    "royal canin sterilised"
  ],
  "site": "de",
  "maxItems": 100,
  "sort": "relevance",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (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": [
        "royal canin sterilised"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("superslowsloth/zooplus-products").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": ["royal canin sterilised"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("superslowsloth/zooplus-products").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": [
    "royal canin sterilised"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call superslowsloth/zooplus-products --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,superslowsloth/zooplus-products"
        }
    }
}
```

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/ZSVIbwr4F5dXY747Z/builds/TrOgOjftveJgIDS8B/openapi.json
