DEV Community

Cover image for How to Scrape Spitogatos Listings in Python with Spitogatos Scraper
ReventaData
ReventaData

Posted on Originally published at apify.com

How to Scrape Spitogatos Listings in Python with Spitogatos Scraper

By the end of this tutorial you will have a Python script that pulls Greek homes, apartments and land for sale or rent from spitogatos.gr into structured rows: asking price, floor area, bedrooms, bathrooms, floor label, coordinates, agency details and photo links.

Spitogatos is a Greek real-estate portal whose pages reject simple scripts. In tests on 7 November 2026, plain HTTP requests from both a laptop and a datacenter server received a "Pardon Our Interruption" challenge page or HTTP 403 and 405 responses instead of listings. Spitogatos Scraper (reventadata/spitogatos-scraper) is the Apify Actor I built to handle that fetching and extract the structured state embedded in each search page.

This guide runs the Actor from Python with the Apify API client, and every output example comes from a live capture of Thessaloniki rentals dated 14 November 2026.


Prerequisites

You need three things to follow along:

  1. Python 3.11 or newer (required by the 3.x Apify API client for Python).
  2. A free Apify account, which you can create with an email address or a GitHub login.
  3. Your Apify API token, available in Apify Console under Settings > API & Integrations.

Install the client and export your token:

pip install -U "apify-client~=3.0"
export APIFY_API_TOKEN="your_apify_token_here"
Enter fullscreen mode Exit fullscreen mode

No browser, proxy setup or parsing code is needed on your machine because the Actor handles fetching on Apify.


What you'll achieve

You will search spitogatos.gr for homes to rent in central Thessaloniki up to 900 EUR per month, read the dataset rows in Python and print a two-line summary per listing that computes monthly rent per square metre from two fields.

You will also see how the Actor handles reduced-price flags, missing descriptions, offset map pins and floor codes so your downstream analysis or spreadsheet import handles nulls cleanly.

The same script works for homes, apartments or land for sale across any Greek area slug Spitogatos publishes, from athens-center to cyclades, chalkidiki or zante. Only the input dictionary changes between runs.


Step 1: Open Spitogatos Scraper

Go to the Spitogatos Scraper page on Apify Store. The Try for free button opens the Actor in Apify Console if you want to run it interactively. For the Python workflow in this guide, you only need its identifier: reventadata/spitogatos-scraper.


Step 2: Configure your search inputs

The Actor takes these main parameters:

  • listingType: sale (default) or rent.
  • category: homes (default), apartments or land. Rent searches support homes only; combining rent with apartments or land fails when the Actor starts.
  • area: a Spitogatos area slug copied from a search URL after the category, such as athens-center, thessaloniki-center or cyclades (default athens-center).
  • reducedPriceOnly: boolean (default false), valid for sale searches only.
  • minPrice, maxPrice: optional integer limits in EUR (monthly rent when listingType is rent).
  • minAreaM2, maxAreaM2: optional integer limits in square metres.
  • maxItems and maxPages: row cap (default 1000) and page cap (default 200, with 30 listings per page).

Invalid listingType or category values are refused by the input schema before the run starts. Note that minPrice, maxPrice, minAreaM2 and maxAreaM2 filter rows after each page is fetched rather than on Spitogatos' servers, so rows lacking a price or area are dropped when those limits are set.

For reduced-price homes for sale in central Athens, the input looks like this:

