# Fotocasa Scraper: Spain Property Listings & Price Drops (`datasmithlab/fotocasa-property-scraper`) Actor

Spain homes for sale and rent from Fotocasa with price per m², postcode and coordinates. Track new listings, price drops and removed homes between runs. Search by city, district or URL; bedrooms, full description, agency and photos.

- **URL**: https://apify.com/datasmithlab/fotocasa-property-scraper.md
- **Developed by:** [Datasmithlab](https://apify.com/datasmithlab) (community)
- **Categories:** Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.60 / 1,000 listings

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Fotocasa Scraper: Spain Property Listings & Price Drops

Export homes for sale and to rent from [Fotocasa](https://www.fotocasa.es) by **city, district or province**, or by pasting a search URL. Every row comes with price, floor area in m², **price per m²**, bedrooms, bathrooms, postcode, coordinates, the **full description**, features, agency and photos. Nothing extra to open, no "details" surcharge.

Turn on **tracking** and every later run returns only what changed: **new listings, price drops, price rises and removed homes**, with the previous price and the % change. Schedule it daily and you have a price-drop alert for any area of Spain.

No proxy, no login. Private owners' name, phone and street are never collected, and their map position is rounded to about 100 m.

### What you get

- **Clean numbers.** `price: 1400`, `currency: "EUR"`, `pricePeriod: "month"` for rentals, `areaSqm: 85`, and `pricePerSqm` for homes for sale.
- **Location down to the street area.** `neighborhood`, `district`, `city`, `province`, `region`, `postcode`, `latitude`, `longitude` (with `exactLocation` telling you if the pin is exact).
- **Full description and features** (`exterior`, `for students`, `fireplace`...), flags investors care about: `isOccupied`, `isRentedWithTenants`, `isBareOwnership`, `isAuction`, `isNewConstruction`, `isTemporaryRental`.
- **Fotocasa's own price cut**: `priceReducedBy` when the advertiser lowered the price, even without tracking.
- **Advertiser.** `sellerType` is `agency` or `private`. For agencies you also get `agencyName`, `agencyPhone`, `agencyUrl`, the street and the exact pin when published.
- **Change tracking.** With *Only new, price-changed and removed listings* on, each row has `changeType`: `NEW`, `PRICE_DROP`, `PRICE_UP` or `REMOVED`, plus `previousPrice`, `priceChange`, `priceChangePct` and `firstSeenAt`. Unchanged listings are not returned and **not charged**.
- **Reports** in the run's key-value store: `SEARCHES` (location found, listings available, pages read, status per search) and `HEALTH`. If Fotocasa blocks the run or changes its page format, you get a clear message instead of an empty dataset.

### How to search

| You want | Set |
|---|---|
| Homes for sale in a city | **Cities, districts or provinces** `Valencia` |
| Rentals | **Buy or rent** `To rent` |
| One district | `Chamberí`, `Gràcia` (or paste the district's Fotocasa URL if the name is not found) |
| A whole province | `Barcelona provincia` |
| 2+ bedrooms under €1,200 | **Min bedrooms** `2`, **Max price** `1200` |
| Only private owners | **Advertiser** `Private owners only` |
| A search you already set up on Fotocasa | Paste it in **Fotocasa search URLs** (its price/room filters are ignored: set them in the input, the log warns you) |
| A daily price-drop alert | Turn on tracking and schedule the Actor daily |

A city name searches the city itself (`Madrid` = Madrid capital, about 8,500 rentals). If a name doesn't exist on Fotocasa, the `SEARCHES` report says so.

Price, bedroom, floor-area and advertiser filters are applied by the Actor while reading (Fotocasa's own URL filters are not used), so **you only pay for listings that match**. Narrow filters on a big city read more pages and take longer.

### Output example

```json
{
  "id": "fotocasa:191051285",
  "url": "https://www.fotocasa.es/es/alquiler/vivienda/sevilla-capital/aire-acondicionado-ascensor/191051285/d",
  "title": "Flat in Casco Antiguo, Sevilla",
  "transaction": "rent",
  "propertyType": "Flat",
  "price": 1400,
  "currency": "EUR",
  "pricePeriod": "month",
  "areaSqm": 85,
  "bedrooms": 2,
  "bathrooms": 2,
  "floor": 3,
  "neighborhood": "Casco Antiguo",
  "city": "Sevilla",
  "province": "Sevilla",
  "postcode": "41004",
  "latitude": 37.3891,
  "longitude": -5.9845,
  "features": ["exterior"],
  "sellerType": "agency",
  "agencyName": "Example Inmobiliaria",
  "imageCount": 24,
  "listedAt": "2026-10-07T08:59:55Z",
  "changeType": "NEW",
  "previousPrice": null,
  "firstSeenAt": "2026-10-08T00:00:30Z"
}
```

On a later run, the same home after a price cut comes back as `"changeType": "PRICE_DROP"` with `previousPrice`, `priceChange` and `priceChangePct` filled in.

### Pricing

You pay a small fee per run start and per listing row. The full description, features, coordinates and photos are included in every row. Rows filtered out, and unchanged listings in tracking mode, are free.

### Good to know

- **Tracking memory** is kept per set of searches and filters. Change the places or filters and the next run starts fresh (everything is `NEW` once).
- **`REMOVED`** means the home is no longer in that search on Fotocasa (usually sold, rented or withdrawn). It is reported only when every search was read to the end and the number of homes read matches Fotocasa's own count. Fotocasa reorders results while you read, so on large searches this check sometimes fails and that run simply reports no removals. Homes that only moved outside your filters are **not** marked as removed. If more than half of the remembered homes vanish at once, or a search suddenly shows 0 results, they are kept for the next run instead of being reported (that looks like an incomplete page, not real removals).
- **Floor area** is missing when the advertiser didn't publish it; a floor-area filter leaves those homes out.
- **Phone numbers and emails** written inside descriptions are replaced with `[phone removed]` / `[email removed]`. Descriptions are otherwise the advertiser's own text.
- **Studios** have `bedrooms: 0`, so *Max bedrooms* `1` includes them.
- **If Fotocasa blocks the run**, the search is reported as `blocked` and the run stops. Try again later. The Actor reads Fotocasa's public search pages politely (about one request per second) and does not use proxies or any workaround.
- This Actor is independent and not affiliated with Fotocasa or Adevinta.

Found a bug or need a field? Open an issue on this page. We read every one and reply.

# Actor input Schema

## `locations` (type: `array`):

One per line, e.g. 'Madrid', 'Valencia', 'Chamberí', 'Barcelona provincia'. A city name searches the city itself; add 'provincia' for the whole province.

## `startUrls` (type: `array`):

Or paste home search pages from fotocasa.es (comprar / alquiler). URL filters such as price or rooms are not used; set them below instead.

## `transaction` (type: `string`):

Applies to the cities above. Pasted URLs keep their own.

## `minPrice` (type: `integer`):

Rent: monthly price.

## `maxPrice` (type: `integer`):

Rent: monthly price. Leave empty for no limit.

## `minBedrooms` (type: `integer`):

Leave empty for any.

## `maxBedrooms` (type: `integer`):

Leave empty for any.

## `minAreaSqm` (type: `integer`):

Homes without a published floor area are left out when set.

## `maxAreaSqm` (type: `integer`):

Leave empty for no limit.

## `sellerType` (type: `string`):

Only agencies, only private owners, or both.

## `onlyNewOrChanged` (type: `boolean`):

Remembers every listing per set of searches and filters. Later runs return only NEW listings, PRICE_DROP / PRICE_UP (with previous price and % change) and REMOVED listings (gone from the search). Schedule it daily for a price-drop alert.

## `maxItems` (type: `integer`):

Stop after this many rows (you are charged per row).

## `maxPagesPerSearch` (type: `integer`):

30 listings per page. Filters are applied after reading, so narrow filters may need many pages.

## Actor input object example

```json
{
  "locations": [
    "Valencia"
  ],
  "transaction": "sale",
  "sellerType": "any",
  "onlyNewOrChanged": false,
  "maxItems": 200,
  "maxPagesPerSearch": 1000
}
```

# Actor output Schema

## `listings` (type: `string`):

Listings (table view, JSON/CSV/Excel export).

## `allFields` (type: `string`):

Every field, including description, features, photos and coordinates.

## `searches` (type: `string`):

For each search: location found, listings available, pages read, status.

## `health` (type: `string`):

Empty-field ratios, failed searches, skipped listings.

# 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 = {
    "locations": [
        "Valencia"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("datasmithlab/fotocasa-property-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 = { "locations": ["Valencia"] }

# Run the Actor and wait for it to finish
run = client.actor("datasmithlab/fotocasa-property-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 '{
  "locations": [
    "Valencia"
  ]
}' |
apify call datasmithlab/fotocasa-property-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datasmithlab/fotocasa-property-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/kNYqmD7iKsifaOC3L/builds/r54Zuk7WLcqYRmbRH/openapi.json
