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:
- Python 3.11 or newer (required by the 3.x Apify API client for Python).
- A free Apify account, which you can create with an email address or a GitHub login.
- 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"
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) orrent. -
category:homes(default),apartmentsorland. Rent searches supporthomesonly; combiningrentwithapartmentsorlandfails when the Actor starts. -
area: a Spitogatos area slug copied from a search URL after the category, such asathens-center,thessaloniki-centerorcyclades(defaultathens-center). -
reducedPriceOnly: boolean (defaultfalse), valid forsalesearches only. -
minPrice,maxPrice: optional integer limits in EUR (monthly rent whenlistingTypeisrent). -
minAreaM2,maxAreaM2: optional integer limits in square metres. -
maxItemsandmaxPages: 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
}
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')}"
)
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
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"
}
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,pricePreReductionandpriceChangePercentage. On non-reduced listings (19 of 20 in the capture),pricePreReductionandpriceChangePercentagearenull. -
Size and rooms:
areaM2,bedrooms(0 when the listing card shows no badge) andbathrooms. -
Floor:
floorCodeholds the site's numeric code, whilefloormaps known codes to-1,LG,G,UGor1through8(nullif missing or unmapped). -
Location:
location,latitude,longitudeandlocationIsExact(falseon 5 of 20 rows where Spitogatos offsets the map pin). -
Text and media:
descriptionis truncated by the site around 300 characters and wasnullon 7 of 20 rows;imageUrl,imageCount,imageIds,hasVideoandhasVirtualTourcover media. -
Agency and timestamps:
agencyId,agencyName,agencyProfileUrl,agencyLogoUrlandagencyMembership, plus site-local timestamps (firstPublishedAt,uploadedAt,modifiedAt). Agency phone numbers are not in search results and are not collected.
Pro tips
-
Deduplication is built in per run. Spitogatos sorts by ranking score, so listings can shift pages mid-walk. The Actor tracks pushed
idvalues and skips repeats automatically. -
Expect post-fetch filtering. Because
minPrice,maxPrice,minAreaM2andmaxAreaM2apply after each page arrives, pair narrow filters with a sensiblemaxPagescap so you do not walk hundreds of pages for a handful of matches. -
Check
locationIsExactbefore mapping. Five of the 20 Thessaloniki rows had offset coordinates (false), so filter onlocationIsExactwhen you need street-level precision. -
Watch for
0andnullvalues.bedroomscan be0(1 of 20 rows in the capture) anddescriptioncan benull(7 of 20). -
Compute ratios yourself. There is no built-in price-per-square-metre field; divide
pricebyareaM2after checking both are present. - 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. WidenminPrice,maxPrice,minAreaM2ormaxAreaM2and run again. -
Zero rows, status
FAILED. Common causes are an unknown area slug (which returns a 404 from Spitogatos), a malformed slug, combiningrentwithapartmentsorland, combiningrentwithreducedPriceOnly, or three consecutive failed page fetches. -
Fewer rows than expected. Check the end of the run log with
client.log(run.id).get()forSkipped N: ...lines and the finalPushed 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)