# Property Listing Scraper (`darknezz/property-listing-scraper`) Actor

Extract structured data from any real estate listing. Works with Zillow, Rightmove, Imobiliare, Idealista and more. Gets price, photos, bedrooms, area, coordinates, and agent details from any property page.

- **URL**: https://apify.com/darknezz/property-listing-scraper.md
- **Developed by:** [Oaida Adrian](https://apify.com/darknezz) (community)
- **Categories:** Real estate, Lead generation
- **Stats:** 3 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.10 / 1,000 property listings

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Property Listing Scraper — Universal Real Estate Data Extractor

Extract clean, structured real estate data from **any property website** — Zillow, Rightmove, Idealista, Imobiliare, Realtor, Domain, or a local agency site you found this morning. Point it at a single listing or a whole search-results page and get back one tidy JSON item per property: price, location, size, rooms, and images.

No per-site templates to maintain. The scraper reads the structured data that modern property sites already embed (JSON-LD, microdata, OpenGraph) and falls back to smart HTML parsing, so it keeps working when a site changes its layout.

### Why this Actor?

- **Works everywhere** — one Actor for every portal instead of one brittle scraper per site.
- **Search-page aware** — give it a search URL and it discovers and follows the individual listing links for you (up to `maxListings`).
- **Structured, null-tolerant output** — every field is always present; missing values come back as `null` rather than breaking your pipeline.
- **Fast** — up to 5 listings scraped concurrently.
- **No proxy setup** — residential proxy is enabled by default so geo-restricted portals load reliably.

### Who is this for?

- **Investors & analysts** — build price-per-m² datasets across neighbourhoods and portals.
- **PropTech & aggregators** — feed a normalised listing stream into your own app or database.
- **Agents & valuers** — pull comparables from any market on demand.
- **Data / RAG pipelines** — clean, typed property records ready for an LLM or analytics stack.

### Input

```json
{
  "startUrls": [
    { "url": "https://www.rightmove.co.uk/property-for-sale/find.html?searchLocation=London" },
    { "url": "https://www.zillow.com/homedetails/123-Main-St/12345_zpid/" }
  ],
  "maxListings": 50
}
```

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `startUrls` | array | required | Listing pages or search-results pages |
| `maxListings` | int | 50 | Max listings to follow from each search page |

Residential proxy is enabled by default to reach geo-restricted portals reliably.

### Output (one item per property)

```json
{
  "id": "12345_zpid",
  "title": "3-Bed Terraced House, Camden",
  "price": 725000,
  "currency": "GBP",
  "address": "42 Example Road, London NW1",
  "propertyType": "House",
  "areaSqm": 96,
  "bedrooms": 3,
  "bathrooms": 2,
  "images": ["https://.../photo1.jpg", "https://.../photo2.jpg"]
}
```

| Field | Type | Notes |
|-------|------|-------|
| `id` | string | Site's own listing ID when available |
| `price` / `currency` | number / string | Normalised numeric price + ISO currency code |
| `address` | string | Full address as published by the site |
| `propertyType` | string | House, apartment, land… |
| `areaSqm` | number | Area in square metres (converted when the site uses sq ft) |
| `bedrooms` / `bathrooms` | int | Counts; `null` when the site doesn't publish them |
| `images` | array | Direct image URLs |

Fields the site doesn't publish come back as `null` — never a wrong guess, and never a missing key.

### Worked example

A run against an Austin, TX search page produced these real records (values trimmed):

```json
[
  {
    "id": "20642605",
    "title": "4 Bed Single Family Residence, 11204 Trelawney Ln",
    "price": 695000,
    "currency": "USD",
    "address": "11204 Trelawney Ln, Austin, TX 78726",
    "propertyType": "Single Family Residence",
    "areaSqm": 222,
    "bedrooms": 4,
    "bathrooms": 3,
    "images": ["https://.../photo.jpg"]
  },
  {
    "id": "17420635",
    "title": "3 Bed Townhouse, Riverside",
    "price": 412000,
    "currency": "USD",
    "address": "8408 D-K Ranch Rd, Austin, TX 78744",
    "propertyType": "Townhouse",
    "areaSqm": 149,
    "bedrooms": 3,
    "bathrooms": 2,
    "images": ["https://.../photo.jpg"]
  }
]
```

Price is normalised to a plain number, currency is ISO-coded, and area is converted to square metres when the portal publishes square feet.

### Run it on a schedule or from your app

```bash
curl -X POST "https://api.apify.com/v2/acts/darknezz~property-listing-scraper/runs?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "startUrls": [{ "url": "https://www.idealista.com/venta-viviendas/madrid-madrid/" }], "maxListings": 100 }'
```

**Python:**

```python
import requests
resp = requests.post(
    "https://api.apify.com/v2/acts/darknezz~property-listing-scraper/runs",
    params={"token": "YOUR_TOKEN"},
    json={
        "startUrls": [{"url": "https://www.rightmove.co.uk/property-for-sale/find.html?searchLocation=London"}],
        "maxListings": 100,
    },
    timeout=300,
)
run_id = resp.json()["data"]["id"]
```

Schedule a daily run in the Apify Console to keep a market snapshot fresh, and pull new items from the dataset via the API.

### Pricing

Pay per event — a small fee **per property extracted**. No subscription, no minimums: scrape one comparable or ten thousand listings and pay only for what you pull.

### FAQ

**Which sites are supported?** Any property site that embeds standard structured data — that covers virtually every major portal (Zillow, Rightmove, Idealista, Realtor, Domain, Imobiliare, and thousands of regional sites). Unknown fields return `null` rather than a wrong guess.

**Can I give it a search page instead of individual listings?** Yes. The Actor detects search-results pages, follows the listing links, and scrapes each one up to your `maxListings` limit.

**Do I need my own proxies?** No. Residential proxy is configured by default so geo-restricted and bot-protected portals load reliably.

**What if a listing is missing the price or area?** The field comes back as `null`. Every output field is always present, so your downstream schema never breaks.

**Is it fast enough for large markets?** Up to 5 listings are scraped concurrently, and each search page's listing links are followed automatically — a 500-listing run typically finishes in a few minutes on standard Actor memory.

**How does the parser stay current when sites change their markup?** It reads the structured data modern portals already embed — JSON-LD, microdata and OpenGraph — and only falls back to HTML parsing when no structured block exists. When a site redesigns, the structured data usually stays, so the extractor keeps working without a code change.

**Do search-page results carry full detail?** Usually yes — most portals embed the full listing on the result card. A few portals render search pages with truncated cards, in which case some fields (notably `price` and `title`) can come back `null` on the first page until the detail page is reached. Re-running with the detail URL directly returns complete records.

**What about geo-restricted or bot-protected portals?** Residential proxy is enabled by default, which clears most geo-blocks. Portals behind aggressive anti-bot walls (JavaScript challenges, device fingerprinting) may still refuse datacenter egress; for those, running from a residential proxy group or a VPN-routed environment is the reliable path.

### Limitations

- **Search-page completeness varies by portal.** Detail pages always yield the full field set; search-result cards occasionally omit `price`/`title` on first render (see FAQ). The output schema is fixed and null-tolerant regardless.
- **Zip-code / postcode URLs are not supported as start URLs.** Several portals (notably Redfin) route postcode searches through a region-id system that requires a challenge-answered session; use a city or neighbourhood search URL instead.
- **Concurrency is deliberate.** Five concurrent requests keeps the actor polite to portals and avoids triggering rate-limit walls on large runs. If you need higher throughput, run multiple parallel runs against split URL lists.
- **`id` is the portal's own identifier when published.** Portals that don't expose an ID get a stable hash of the listing URL instead.

# Actor input Schema

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

URLs of property listing pages or search result pages.

## `maxListings` (type: `integer`):

Maximum number of listings to scrape from search result pages.

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

Proxy used to reach listing sites. Residential proxy is strongly recommended — major portals (Zillow, Idealista) block datacenter IPs.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.zillow.com/homedetails/1100-S-Hope-St-Los-Angeles-CA-90015/20746673_zpid/"
    }
  ],
  "maxListings": 50,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

