Idealista Scraper - Spain, Italy and Portugal Property Listings
Pricing
from $0.50 / 1,000 listings
Idealista Scraper - Spain, Italy and Portugal Property Listings
Collect Idealista houses and flats for sale or rent in Spain, Italy and Portugal: price, price drops, size, rooms, floor, agency and listing URL.
Pricing
from $0.50 / 1,000 listings
Rating
0.0
(0)
Developer
Amadeusz
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
18 hours ago
Last modified
Categories
Share
Idealista Scraper
Collect houses and flats for sale or rent from Idealista in Spain (idealista.com), Italy (idealista.it) and Portugal (idealista.pt). Type a province or town name, add the filters of the Idealista search form, or paste a search URL copied from Idealista. Every listing is checked against your filters, de-duplicated and returned as clean JSON, ready for CSV, Excel, the API or AI agents.
Run Idealista Scraper · Input schema · Output schema
What you get
- Three markets: Spain, Italy and Portugal, houses and flats for sale or for rent.
- Place names instead of codes:
locationtakes a province, municipality, island, coast or region name (Madrid,Valencia,Milano,Lisbon,Mallorca,Costa del Sol,Toscana,Algarve, accents optional) or the path from the Idealista address bar (barcelona/eixample). A misspelled name ends the run with the closest names. AlistLocationsrun (start fee only) lists every name and path. - Every option of the search form: property type, bedrooms (rooms in Italy), bathrooms, condition, lift, terrace, parking, pool, floor, energy rating, floor plan, virtual tour and more. A
listFiltersrun (start fee only) lists every key per country and operation; an unknown key gets the closest keys in the error message. - Price and size ranges, publication date (
publishedWithin), 14 sort orders, search URLs copied from Idealista (startUrls), several searches in one run with shared de-duplication. - Verified results: the filters and the sort order are checked against the search Idealista actually applied before anything is charged. A filter Idealista would silently ignore stops the run with an explanation instead of returning wrong data, and a card that contradicts your price, size, bedroom or lift filter is dropped without a charge.
- No duplicates: the same listing is returned once per run, and you are charged once.
- Price drops from the card:
previousPriceandpriceDropPercent, plus price per m², floor, lift, parking and the agency. - Optional details (
enrichDetails): full description, all features, bathrooms, condition, year built, energy certificate, community costs, address, coordinates, all photos, floor plans, update date and advertiser. - Monitoring (
mode: monitor): each run returns only listings not reported by earlier runs, plus achangerecord when the price of a reported listing changed. - Estimate (
countOnly): how many listings match your search, without listing charges. - More than 1,800 results per search: one Idealista search shows at most 1,800 listings, so the Actor splits larger searches by price automatically.
- One date format (UTC,
YYYY-MM-DDTHH:mm:ssZ) in every field.
Quick start
- Choose Sale or rent and type a Location, for example
Madrid,Valencia,MilanoorLisbon. - Add price, size or filters if you need them.
- Set Max items (the limit of listings returned and charged).
- Click Start. The default input (
location: Madrid,operation: sale,maxItems: 100) is a working example. - Open the Overview table, export JSON, CSV or Excel, or read the data through the API.
Not sure how many listings match? Start with Count only.
Pricing
Pay per event:
| Event | Price | When |
|---|---|---|
listing | $0.50 / 1000 | One per listing returned |
listing-details | $1.00 / 1000 | One per listing when enrichDetails is on and the listing page was read |
listing-change | $0.50 / 1000 | One per change record in monitor mode |
| Actor start | $0.005 per run | Once per run with the default 1 GB of memory (once more per extra GB) |
Duplicates, rejected cards and listings removed before their page was read are not charged. countOnly, listLocations and listFilters runs, empty runs and runs that end with an input or filter error return no listings and pay only the start. maxItems and your maximum cost per run are hard limits: the run stops when either is reached.
Cost of a run = $0.005 + listings × $0.0005 (plus $0.001 per enriched listing and $0.0005 per change record). For example, 1,000 listings with details cost $1.505.
Ready-to-use recipes
1. Flats for sale in a city
{"country": "es","operation": "sale","location": "Valencia","filters": ["flats", "lift", "bedrooms-2", "bedrooms-3"],"priceTo": 300000,"sizeFrom": 70,"maxItems": 200}
Keys of one group (bedrooms-2, bedrooms-3) mean either; keys of different groups must all apply.
2. Rentals in Milan published this week
{"country": "it","operation": "rent","location": "Milano","filters": ["rooms-2", "furnished"],"publishedWithin": "week","priceTo": 1800,"maxItems": 100}
Italy counts rooms (rooms-2 is a "bilocale"), Spain and Portugal count bedrooms (bedrooms-2).
3. Search URL copied from Idealista
{"startUrls": ["https://www.idealista.com/alquiler-viviendas/madrid-madrid/con-precio-hasta_1500,ascensor/"],"maxItems": 200}
The country, operation, location, filters and sort come from the URL. A URL part the Actor does not know is rejected instead of ignored.
4. Monitor new listings and price changes
{"country": "pt","operation": "sale","location": "Lisbon","filters": ["bedrooms-2"],"priceTo": 500000,"mode": "monitor","maxItems": 500}
Schedule it in Apify. The first run returns what is there; later runs return only new listings and change records.
Use it from the API
curl -X POST "https://api.apify.com/v2/acts/ziomixshot~idealista-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"location":"Madrid","operation":"rent","priceTo":1500,"maxItems":50}'
The same Actor is available to AI agents through the Apify MCP server: add ziomixshot/idealista-scraper to the tools of the server.
Input
The full description of every field is in the input form. A field the Actor does not know (for example a typo such as locaton) is named in a warning in the run log and ignored.
| Field | Description |
|---|---|
country | es, it or pt. Empty: taken from the location name; a location path without a country searches Spain. |
operation | sale (default) or rent. |
location | Province, municipality, zone (island, coast, comarca: Mallorca, Tenerife) or region name (Costa del Sol, Toscana, Algarve) (a municipality wins over a province, a zone or a region with the same name; of several municipalities the provincial capital wins and the log names the others; a country or province after a comma picks one: Porto, Spain, Arroyomolinos, Madrid), or the path from the Idealista address bar (madrid-madrid, barcelona/eixample, geo/costa-del-sol). A name the catalog does not have ends the run with the closest names, and so does a path Idealista does not know. |
filters | Filter keys of the search form in any letter case, for example lift, terrace, flats, bedrooms-2 (Spain, Portugal) or rooms-2 (Italy). listFilters shows the keys of each country and operation. |
publishedWithin | 24h, 48h, week or month. |
priceFrom, priceTo | Price in EUR, inclusive: sale price, or monthly rent for rent. |
sizeFrom, sizeTo | Built area in m², inclusive. |
sortBy | relevance, priceAsc, priceDesc, newest, oldest, biggestPriceDrop, pricePerM2Asc, pricePerM2Desc, sizeDesc, sizeAsc, floorDesc, floorAsc, privateAdvertisersFirst, professionalsFirst. An order a country or operation does not offer is refused. |
startUrls | Search URLs copied from Idealista. They replace all search, filter and sort fields above. |
maxItems | Hard limit of returned listings and charges, shared by all searches of the run (default 100, up to 100,000). |
enrichDetails | Read the page of each listing; each one also triggers the listing-details event. |
mode, seenStoreName, seenIdsKey | Monitoring, see below. |
countOnly | Return only the number of matching listings. |
listLocations | Return the provinces, municipalities, zones and regions (name, path, search URL) instead of listings; country and location narrow the list. When both listLocations and listFilters are set, only locations are listed. |
listFilters | Return the filter keys and sort orders instead of listings; country and operation narrow the list. |
Output
Every listing is one dataset item. The dataset has three views: Overview, Details and Price changes.
Fields of every listing item: recordType, id, url, title, country, operation, price, currency (EUR), previousPrice, priceDropPercent, pricePerM2, bedrooms (Spain, Portugal), rooms (Italy), sizeM2, floor, hasLift, exterior, parking, parkingPrice, publishedLabel, otherDetails, descriptionSnippet, tags, ribbon (for example "Finished new development"), photoCount, thumbnail, isProfessional, highlight (top, featured or null), agency (name, profileUrl, logoUrl), publishedAt, scrapedAt. A value Idealista does not show is null.
Added by enrichDetails: detailsFetched, description, descriptionTranslated, locationName, address (from street to province), latitude, longitude, locationId, builtAreaM2, usableAreaM2, bathrooms, condition, yearBuilt, orientation, heating, energy (rating A to G, plus A+, A1 to A4 and B- where the country uses them; consumptionKwhM2Year, emissionsKgCo2M2Year), communityCosts, priceNotes, features (every section of the listing page), images (url, tag), floorPlans, updatedAt, reference, advertiser (type, name, agencyName, agencyProfileUrl, agencyLogoUrl). A listing removed before its page was read is skipped and not charged (detailsUnavailable in the run status); a page that could not be read after all retries leaves the card record, charged only as listing (detailsFailed, run status PARTIAL).
Other record types, always free except change:
status: the run ended because of your input and returned no listings. Fields:status,message(what is wrong and how to fix it),details,scrapedAt.estimate: result ofcountOnly:estimatedTotal,sources(one count per search),note.location: result oflistLocations:country,kind(province,municipality,zoneorareafor a region),name,path,province(of a municipality or zone),url. Putnameorpathintolocation.filterandsort: result oflistFilters:country,operation,key,inputField(filtersorsortBy),group,label,slug.change: see monitoring.
Run status
The OUTPUT record of the key-value store describes the run: status (OK, PARTIAL, INVALID_INPUT, URL_NOT_FULLY_RESOLVED, FILTER_VERIFICATION_FAILED, SEEN_STATE_INVALID, UPSTREAM_ERROR), counters (duplicates, detailsUnavailable, detailsFailed, predicateViolations) and jobs[] per search (url, expectedTotal, pagesRead, failedPages, coverage, partitions, truncatedPartitions). Input and filter errors end the run without listings. A valid search Idealista has no listings for ends with OK and 0 listings. UPSTREAM_ERROR with a first page blocked or failed after all retries charges only the start: run it again later. PARTIAL means some pages or details failed after all retries; the returned records are still valid.
Monitoring new listings and price changes
mode: monitor returns only listings whose ID was not reported by earlier runs with the same seenStoreName and seenIdsKey, and a change record for every reported listing whose price changed since: recordType: "change", metric: "price", id, url, title, country, operation, currency, previous, current, delta and changedAt.
- Every run reads the whole search, because Idealista in Spain blocks the newest-first order.
sortBydoes not change what a monitor returns. - The first run returns every listing as new and stores its price. Later runs compare the price of each known card with the stored one, with no extra requests. A price that appears or disappears is stored without a
changerecord. maxItemslimits new listings and change records together. New listings or changes beyond the limit are kept for the next run.- Without
seenStoreNamethe Actor uses a store named after the search (idealista-monitor-..., shown in the run log), so repeating the same search keeps its history; the sort order and the order of filters do not change the name. Set a new name (letters, digits and hyphens) to share one history between different runs: the Actor creates that store and keeps access to it. A store created outside this Actor cannot be used, because the Actor runs with limited permissions; such a run stops before any listing is charged. - The state keeps the 500,000 newest listings with their last price. A corrupted state record ends the run with
SEEN_STATE_INVALIDinstead of starting over. - The state is saved when a run ends. Runs on the same store must not overlap (for example a schedule shorter than one run): overlapping runs report the same listings, and an aborted or timed-out run may not save what it reported.
Estimate (countOnly)
Returns one item with recordType: "estimate" and estimatedTotal, the number Idealista reports for each search. The first page of each search is read, so an unknown location or a filter Idealista ignores still stops the run. With several startUrls the result is the sum of the searches (overlapping searches are counted once per search). The monitor state is not touched. No listing is charged.
Limits and notes
- One search exposes at most 1,800 listings (60 pages of 30). For
maxItemsabove that, larger searches are read in price ranges. Sorting then applies only inside each range.truncatedPartitionsabove 0 means more than 1,800 listings with the same price that cannot be separated. - In Spain Idealista blocks the
newestsort order; usepublishedWithinto get recent listings. - The Actor reads the English version of each site.
descriptionTranslated: truemeans Idealista translated the description automatically. - Phone numbers are not collected: Idealista reveals them only after a click on the listing page.
- Idealista protects its sites with anti-bot systems. Runs use Apify residential proxies in the country of the site (included in the price). A page that cannot be read after all retries is counted in
failedPagesand the run endsPARTIAL. - The Actor uses the public web pages of Idealista, which are unofficial as an interface and can change without notice.
- The Actor reads public pages anonymously. It never logs in, never contacts advertisers, never saves searches and never changes anything on an account.
Legal notice
This Actor collects data that Idealista shows publicly. Idealista's terms of use restrict automated access to the site and the use of its content; you are responsible for using the data lawfully and in line with those terms. Listings contain personal data of advertisers (names of private advertisers and agents), so GDPR rules apply to how you store and use them. The Actor is not affiliated with Idealista.
Development
Stack: Node 20+, TypeScript strict (ESM), Apify SDK 3, Impit 0.7.5 (Chrome profile), cheerio 1.0.0-rc.12, Vitest, ESLint, Prettier. Apify RESIDENTIAL proxy of the country of the site. No database and no migrations.
npm cinpm run typecheck && npm run lint && npm run format:check && npm test
Local run (needs APIFY_TOKEN in the environment and RESIDENTIAL proxy on the account):
mkdir -p storage/key_value_stores/defaultecho '{"country":"es","location":"madrid-madrid","maxItems":60}' > storage/key_value_stores/default/INPUT.jsonnpm run start:dev
Results: storage/datasets/default/*.json and storage/key_value_stores/default/OUTPUT.json.
| Variable | Role |
|---|---|
APIFY_TOKEN | Apify token: locally it fetches the proxy password; in CI a GitHub secret for the deployment |
The location catalog (src/idealista/locations/*.json: provinces and municipalities with English names, zones linked from province pages and /geo/ regions) is built offline by a read-only script, about 190 requests through the RESIDENTIAL proxy:
npx tsx --env-file=<file with APIFY_TOKEN> scripts/build-locations.ts es it ptnpx prettier --write src/idealista/locations
A push to main runs .github/workflows/deploy-apify.yml: typecheck, lint, format, tests, schema validation and an Actor build tagged beta.
Backlog and decisions: docs/backlog.md, architecture: docs/diagram.md, routes and robots.txt: docs/ENDPOINTS.md, competitors and pricing: docs/pricing.md.