# Pisos.com Scraper - Spain Property Listings (`dami_studio/pisos-com-scraper`) Actor

Scrape property listings from pisos.com across Spain, for sale and to rent. Every row carries asking price, price per m2, built area, rooms, bathrooms, floor, neighbourhood, latitude and longitude, advertiser type, agency, photo count and the listing URL. Flat price per listing, no tiers.

- **URL**: https://apify.com/dami\_studio/pisos-com-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Real estate
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.85 / 1,000 listing returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## Pisos.com Scraper

Pulls property listings off **pisos.com**, Spain's agency-fed property portal, and hands you back
clean rows: asking price, price per square metre, built area, rooms, bathrooms, floor, the
neighbourhood and district, latitude and longitude, who is advertising it, how many photos it has,
and the link back to the advert.

Works on both sides of the market, **venta** (for sale) and **alquiler** (to rent), and on flats,
houses, penthouses, duplexes, studios, lofts, detached houses and country properties.

No account, no cookie, no API key. You give it a place and it starts returning rows.

***

### What you get, per listing

One row per advert. Here is a real one, trimmed:

```json
{
  "listingId": "65098413759.100500",
  "url": "https://www.pisos.com/comprar/piso-gaztambide-65098413759_100500/",
  "title": "Piso en calle de la Princesa, 76",
  "operation": "venta",
  "propertyType": "piso",
  "price": 1600000,
  "priceCurrency": "EUR",
  "pricePerM2": 10323,
  "areaM2": 155,
  "rooms": 3,
  "bathrooms": 3,
  "floor": "4ª planta",
  "neighbourhood": "Gaztambide",
  "district": "Chamberí",
  "municipality": "Madrid Capital",
  "province": "Madrid",
  "country": "ES",
  "latitude": 40.4325,
  "longitude": -3.71735,
  "advertiserType": "professional",
  "advertiserName": "Princess Homes",
  "advertiserUrl": "https://www.pisos.com/inmobiliaria-princess_homes/",
  "phone": "919384534",
  "isNewDevelopment": false,
  "imageCount": 34,
  "imageUrl": "https://fotos.imghs.net/mm-wp/1005/101/1005_2935432214101_1_202606051854533172743.jpg",
  "descriptionSnippet": "No cobramos honorarios al comprador. Espectacular propiedad de alta gama...",
  "searchUrl": "https://www.pisos.com/venta/pisos-madrid/",
  "page": 1,
  "position": 1,
  "scrapedAt": "2026-09-19T22:14:03.119Z"
}
```

Rental rows look the same, with `price` as the monthly rent and `pricePerM2` as rent per square
metre. So a `pricePerM2` of `15` on a rental is €15/m² a month, not a typo.

#### How often each field actually arrives

Measured over 305 listings across ten different searches (Madrid, Barcelona, Valencia, Bilbao,
Málaga, Granada, Marbella, Elche, San Sebastián, A Coruña), sale and rent mixed:

| Field | Filled |
|---|---|
| `listingId`, `url`, `title`, `price`, `municipality`, `province`, `latitude`, `longitude`, `propertyType`, `operation`, `descriptionSnippet`, `bathrooms` | 100% |
| `imageUrl`, `imageCount` | 99.7% |
| `areaM2`, `pricePerM2` | 96.7% |
| `advertiserType`, `phone` | 95.1% |
| `rooms` | 93.4% |
| `advertiserName`, `advertiserUrl` | 89.2% |
| `neighbourhood` | 88.9% |
| `floor` | 43.6% |
| `district` | 42.3% |

`floor` and `district` are low because plenty of listings genuinely have neither. A detached house
has no floor number, and most Spanish towns are not split into districts the way Madrid and Bilbao
are. Everything else that comes back empty is empty on pisos.com too.

***

### Input

The short version: put a place in **Where**, pick sale or rent, press Start.

```json
{
  "location": "madrid",
  "operation": "venta",
  "propertyType": "pisos",
  "maxItems": 200
}
```

With filters and rent:

```json
{
  "location": "valencia",
  "operation": "alquiler",
  "propertyType": "pisos",
  "minPrice": 800,
  "maxPrice": 1500,
  "sortBy": "newest",
  "maxItems": 500
}
```

Or skip the form entirely. Set the search up on pisos.com however you like, using any filter it offers
including ones this form does not expose, then paste the address:

```json
{
  "startUrls": [
    "https://www.pisos.com/venta/chalets-marbella/desde-500000/",
    "https://www.pisos.com/alquiler/pisos-barcelona/fecharecientedesde-desc/"
  ],
  "maxItems": 1000
}
```

Single listing pages work in `startUrls` too, if you have a list of adverts you want re-checked.