No description

## `id` (type: `string`):

No description

## `title` (type: `string`):

No description

## `price` (type: `string`):

No description

## `currency` (type: `string`):

No description

## `address` (type: `string`):

No description

## `addressLocality` (type: `string`):

No description

## `addressRegion` (type: `string`):

No description

## `postalCode` (type: `string`):

No description

## `latitude` (type: `string`):

No description

## `longitude` (type: `string`):

No description

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

No description

## `areaSqm` (type: `string`):

No description

## `bedrooms` (type: `string`):

No description

## `bathrooms` (type: `string`):

No description

## `scrapedAt` (type: `string`):

No description

# 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 = {
    "startUrls": [
        {
            "url": "https://www.zillow.com/homedetails/1100-S-Hope-St-Los-Angeles-CA-90015/20746673_zpid/"
        }
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("darknezz/property-listing-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 = {
    "startUrls": [{ "url": "https://www.zillow.com/homedetails/1100-S-Hope-St-Los-Angeles-CA-90015/20746673_zpid/" }],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("darknezz/property-listing-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 '{
  "startUrls": [
    {
      "url": "https://www.zillow.com/homedetails/1100-S-Hope-St-Los-Angeles-CA-90015/20746673_zpid/"
    }
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call darknezz/property-listing-scraper --silent --output-dataset

```

## MCP server setup

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