{
    "listingType": "sale",
    "category": "homes",
    "area": "athens-center",
    "reducedPriceOnly": true,
    "maxPrice": 250000,
    "minAreaM2": 60,
    "maxItems": 30,
    "maxPages": 5
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Run it from Python

This script starts a run, waits for it to finish, reads the dataset items and prints a summary with EUR per square metre:

import os
from apify_client import ApifyClient

# 1. Initialise the client with your token from the environment
client = ApifyClient(os.environ["APIFY_API_TOKEN"])

# 2. Describe the search: rentals in Thessaloniki center up to 900 EUR/month
run_input = {
    "listingType": "rent",
    "category": "homes",
    "area": "thessaloniki-center",
    "maxPrice": 900,
    "maxItems": 20,
    "maxPages": 1,
}

# 3. Start the Actor and wait for it to finish
run = client.actor("reventadata/spitogatos-scraper").call(run_input=run_input)
if run is None or run.status != "SUCCEEDED":
    status = run.status if run else "unknown"
    raise SystemExit(f"Run did not succeed (status: {status}).")

# 4. Read the rows from the run's default dataset
items = client.dataset(run.default_dataset_id).list_items().items
print(f"Retrieved {len(items)} listings.\n")

# 5. Print two lines per listing, computing EUR per square metre in Python
for item in items:
    price, area = item.get("price"), item.get("areaM2")
    per_m2 = f"{price / area:.1f} EUR/m2" if price and area else "no area"
    print(f"{item.get('location')} | {price} EUR/month | {area} m2 | {per_m2}")
    print(
        f"  bedrooms: {item.get('bedrooms')}, floor: {item.get('floor')}, "
        f"reduced: {item.get('priceReduced')}, agency: {item.get('agencyName')}"
    )
Enter fullscreen mode Exit fullscreen mode

Keep maxPages small while testing because each page triggers a paid unblocking fetch on the Actor's side.


Step 4: Inspect the output and one full row

On the live Thessaloniki capture dated 14 November 2026 (20 listings returned in 24 seconds), the script begins with:

Voulgari - Agios Eleftherios - Ntepo (Voulgari - Ntepo - Martiou) | 440 EUR/month | 50 m2 | 8.8 EUR/m2
  bedrooms: 1, floor: LG, reduced: False, agency: Lampraki real estate
Ippokratio (Faliro - Ippokratio) | 660 EUR/month | 72 m2 | 9.2 EUR/m2
  bedrooms: 2, floor: 3, reduced: True, agency: IKIMA Real Estate
Terpsithea (Ano Poli) | 390 EUR/month | 37 m2 | 10.5 EUR/m2
  bedrooms: 1, floor: LG, reduced: False, agency: IKIMA Real Estate
Enter fullscreen mode Exit fullscreen mode

Notice the second listing has reduced: True. Here is that complete row from the same capture, with imageIds cut to its first three entries:

{
  "id": "21029205",
  "url": "https://www.spitogatos.gr/en/property/2121029205",
  "listingType": "rent",
  "category": "house",
  "subtype": 1,
  "price": 660,
  "currency": "EUR",
  "priceReduced": true,
  "pricePreReduction": 690,
  "priceChangePercentage": 4,
  "areaM2": 72,
  "bedrooms": 2,
  "bathrooms": 1,
  "floorCode": 8,
  "floor": "3",
  "location": "Ippokratio (Faliro - Ippokratio)",
  "latitude": 40.611877,
  "longitude": 22.958422,
  "locationIsExact": true,
  "description": "For rent 72 sq.m. apartment on the 3rd floor of a building with an elevator, in Faliro.\n\nThe property was built in 1978 and is in very good condition. It is front-facing, dual-aspect, and very bright. It consists of a spacious living room, 2 bedrooms, separate kitchen, bathroom, storage room, and...",
  "isNewDevelopment": false,
  "hasVideo": false,
  "hasVirtualTour": false,
  "imageUrl": "https://m3.spitogatos.gr/371683796_300x220.jpg?v=20130730",
  "imageCount": 14,
  "imageIds": [
    371683796,
    371683797,
    371683798
  ],
  "adType": "vip",
  "agencyId": 17152,
  "agencyName": "IKIMA Real Estate",
  "agencyProfileUrl": "https://www.spitogatos.gr/en/find-agents/IKIMA-Real-Estate/17152",
  "agencyLogoUrl": "https://m2.spitogatos.gr/214960453_100x50.jpg",
  "agencyMembership": "expert",
  "firstPublishedAt": "2026-10-08 17:41:50",
  "uploadedAt": "2026-10-08 17:41:50",
  "modifiedAt": "2026-11-14 15:32:00",
  "searchUrl": "https://www.spitogatos.gr/en/to_rent-homes/thessaloniki-center"
}
Enter fullscreen mode Exit fullscreen mode

Understanding your results

Each row carries 36 fields extracted from Spitogatos' search results page without opening individual property pages:

  • Price and reductions: price, currency (EUR), priceReduced, pricePreReduction and priceChangePercentage. On non-reduced listings (19 of 20 in the capture), pricePreReduction and priceChangePercentage are null.
  • Size and rooms: areaM2, bedrooms (0 when the listing card shows no badge) and bathrooms.
  • Floor: floorCode holds the site's numeric code, while floor maps known codes to -1, LG, G, UG or 1 through 8 (null if missing or unmapped).
  • Location: location, latitude, longitude and locationIsExact (false on 5 of 20 rows where Spitogatos offsets the map pin).
  • Text and media: description is truncated by the site around 300 characters and was null on 7 of 20 rows; imageUrl, imageCount, imageIds, hasVideo and hasVirtualTour cover media.
  • Agency and timestamps: agencyId, agencyName, agencyProfileUrl, agencyLogoUrl and agencyMembership, plus site-local timestamps (firstPublishedAt, uploadedAt, modifiedAt). Agency phone numbers are not in search results and are not collected.

Pro tips

  1. Deduplication is built in per run. Spitogatos sorts by ranking score, so listings can shift pages mid-walk. The Actor tracks pushed id values and skips repeats automatically.
  2. Expect post-fetch filtering. Because minPrice, maxPrice, minAreaM2 and maxAreaM2 apply after each page arrives, pair narrow filters with a sensible maxPages cap so you do not walk hundreds of pages for a handful of matches.
  3. Check locationIsExact before mapping. Five of the 20 Thessaloniki rows had offset coordinates (false), so filter on locationIsExact when you need street-level precision.
  4. Watch for 0 and null values. bedrooms can be 0 (1 of 20 rows in the capture) and description can be null (7 of 20).
  5. Compute ratios yourself. There is no built-in price-per-square-metre field; divide price by areaM2 after checking both are present.
  6. Respect site terms. Spitogatos' legal terms prohibit copying, reproducing or commercial use without prior consent, so verify your compliance with those terms and applicable data-protection law.

Troubleshooting

  • Zero rows, status SUCCEEDED. Spitogatos answered normally, but no listing matched your filters. The status message lists how many rows each filter dropped. Widen minPrice, maxPrice, minAreaM2 or maxAreaM2 and run again.
  • Zero rows, status FAILED. Common causes are an unknown area slug (which returns a 404 from Spitogatos), a malformed slug, combining rent with apartments or land, combining rent with reducedPriceOnly, or three consecutive failed page fetches.
  • Fewer rows than expected. Check the end of the run log with client.log(run.id).get() for Skipped N: ... lines and the final Pushed N row(s). total. For help, use the Actor's Store page.

Next steps

For current pricing and details, see the Store page.

To run searches automatically, save your inputs as an Apify task and attach a schedule using a cron expression. You can export datasets as JSON, CSV, XML, Excel, HTML table, RSS or JSONL (dataset docs), or trigger downstream jobs with a webhook whose resource payload includes defaultDatasetId.

What Greek areas or property filters are you planning to monitor? Drop a comment! 💬

About the author: reventadata builds and publishes data extraction Actors on the Apify Store.

Top comments (0)