| Field | What it does |
|---|---|
| `location` | A pisos.com place slug. Provinces (`madrid`, `alicante`), towns (`marbella`, `elche_elx`). |
| `operation` | `venta` for sale, `alquiler` for rent. |
| `propertyType` | `pisos`, `casas`, `aticos`, `duplexs`, `estudios`, `lofts`, `chalets`, `fincas_rusticas`. |
| `minPrice` / `maxPrice` | Euros. Monthly rent when `operation` is `alquiler`. |
| `sortBy` | `relevance`, `newest`, `cheapest`, `priciest`. Pick `newest` if you are polling for changes. |
| `maxItems` | Stop after this many listings. You pay per listing returned. |
| `startUrls` | Paste pisos.com search or listing URLs. Paginates from wherever the URL points. |
| `includeDetails` | Fetch each listing's own page as well. See below. |

#### Place names

pisos.com spells some places its own way, and they are not always guessable. `elche` is a 404,
`elche_elx` is the real one. The Actor maps the usual spellings for you, so `elche`, `Elx`,
`San Sebastián`, `La Coruña`, `Palma` and `Vitoria` all land on the right page. 51 of the largest
Spanish cities and provinces were checked against the live site one by one.

If you ask for somewhere it does not recognise, you get a row telling you so and **nothing is
charged**. The fix is always the same: open the search on pisos.com, copy the address bar, put it in
`startUrls`.

#### `includeDetails`

Off by default. Every listing then comes from the results grid, which is where the price, size,
rooms, location, coordinates, agency and lead photo live.

Turn it on and the Actor also opens each listing's own page and adds:

- `features`: the full list, as pisos.com writes it: `"Superficie construida: 155 m²"`,
  `"Antigüedad: Más de 50 años"`, `"Ascensor"`, `"Aire acondicionado"`, `"Orientación: Oeste"`.
  Between 3 and 28 entries in testing, 12 on a typical flat.
- `description`: the whole advert text rather than the 150-character snippet.
- `images`: every photo, not the two the results page preloads. Often 20 to 75 of them.
- `reference`: the agency's own reference code.
- `lastUpdated`: the date pisos.com says the advert was last touched, as `YYYY-MM-DD`.
- `pricePerM2`: read off the page instead of worked out from price ÷ area.
- `advertiserAddress`: the agency's street address.

That worked on 20 out of 20 listings tested. It costs you nothing extra per listing, because the price is
the same either way. It is one more request per row, so a run takes roughly ten times longer.
Leave it off for a broad market sweep; turn it on when you are looking at a shortlist.

***

### What this does not do

Worth reading before you buy, because two of these will shape how you use it.

**pisos.com stops at page 100.** Every search, no matter how big. 30 listings a page, so **3,000
listings is the deepest any single search goes**. Madrid flats for sale advertise 15,955 results,
of which you can reach 3,000. Ask for page 101 and the site quietly hands back page 1 again at HTTP
200; the Actor spots the repeat, stops, and writes you an uncharged note saying so. To go wider,
split the search: run each province separately, or slice by price band with `minPrice`/`maxPrice`.

**pisos.com ignores filters it does not understand, silently.** Put a nonsense word in a search URL
and it answers HTTP 200 with the *unfiltered* set, the full 15,955 instead of your price range.
Handing you that as though it were your filter would be quietly wrong, so when you set a price bound
the Actor checks the result count against the unfiltered count first. If they match, the filter was
dropped, and you get an uncharged row saying so rather than 500 listings you did not ask for.

**The upper price bound is approximate.** That is pisos.com's own behaviour, not ours: ask for a
€300,000 ceiling and the odd €320,000 listing still comes back. We return what the site returns
rather than silently dropping rows you were shown.

**`pricePerM2` is calculated unless `includeDetails` is on.** Grid rows compute it as price ÷ built
area, which can land a euro either side of the figure pisos.com prints because of rounding. Detail
rows use the site's own number.

**Grid rows carry two photos, not all of them.** `imageCount` is the real total, and that part is
accurate, but the `images` array only holds what the results page preloads. `includeDetails` fills
it in properly.

**No energy rating, no floor plans, no price history, no sold prices.** pisos.com does not publish
sold prices at all, and the energy label sits behind a component the logged-out page does not render.

**`advertiserType` is nearly always `professional`.** That is not a bug. pisos.com is fed by estate
agencies, and private listings are rare on it. If you specifically want private sellers, this is the
wrong portal.

**Results pages sometimes carry a few extra cards.** New-build promotions get slotted into the grid,
which is why a page can return 33 rows instead of 30. They are real pisos.com listings and they are
flagged with `isNewDevelopment: true` so you can drop them.

***

### What it costs you

Pay per event. **One charge per listing row delivered**, at the figure on the Pricing tab
above, plus the standard Apify run-start event.

Free, every time:

- the sample row an empty input produces
- every diagnostic row: unknown place, filter ignored, page refused, listing gone, depth limit
- the extra requests `includeDetails` makes, because enrichment does not cost you a second charge

So a run that finds nothing costs you the run-start event and nothing else, and a run that hits a
problem tells you what happened without billing you for the telling.

`maxItems` is a hard stop. Set it to 200 and you are charged for at most 200 listings.

***

### Speed

