# Skroutz Scraper - Greek Price Comparison & Product Prices (`s-r/skroutz-scraper`) Actor

Search Skroutz or paste a category URL and get one row per Greek product: title, the lowest price across every shop Skroutz compares, currency, rating, review count, image and a stable SKU id. Up to 600 products per run. For price monitoring, assortment research and Greek market analysis.

- **URL**: https://apify.com/s-r/skroutz-scraper.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:** E-commerce, Business, Marketing
- **Stats:** 3 total users, 0 monthly users, 96.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 run start fees

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

## Skroutz Scraper

The Skroutz scraper turns skroutz.gr, Greece's dominant price-comparison site, into clean rows: the lowest price across every shop Skroutz compares, plus rating, review count and a stable SKU id. Pass a search term or a category URL and take one row per product, up to 600 per run.

Skroutz is not a single retailer. Each listing is a product that many Greek shops offer, and `price_from` is the market floor for that product. A category run is therefore a direct answer to "what is the cheapest anyone sells this for in Greece right now?" — without opening one shop tab per row.

### What the Skroutz scraper returns

- **A market minimum.** `price_from` is the lowest offer Skroutz compares for that product
- **Rating and review count** as numbers, from the site's own aggregate
- **Stable SKU ids** parsed from the product URL, so two runs join cleanly
- **Honest pagination.** A search returns one page of about 52 products and no pagination parameter moves it; the run says so instead of looping over the same 52 rows. Category URLs (`/c/<id>/…`) page deeper
- **Success scored on the data.** An interstitial page also answers 200, so the run only succeeds when the product list is actually present
- **$0.0005 start fee + $0.003 per product.** Empty results cost nothing beyond the start fee

A row looks like this (first product of a search for `laptop`, 2026-10-06):

```json
{
  "position": 1,
  "sku_id": "61438939",
  "title": "HP Victus 15-fb3093dx 15.6\" IPS FHD 144Hz (Ryzen 7-7445HS/16GB/512GB SSD/GeForce RTX 4050/W11 Home) (US Keyboard)",
  "url": "https://www.skroutz.gr/s/61438939/hp-victus-15-fb3093dx-15-6-ips-fhd-144hz-ryzen-7-7445hs-16gb-512gb-ssd-geforce-rtx-4050-w11-home-us-keyboard.html",
  "price_from": 1055.18,
  "currency": "EUR",
  "rating": 4.84615,
  "reviews_count": 13,
  "image": "https://d.scdn.gr/images/sku_main_images/061438/61438939/xlarge_20251002161814_hp_victus_15_fb3093dx_15_6_ips_fhd_144hz_ryzen_7_7445hs_16gb_512gb_ssd_geforce_rtx_4050_w11_home_us_keyboard.jpeg",
  "brand": null,
  "query": "laptop"
}
```

Every row also carries the marketplace contract fields `source` (`"skroutz-scraper"`), `country` (`"GR"`), `fetched_at` (ISO 8601 UTC), `product_id` (same value as `sku_id`), `price` and `lowest_price` (same value as `price_from`) and `image_url` (same value as `image`), so a row can be joined with data from other markets without renaming columns.

### Input

| Field | Type | Required | Default | What it does |
|---|---|---|---|---|
| `query` | string | one of query/url | – | What to search for. Greek and English terms both work |
| `url` | string | one of query/url | – | A category URL such as `/c/40/laptop.html`, or any absolute/partial URL resolved against skroutz.gr. When set it wins over `query`. Only `/c/<id>/` URLs paginate |
| `sort` | select | no | `relevance` | Relevance, popularity or newest. Any other value falls back to relevance. Sorting by price is deliberately not offered: that ordering returns a payload with the prices stripped out |
| `limit` | integer | no | `52` | Products to return, 1 to 600. A page carries about 48–52. Whole numbers may arrive as a number or as text (`"200"`); `0` or an empty value falls back to 52 |
| `use_proxy` | boolean | no | `false` | Off by default. Turn it on only if Skroutz starts refusing your runs |
| `retries` | integer | no | `6` | Attempts per page, 2 to 12, with the same number-or-text leniency as `limit` (`0` or empty falls back to 6). Each attempt climbs to the next request shape |
| `cursor` | string | no | – | New. Send back `next_cursor` from the previous response with the same `query`/`url` and `sort` to continue a category |

Four inputs are enough for most runs:

- `{"query": "laptop"}` — one search page, about 52 products
- `{"url": "/c/40/laptop.html", "limit": 200}` — a category, three pages, 200 unique products
- `{"query": "laptop", "sort": "popularity"}` — Skroutz's popularity ordering on a search
- `{"url": "/c/40/laptop.html", "limit": 600, "sort": "newest"}` — a whole category, then continue with `next_cursor`

