By the end of this tutorial you will have a Python script that pulls Italian property listings from Immobiliare.it into structured Python dictionaries: asking price or monthly rent in euros, surface area in square metres, rooms, floor, coordinates, feature tags, and the agency and agent contact details published on the ad. We use the Apify Actor Immobiliare.it Scraper (reventadata/immobiliare-scraper) and the official Apify Python client, so you do not need to run a browser, configure proxies, or parse HTML yourself.
If you are looking for a practical way to scrape Immobiliare.it across cities, provinces, or regions, this guide walks through configuring a rental search in Torino, inspecting the returned fields, and ranking rentals by monthly rent per square metre.
⏱️ Time estimate: Setup takes a few minutes.
This Actor is unofficial and not affiliated with Immobiliare.it.
Prerequisites
You need three things before running the script:
-
Python 3.11 or newer (
apify-client3.x requires Python 3.11+). - A free Apify account and your personal API token from the API & Integrations page in Apify Console.
- The official Apify Python client library installed in your environment:
pip install -U "apify-client~=3.0"
Export your API token in your shell so it stays out of your source code:
export APIFY_API_TOKEN="your_apify_token_here"
What you'll achieve
You will run Immobiliare.it Scraper from Python and receive one dictionary per property ad, ready to filter in Python or export as JSON, CSV, or Excel. Each record includes numeric and formatted prices, price reduction details when a listing has been discounted, room and bathroom counts, numeric floor area in square metres (surfaceM2), street address, neighbourhood, city, province, region, latitude and longitude, photo URLs, and public seller contact fields (agencyName, agencyPhone, agentName, agentPhone).
As a concrete example, we will search Torino rentals between 500 and 900 EUR per month with at least two rooms, then rank sample rows by monthly rent per square metre (price / surfaceM2) to spot the lowest cost per square metre.
Step 1: Open the Actor
Go to the Immobiliare.it Scraper page on Apify Store to review the input schema and documentation. Because we trigger the run from Python, you do not need to fill in the web form.
Step 2: Configure the search parameters
The Actor accepts the following input fields, sending your filter parameters directly in the immobiliare.it search URL:
| Field | Type | Default | What it does |
|---|---|---|---|
contract |
string | "sale" |
"sale" (vendita-case) or "rent" (affitto-case). |
location |
string | "torino" |
Location slug from an Immobiliare.it search URL, such as "torino", "milano", "torino-provincia", or "lombardia". |
minPrice, maxPrice
|
integer | unset | Optional price limits in EUR (prezzoMinimo, prezzoMassimo). On rent searches this is the monthly rent. |
minSurface |
integer | unset | Optional minimum surface in m² (superficieMinima). |
minRooms, maxRooms
|
integer | unset | Optional room (locali) limits (localiMinimo, localiMassimo). |
sortBy |
string | "relevance" |
"relevance" (site default) or "priceAscending" (criterio=prezzo&ordine=asc). |
maxItems |
integer | 1000 |
Maximum number of rows pushed to the dataset. |
maxPages |
integer | 80 |
Maximum result pages fetched (25 ads per page), capped at the site ceiling of 80. |
Both contract and sortBy are enum fields in the input schema: any value outside their allowed choices is refused before the run starts. To find a location slug, run a search on immobiliare.it and copy the path segment after /vendita-case/ or /affitto-case/.
Here is the first part of our Python script, defining a rental search in Torino between 500 and 900 EUR per month with at least 2 rooms:
import os
from apify_client import ApifyClient
# 1. Initialize the client with your token from the environment
client = ApifyClient(os.environ["APIFY_API_TOKEN"])
# 2. Describe the search: rent in Torino, 500 to 900 EUR/month, at least 2 rooms
run_input = {
"contract": "rent",
"location": "torino",
"minPrice": 500,
"maxPrice": 900,
"minRooms": 2,
"sortBy": "relevance",
"maxItems": 20,
"maxPages": 1,
}
Step 3: Run the Actor
Call the Actor with client.actor("reventadata/immobiliare-scraper").call(run_input=run_input). It starts the run on Apify, waits for completion, and returns the settled run object:
# 3. Start the Actor and wait for it to finish
run = client.actor("reventadata/immobiliare-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.")
Behind the scenes, the Actor fetches each search page through a third-party unblocking service that the Actor pays for (no proxy configuration or key of your own is required from you) and parses the <script id="__NEXT_DATA__"> payload (real-estate-list query).
Step 4: Print the first rows
Iterate through the dataset items to inspect titles, prices, surface area, rooms, floor, neighbourhood, and seller contact details:
for item in items[:3]:
price_text = item["priceText"] or "price on request"
surface_text = item["surfaceText"] or "surface n/a"
seller = item["agencyName"] or item["agentType"] or "seller n/a"
print(item["title"])
print(f' {price_text} | {surface_text} | rooms: {item["rooms"]} | floor: {item["floor"]}')
print(f' {item["neighbourhood"]}, {item["city"]} | {seller} ({item["agencyPhone"] or "no phone"})')
print(f' {item["url"]}')
Step 5: Inspect a sample row
Below is one record from the sample rows from a run on 2026-11-10 using the Torino rental input above (photos is shortened here from 44 thumbnail URLs to the first two):
{
"id": "122075776",
"url": "https://www.immobiliare.it/annunci/122075776/",
"title": "Trilocale via Francesco Cigna 44, Aurora, Torino",
"contract": "rent",
"adType": "ad",
"price": 800,
"priceText": "€ 800/mese",
"priceVisible": true,
"priceOriginalText": "€ 1.010",
"priceReductionPercent": 20.8,
"priceReducedOn": "29/10/2026",
"typology": "Trilocale",
"category": "Residenziale",
"isProject": false,
"projectUnits": null,
"isNew": false,
"isLuxury": false,
"rooms": "3",
"bedrooms": 2,
"bathrooms": "1",
"surfaceText": "85 m²",
"surfaceM2": 85,
"floor": "3",
"floorText": "3°",
"hasElevator": null,
"condition": "Ottimo / Ristrutturato",
"heating": "Centralizzato",
"features": [
"fibra ottica",
"porta blindata",
"balcone",
"impianto tv centralizzato",
"arredato",
"esposizione doppia"
],
"caption": "AFFITTASI TRILOCALE RISTRUTTURATO VICINO AL CENTRO DI TORINO",
"description": null,
"address": "Via Francesco Cigna",
"zone": "Aurora, Barriera di Milano, Rebaudengo",
"neighbourhood": "Aurora",
"city": "Torino",
"province": "Torino",
"region": "Piemonte",
"latitude": 45.0836,
"longitude": 7.6796,
"imageUrl": "https://pwm.im-cdn.it/image/1754187330/xxl.jpg",
"photoCount": 44,
"photos": [
"https://pwm.im-cdn.it/image/1754187330/xxs-c.jpg",
"https://pwm.im-cdn.it/image/1754187342/xxs-c.jpg"
],
"agencyId": 197355,
"agencyName": "Studiocasa",
"agencyType": "agenzia",
"agencyPhone": "011 1980 1344",
"agencyPhones": [
"011 1980 1344"
],
"agencyUrl": "https://www.immobiliare.it/agenzie-immobiliari/197355/studio-casa-torino/",
"agentName": "Guglielmo Marino",
"agentType": "agente immobiliare",
"agentPhone": "011 1980 1344",
"agentPhones": [
"011 1980 1344"
],
"transactionDate": null,
"searchUrl": "https://www.immobiliare.it/affitto-case/torino/?prezzoMinimo=500&prezzoMassimo=900&localiMinimo=2"
}
Step 6: Rank rentals by monthly rent per square metre
Because each row provides numeric price and surfaceM2, we can rank single-unit listings by monthly rent per square metre (price / surfaceM2), skipping new-development project tiles (isProject) and unpriced ads:
# 5. Rank rentals by monthly rent per square metre (EUR/m2)
ranked = [
item for item in items
if item["price"] is not None and item["surfaceM2"] and not item["isProject"]
]
ranked.sort(key=lambda i: i["price"] / i["surfaceM2"])
print("Cheapest per m2:")
for item in ranked[:5]:
per_m2 = item["price"] / item["surfaceM2"]
seller = item["agencyName"] or item["agentType"] or "n/a"
print(
f'{per_m2:5.1f} EUR/m2 | {item["price"]:4d} EUR | '
f'{item["surfaceM2"]:3d} m2 | rooms: {item["rooms"]} | '
f'{item["neighbourhood"]} | {seller}'
)
Running this ranking snippet over the sample rows from a run on 2026-11-10 prints:
Cheapest per m2:
9.4 EUR/m2 | 800 EUR | 85 m2 | rooms: 3 | Aurora | Studiocasa
10.0 EUR/m2 | 600 EUR | 60 m2 | rooms: 2 | Don Bosco | Studiocasa
10.0 EUR/m2 | 750 EUR | 75 m2 | rooms: 3 | Aurora | Studiocasa
13.3 EUR/m2 | 600 EUR | 45 m2 | rooms: 2 | Parella | Immobiliare Michielin
13.5 EUR/m2 | 850 EUR | 63 m2 | rooms: 3 | Cittadella | RE/MAX Vivere
Understanding your results
Each dataset row contains 53 fields declared in the Actor's dataset schema:
-
price,priceText,priceVisible: integer price in EUR (nullwhen"prezzo su richiesta"), formatted string, and visibility boolean. -
priceOriginalText,priceReductionPercent,priceReducedOn: populated when the listing displays a price reduction. -
surfaceM2,surfaceText,rooms,bedrooms,bathrooms,floor,floorText,hasElevator: size and layout fromproperties[0].roomsandbathroomsstay strings for values like"5+"or"2 - 5+";bedroomsis an integer only when the site gives a single number. -
agencyName,agencyType,agencyPhone,agencyPhones,agentName,agentType,agentPhone: public seller details (nullname and phone whenagentTypeis"privato").
New-development project tiles (isProject: true) group several units. Here is an excerpt from a Torino sale capture dated 2026-11-07:
{
"id": "97492960",
"title": "Appartamenti e Attici di nuova costruzione a Torino",
"contract": "sale",
"price": 298000,
"priceText": "€ 298.000 - € 1.009.000",
"typology": "Progetto",
"category": "Nuove costruzioni",
"isProject": true,
"projectUnits": 10,
"rooms": "2 - 5+",
"bedrooms": null,
"surfaceText": "66 m²",
"surfaceM2": 66,
"agencyName": "Residenza PARCO SUL PO",
"agencyType": "Impresa Edile",
"agencyPhones": [
"011 771 5000",
"366 509 2054"
]
}
Pro tips
-
Plan around the 80-page site ceiling: Immobiliare.it serves at most 80 pages (2,000 ads, at 25 per page) per search query; requesting page 81 returns HTTP 404 on the site even when
totalAdsis larger (such as 8,662 sale ads in Torino on 2026-11-07). WhentotalAdsexceeds 2,000, the Actor logs a warning on page 1. Split broad searches byminPrice/maxPrice,minSurface, orminRooms/maxRooms. -
Start with a small page cap: The Actor fetches pages one after another, so a many-page run takes longer. Keep
maxPages: 1andmaxItems: 20while testing your pipeline. -
Send filters in the search URL:
minPrice,maxPrice,minSurface,minRooms,maxRooms, andsortBy: "priceAscending"are applied by Immobiliare.it on the server before rows are returned. -
Separate auctions and project tiles in analysis: Sale searches can return property auctions (
contract: "auction",adType: "auction") and multi-unit project tiles (isProject: true). Filter onadType == "ad"andnot isProjectwhen comparing standard listings.
Troubleshooting
-
Run succeeded with 0 rows: When page 1 loads with
totalAds: 0and emptyresults, nothing matched on the site. The run endsSUCCEEDEDwith a status message; loosen your price, surface, or room limits. -
Run failed on page 1 with a location error: An HTTP 404 on page 1 means the
locationslug does not exist onimmobiliare.itand ends the runFAILEDnaming the slug. Copy the exact slug from a real search URL. -
Run failed before any request: Passing
minPrice > maxPrice,minRooms > maxRooms, negative numbers, or a malformed slug fails immediately. Values outside the enums forcontractorsortByare refused before the run starts. -
Reading the log: Call
client.log(run.id).get()to inspectSkipped N: already pushed (ids repeat across pages)andPushed N row(s)., or check the Store page.
Next steps
For current pricing and details, see the Store page.
To monitor a city or price band over time, save your input configuration as an Apify task and attach an Apify schedule on a cron expression.
Which Italian cities or property segments are you planning to analyze? Let us know in the comments!
Top comments (0)