A results page comes back in about 0.8 seconds and yields 30 listings, so a 1,000-listing run is
roughly 34 requests and finishes in well under a minute. With `includeDetails` on, add about
0.6 seconds per listing; the Actor fetches five at a time, so 1,000 enriched listings takes a few
minutes rather than a few seconds.

***

### FAQ

**Does this need a pisos.com account or an API key?**
No. It reads the public, logged-out pages. There is nothing to sign up for and no key to paste.

**Can I get every listing in Spain?**
Not in one run. pisos.com caps any single search at 100 pages, so 3,000 listings. Run province by
province, or split by price band, and you can cover a lot more.

**How do I find the right place name?**
Type the city in plain English or Spanish (`madrid`, `Valencia`, `San Sebastián`) and it will
usually resolve. If it does not, search on pisos.com and paste the URL into `startUrls`. That always
works, because it is the site's own address.

**Can I filter on things this form does not offer, like a garage or a terrace?**
Yes, through `startUrls`. Set the filters up on pisos.com, copy the address, paste it in. The Actor
paginates whatever URL you give it.

**Does it return coordinates?**
Yes, latitude and longitude on every row, straight from the structured data pisos.com publishes on
its own results pages. It was 100% in testing.

**Are the phone numbers real?**
They are the contact numbers pisos.com shows on the results page, which for an agency listing is the
agency's line. 95% of rows had one.

**How current is the data?**
It is read live when the run starts. There is no cache in between. With `includeDetails` on,
`lastUpdated` tells you when the advertiser last touched the advert.

**What happens if pisos.com changes or blocks the Actor?**
You get uncharged diagnostic rows naming what came back instead of a results page, and the run still
ends as succeeded rather than failing on you. Nothing is charged for a page that did not deliver
listings.

**Can I run this on a schedule?**
Yes. Pair it with `sortBy: "newest"` and a modest `maxItems` and you get a cheap daily check of what
is new in an area.

***

### A note on what this is for

Market analysis, comparables, rent-versus-buy maths, feeding a property CRM, watching a
neighbourhood before you buy in it. If what you need is the actual sale price a flat went for,
pisos.com does not publish that and neither does this. Asking prices are asking prices.

# Actor input Schema

## `location` (type: `string`):

A pisos.com place slug: a province like madrid, barcelona, alicante, or a town like marbella, elche\_elx, san\_sebastian\_donostia. Common spellings are mapped for you. If a place 404s, open the search on pisos.com and paste the address into Search URLs instead.

## `operation` (type: `string`):

venta is for sale, alquiler is to rent.

## `propertyType` (type: `string`):

pisos covers flats and apartments and is the broadest choice. The others narrow the search to that one type.

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

Stop after this many listings across every search. You are charged per listing returned. pisos.com serves 30 per page and stops at page 100, so a single search tops out around 3,000.

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

Optional, and the most reliable way in: set up the search on pisos.com with whatever filters you want, then paste the address here. Search pages and single listing pages both work. Paginates from wherever the URL points.

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

Lower price bound. For rentals this is monthly rent. Leave empty for no lower bound.

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

Upper price bound. For rentals this is monthly rent. Leave empty for no upper bound.

## `sortBy` (type: `string`):

Which listings come first. Newest first is the one to pick if you are polling for changes.

## `includeDetails` (type: `boolean`):

Off, every listing comes from the results grid: price, size, rooms, location, coordinates, agency, main photo. On, each listing also gets its own page fetched for the full feature list, the whole description, every photo, the agency reference and the date the advert was last updated. Same price per listing either way, but the run takes roughly ten times longer.

## `proxyConfiguration` (type: `object`):

Optional. The Actor reaches pisos.com fine without one, so leave this alone unless you need to exit from a particular network.

## Actor input object example

```json
{
  "location": "madrid",
  "operation": "venta",
  "propertyType": "pisos",
  "maxItems": 50,
  "startUrls": [],
  "sortBy": "relevance",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

One row per pisos.com listing: price, price per m2, built area, rooms, bathrooms, floor, neighbourhood and district, municipality and province, latitude and longitude, advertiser type and agency, photo count and listing URL. Turn on Include full detail pages to add the feature list, the full description, the agency reference and the date the advert was last updated. Empty input writes one uncharged sample row; an unknown location, a search pisos.com ignored or a page it refused writes an uncharged diagnostic row instead.

# 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 = {
    "location": "madrid",
    "operation": "venta",
    "propertyType": "pisos",
    "maxItems": 50,
    "startUrls": [],
    "sortBy": "relevance",
    "includeDetails": false,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/pisos-com-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 = {
    "location": "madrid",
    "operation": "venta",
    "propertyType": "pisos",
    "maxItems": 50,
    "startUrls": [],
    "sortBy": "relevance",
    "includeDetails": False,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/pisos-com-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 '{
  "location": "madrid",
  "operation": "venta",
  "propertyType": "pisos",
  "maxItems": 50,
  "startUrls": [],
  "sortBy": "relevance",
  "includeDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call dami_studio/pisos-com-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/pisos-com-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/xpgOipzxnVe4SwgAH/builds/rrnInbIwCeXqB013m/openapi.json