`query` and `url` may both be filled; `url` is what gets fetched, and `query` is still echoed on every row, which is how a mixed input stays traceable. There is no schema requirement forcing one of them: an empty run comes back successful and empty with a `bad_input` note, exactly like earlier versions of this actor.

### Output

One row per unique product, deduplicated on `sku_id` (falling back to the product URL):

| Field | Type | Notes |
|---|---|---|
| `position` | integer | 1..n after deduplication and the `limit` cut, across all pages |
| `sku_id` | string | null | Taken from `/s/<id>/` in the product URL; stable across runs |
| `title` | string | Product name as Skroutz lists it, whitespace compacted |
| `url` | string | null | Absolute product URL |
| `price_from` | number | null | Lowest price across the compared shops. Whole prices come back as JSON integers (`931`), others as decimals (`1055.18`) |
| `currency` | string | `EUR` |
| `rating` | number | null | Out of 5, passed through as published — `5` or `4.84615` |
| `reviews_count` | integer | null | Number of reviews behind the rating |
| `image` | string | null | Main product image URL |
| `brand` | string | null | Filled when Skroutz publishes one on the listing |
| `query` | string | null | Echo of your search term; `null` when you passed only `url` |

When a category has more rows than `limit`, the run continues from a cursor and keeps positions and SKU ids unique across pages. When nothing is found, the run finishes successfully with zero rows and a clear reason — never as a crash.

Besides the rows, the run writes a summary (`itemCount`, `errorCount`, `paginates`, `singlePageNote`, `pagesRequested`, `pagesServed`, `productsReturned`, `withPrice`, `withRating`, `fetchedInSeconds`) and, when something went wrong, an `errors` list with a code and an English message. Codes you may see: `bad_input`, `bad_url`, `no_results`, `challenged`, `fetch_failed`. Only the first three are ordinary: they end the run successfully with zero rows. A blocked or timed-out page is retried within the attempt budget and reported if it never arrives.

The run summary and errors live in the key-value store, the products in the default dataset. Everything is readable through the Apify API, the console, or the MCP endpoint attached to this actor.

### Use cases

**Greek market pricing.** A category run gives you the market floor per product without visiting a single retailer. Compare your own margin against the cheapest Greek offer for the same SKU.

**Tracking a category over time.** Store `sku_id` and `price_from` on a schedule. The SKU id does not change, so a diff is an exact join and price moves are unambiguous. `fetched_at` tells you when each row was captured.

**Finding what is worth stocking.** `reviews_count` is the closest public proxy for how much a product actually sells; `rating` tells you whether the listing is healthy. A product with 13 reviews at €1055 behaves differently from one with none.

**Catalogue building.** Title, image, price, rating and URL on every row, about 50 rows per request, no per-product fetch. Feed the result into a sheet, a warehouse table or a feed generator.

**Competitor monitoring for Greek retailers.** Run the category you sell in and see which shop holds the floor price this week.

### Skroutz scraper compared with other options

**Checking Skroutz by hand** works for one product. It stops being practical at twenty, and by the time the spreadsheet is filled the prices have moved. This actor returns the same listings as structured rows, keyed by SKU id.

**Skroutz's own developer documentation** (`developer.skroutz.gr`) is aimed at shops that publish their own data to Skroutz. It is not a catalogue export: it will not hand you the competing offers for a category you choose. If you need the price comparison itself, you need a scraper.

**Writing your own crawler** is the usual alternative — several public Scrapy projects exist. That route means maintaining selectors against a site that changes its markup, deciding what counts as a real product page, deduplicating across pages and rebuilding pagination whenever it breaks. Here the parsing, deduplication, pagination and failure reporting are done for you and priced per row.

**Other Skroutz actors in the Store** cover the same site. Differences worth checking before you buy: whether you get the market floor price or a per-shop offer list, whether category pagination continues past one page, whether empty results are charged, and whether the run reports success on data rather than on an HTTP status code. Several of them advertise price history or a full per-shop offer list, which this actor does not produce: it returns the current snapshot — the lowest price Skroutz shows right now, with rating, review count and SKU id — and you build the series by running it on a schedule.

**Generic scraping services** give you a raw page per request that you still have to parse, deduplicate and price-match yourself. Here the JSON-LD product list is already turned into rows, and a run that never sees a product list is reported as such instead of returning an interstitial page as if it were data.

### Pricing

| Event | Price | When |
|---|---|---|
| `run_start` | $0.0005 | Once per run, including runs that return nothing |
| `product` | $0.003 | Per product row delivered |

A search that returns 52 products costs $0.0005 + 52 × $0.003 = $0.1565. A category run of 600 products costs $0.0005 + 600 × $0.003 = $1.8005. An empty result costs only the start fee, so a search that finds nothing is not a bill. Free-plan runs are capped at 10 products, and those ten are the only ones charged for.

### FAQ

#### Is there a Skroutz API?

Skroutz publishes developer documentation for shops that list their own products. It does not offer a public export of the comparison catalogue, so a scraper is the practical route to category-wide prices. You can also call this actor itself as an API: start a run through the Apify API with your input and read the dataset back.

#### Why does a search stop after one page?

Skroutz answers every pagination parameter on a search URL with the same first page. Walking it would bill you for duplicates. Pass a category URL when you need more than about 52 products; category URLs page deeper and the run reports `paginates: true` when that happened.

#### What happens when a search has no hits?

The run succeeds with zero product rows and a `no_results` note. You are not charged per product — only the $0.0005 start fee applies.

#### Can I get Skroutz price history?

Not from one run. This actor returns the current snapshot — the lowest price Skroutz compares right now, with rating, review count and `fetched_at` — not a stored series. Skroutz publishes no public export of its catalogue either (see the first question), so there is no history endpoint to call. To build a series, run the same `url` or `query` on a schedule and keep `sku_id`, `price_from` and `fetched_at`: the SKU id is stable across runs, so each run joins onto the one before it and a price move is unambiguous. The comparison section above says which other Store actors advertise a history they can only produce the same way.

#### Is `brand` always empty?

Frequently. Skroutz's product list does not carry a brand for most rows, so `brand` comes back `null` unless the listing publishes one. `title` normally contains the brand name as text, which is what most buyers match on.

#### Do I need a proxy?

Not by default. Skroutz is reachable from an ordinary connection with the right request shape, and `use_proxy` starts off. Turn it on only if your own runs start coming back empty; the actor then fetches through an alternative connection.

#### How do I export the data to CSV or Excel?

Open the run's dataset in the Apify console and choose JSON, CSV, Excel or another format, or fetch the items through the Apify API. The `sku_id` column is the one to join on when you export several runs.

#### Will I be charged for an empty dataset?

Only the run-start fee of $0.0005 applies. Product charges are counted per delivered row, so a run without products has no product charges.

# Actor input Schema

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

What to search for on skroutz.gr. Greek and English terms both work. A search returns one page of about 52 products; use a category URL to go deeper.

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

A Skroutz category URL such as https://www.skroutz.gr/c/40/laptop.html, or any absolute/partial URL resolved against https://www.skroutz.gr. When set it wins over query. Only /c/<id>/ URLs paginate.

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

Skroutz's own ordering: relevance (default), popularity or newest. Accepts any JSON value like the original actor (str(x or "relevance")): falsy and unknown values fall back to relevance. Sorting by price is deliberately not offered: that ordering makes the site return a payload with the prices stripped out.

## `limit` (type: `integer`):

How many products to return. Accepts integers, numbers and numeric strings like the original actor (int(x or 52), clamped 1-600); 0 or empty falls back to 52. A page carries about 48-52. A search caps at one page; a category URL pages further.

## `use_proxy` (type: `boolean`):

Off by default: Skroutz is reachable from the platform's own egress with the right request shape. Turn it on only if runs start getting blocked. Accepts any JSON value like the original actor (bool(x)): 0, empty string, false, \[] and {} stay off; 1, "true" and "false" turn it on because Python treats non-empty strings as true.

## `retries` (type: `integer`):

Attempts per page. Accepts integers, numbers and numeric strings like the original actor (int(x or 6), clamped 2-12); 0 or empty falls back to 6. Each attempt climbs to the next header combination. Success is scored on the product list arriving, never on HTTP 200 alone. Budgeted in wall-clock inside one request; leftover attempts resume via the cursor.

## `cursor` (type: `string`):

Opaque continuation returned as next_cursor; keep the same query/url and sort. Omit for the first request.

## Actor input object example

```json
{
  "query": "laptop",
  "url": "https://www.skroutz.gr/c/40/laptop.html",
  "sort": "relevance",
  "limit": 52,
  "use_proxy": false,
  "retries": 6
}
```

# Actor output Schema

## `results` (type: `string`):

One row per product.

## `summary` (type: `string`):

Whether the URL paginates, pages served, and which request shape won.

## `errors` (type: `string`):

Per-page failures with a code and a redacted message.

# 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": "laptop",
    "limit": 52,
    "retries": 6
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/skroutz-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": "laptop",
    "limit": 52,
    "retries": 6,
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/skroutz-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": "laptop",
  "limit": 52,
  "retries": 6
}' |
apify call s-r/skroutz-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,s-r/skroutz-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/yiSfjLpldCx2xo5ae/builds/nmnTTbEvwSvHKQBbm/openapi.json
