# Zoopla Property Scraper (`tortuga/zoopla-scraper`) Actor

Scrape Zoopla listings for sale and to rent: price, address, bedrooms, property type, agent, listing date, images, floor area, EPC and price history. Works with any Zoopla search URL.

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

## Pricing

from $2.00 / 1,000 listing scrapeds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## Zoopla Property Scraper

Scrape Zoopla listings for sale and to rent: price, address, bedrooms, property type, agent, listing date, images, floor area, EPC and price history. Works with any Zoopla search URL.

Paste any zoopla.co.uk search page (for sale, to rent or new homes, with whatever filters you picked in the browser) or just type a location, and get every listing as clean JSON, CSV or Excel, or straight into your app through the API. Built for reliability: it reads the structured data Zoopla embeds in each page instead of scraping the visual layout, uses browser-grade TLS fingerprints, throttles itself and retries on a fresh proxy session, so runs finish instead of failing halfway. You pay only for the listings you get.

### What data does Zoopla Property Scraper extract?

Every listing from the search results gives you:

| Field | Description |
|---|---|
| `id`, `url` | Zoopla listing id and canonical listing URL |
| `title` | e.g. "2 bed flat for sale" |
| `listingType`, `section` | `sale` / `rent`; Zoopla section `for-sale`, `to-rent` or `new-homes` |
| `price`, `priceText`, `priceCurrency` | Numeric price in GBP plus the displayed text ("£1,400 pcm") |
| `pricePeriod`, `pricePerMonth`, `pricePerWeek` | Rentals: `pcm` or `pw`, with both monthly and weekly figures |
| `priceQualifier` | "Guide price", "Offers over", "Fixed price", ... |
| `priceReducedPercent`, `lastPriceChange` | Reduction badge from the card, when present |
| `address`, `postcodeOutcode` | Display address and outward postcode (e.g. `SW1A`) |
| `latitude`, `longitude` | Map coordinates |
| `propertyType` | flat, terraced, semi\_detached, detached, bungalow, end\_terrace, houseboat, ... |
| `bedrooms`, `bathrooms`, `receptions` | Room counts (null when not shown) |
| `floorAreaSqFt`, `floorAreaSqM`, `floorAreaSource` | Floor area when shown, and whether it came from the agent or an EPC estimate |
| `tenure`, `furnished`, `availableFrom` | Freehold / Leasehold / Share of freehold; rental furnishing and availability |
| `publishedOn`, `listedAt`, `statusLabel` | Listing date (ISO), "Just added" / "Reduced" style status |
| `isNewHome`, `isRetirement`, `isSharedOwnership`, `isAuction`, `chainFree`, `underOffer` | Flags |
| `isFeatured`, `isPremium`, `isExtendedResult` | Featured/premium placement; `isExtendedResult` marks nearby listings Zoopla adds when the exact area has no matches |
| `tags`, `highlights`, `buyerIncentives`, `summary` | Card badges, highlights, incentives (e.g. shared ownership) and the short summary |
| `images`, `imageCount`, `floorPlanCount`, `videoCount` | Photo URLs and media counts |
| `agent` | `{name, branchId, phone, url, logoUrl}` of the estate agent branch or developer |
| `viewingUrl` | Zoopla's "contact agent" page |
| `sourceUrl`, `scrapedAt` | The search URL the listing came from and the scrape timestamp |

Turn on **Include listing details** to also open each listing page and add:

| Field | Description |
|---|---|
| `description`, `features` | Full description (plain text) and the agent's key-features bullets |
| `postcode`, `streetName`, `region`, `county`, `postTown` | Full postcode and location breakdown where Zoopla publishes it |
| `epcRating`, `epcImageUrls` | EPC energy rating and EPC chart images |
| `councilTaxBand`, `tenure`, `serviceCharge`, `groundRent`, `leaseInfo` | Council tax band, tenure with lease length, and all "material information" fields |
| `utilities` | Water, heating, electricity, sewerage, broadband, parking, ... as displayed |
| `floorPlanUrls`, `brochureUrls`, `videoUrls`, `virtualTourUrls` | Floor plans, brochures, video and 3D tour links |
| `pricePerSqFt` | Price per square foot when Zoopla shows it |
| `priceHistory`, `firstListedAt` | `[{date, price, event, changePercent}]` from first listing through every reduction or increase |
| `historicSales` | Previous Land Registry sales of the property when Zoopla shows them |
| `nearbyStations`, `nearbySchools` | Closest stations (with lines, zone, distance) and schools (age range, distance) |
| `agent.brand`, `agent.isDeveloper` | Agent brand name and whether the branch is a developer |

### How to scrape Zoopla property listings

1. Open zoopla.co.uk, run the search you want (area, price range, bedrooms, property type, sort order) and copy the URL from the address bar into **Start URLs**. Or type a **Location** ("Manchester", "Kensington", "SW1A") and pick sale or rent plus optional price/bedroom filters.
2. Set **Max items** (cost cap). Optionally turn on **Include listing details**.
3. Click **Start**. Results appear in the **Dataset** tab; export as JSON, CSV or Excel, or read them through the API.

### Input example

```json
{
  "startUrls": [{ "url": "https://www.zoopla.co.uk/to-rent/flats/manchester/?beds_min=2&price_max=1500&price_frequency=per_month" }],
  "maxItems": 200,
  "includeDetails": true
}
```

or, by location:

```json
{
  "location": "Leeds, West Yorkshire",
  "listingType": "sale",
  "minBeds": 2,
  "maxPrice": 300000,
  "propertyType": "houses",
  "maxItems": 100
}
```

### Output example

```json
{
  "id": "74162557",
  "url": "https://www.zoopla.co.uk/for-sale/details/74162557/",
  "title": "2 bed flat for sale",
  "listingType": "sale",
  "price": 625000,
  "priceCurrency": "GBP",
  "priceQualifier": "Guide price",
  "address": "Wheler Street, London E1",
  "postcodeOutcode": "E1",
  "latitude": 51.521962,
  "longitude": -0.075262,
  "propertyType": "flat",
  "bedrooms": 2,
  "bathrooms": 2,
  "receptions": 1,
  "floorAreaSqFt": 750,
  "floorAreaSqM": 69.7,
  "tenure": "Leasehold",
  "publishedOn": "2026-09-07",
  "statusLabel": "Just added",
  "isNewHome": false,
  "chainFree": true,
  "tags": ["Chain free", "Leasehold"],
  "images": ["https://lid.zoocdn.com/645/430/1950ffeb364842c990900ebc6e02dbedfc04120d.jpg"],
  "imageCount": 10,
  "agent": {
    "name": "Savills - Shoreditch",
    "branchId": 60859,
    "phone": "020 8022 3250",
    "url": "https://www.zoopla.co.uk/new-homes/developers/branch/savills-shoreditch-london-60859/"
  },
  "epcRating": "B",
  "councilTaxBand": "D",
  "serviceCharge": "£4,500 per year",
  "priceHistory": [{ "date": "2026-09-07T12:25:24", "price": 625000, "event": "listed", "changePercent": null }],
  "nearbyStations": [{ "name": "Shoreditch High Street", "type": "london_overground_station", "distanceMiles": 0.1, "lines": "Windrush", "zone": "1" }]
}
```

### How many listings can I get from one search?

Zoopla returns 25 listings per page (plus up to 3 featured ones) and stops at page 40, so a single search yields at most about 1,000 listings even if it says "50,000 results". To scrape a whole city, split the search into price bands, bedroom counts or districts and add each URL to **Start URLs**; duplicates across searches are removed automatically.

### Does it scrape rentals as well as sales?

Yes. Use a `/to-rent/` search URL or set **Listing type** to "To rent". Rentals come with `pricePeriod` (`pcm` or `pw`), both `pricePerMonth` and `pricePerWeek`, `furnished` and `availableFrom`. New-homes searches (`/new-homes/`) work too and are flagged with `isNewHome`.

### Can I get price history, EPC and floor plans?

Turn on **Include listing details**. Each listing page is opened once (plus one lightweight data call) to add the full description, key features, floor plans, EPC rating, council tax band, tenure and lease length, service charge, utilities, full postcode, the complete price history (listed price and every reduction with dates) and the nearest stations and schools. This costs a little extra per listing (see the Pricing tab).

### Does it work without login or cookies?

Yes. Everything comes from public search and listing pages; no Zoopla account is used. The default Apify Proxy setting worked with datacenter IPs in testing; if a run shows 403 responses in the log, switch the proxy to residential.

### Pricing

Pay per result: a small fee per listing scraped, plus a small extra fee per listing page opened when details are on. No subscription; Apify's free plan is enough to try it.

### Integrations and API

Use the run in Zapier, Make, n8n, Google Sheets, or call it from Python/Node with the Apify client. See the **API** tab for ready-made snippets. Schedule it daily to monitor new listings and price drops in an area.

### Is it legal to scrape Zoopla?

This Actor collects only publicly available listing data (properties, prices and the advertising agent's business details). It does not collect any data about private individuals. You are responsible for how you use the data and for complying with Zoopla's terms and applicable law.

### Support

Found a bug or need a field added? Open an issue in the **Issues** tab; it is usually answered within a day.

# Actor input Schema

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

Zoopla search result pages to scrape, with any filters and sort applied in the browser, e.g. https://www.zoopla.co.uk/for-sale/property/london/ or https://www.zoopla.co.uk/to-rent/flats/manchester/?beds\_min=2\&price\_max=1500. For-sale, to-rent and new-homes searches all work. A single listing URL (…/details/12345678/) is also accepted and returns that one listing with full details. Leave empty if you use Location instead.

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

Alternative to Start URLs: a town, area, county or postcode, e.g. "Manchester", "Kensington", "SW1A", "Leeds, West Yorkshire". The actor asks Zoopla to resolve it; if several areas match (e.g. "Newport"), the first match is used and the alternatives are listed in the run summary.

## `listingType` (type: `string`):

Used with Location: properties for sale or to rent.

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

Used with Location. Sale price, or monthly rent for rentals.

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

Used with Location. Sale price, or monthly rent for rentals.

## `minBeds` (type: `integer`):

Used with Location. 0 = studio.

## `maxBeds` (type: `integer`):

Used with Location.

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

Used with Location. Zoopla's property groups.

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

Stop after this many listings in total (across all start URLs). Keeps cost predictable. Zoopla shows 25 listings per page (plus 3 featured) and caps any search at 40 pages, so narrow the search (price band, area) to get more than ~1,000 results.

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

Also open every listing page to get the full description, key features, floor plans, EPC rating, council tax band, tenure and lease details, service charge, utilities/parking, full postcode, brochure and video links, price history with prices, and nearby stations and schools. About 2 extra requests per listing; charged extra per listing (see pricing).

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

Zoopla's Cloudflare protection blocks datacenter IPs, so residential Apify Proxy is the default. Proxy usage is billed to your account.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.zoopla.co.uk/for-sale/property/london/"
    }
  ],
  "listingType": "sale",
  "propertyType": "",
  "maxItems": 100,
  "includeDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `results` (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.zoopla.co.uk/for-sale/property/london/"
        }
    ],
    "location": "",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("tortuga/zoopla-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.zoopla.co.uk/for-sale/property/london/" }],
    "location": "",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("tortuga/zoopla-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.zoopla.co.uk/for-sale/property/london/"
    }
  ],
  "location": "",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call tortuga/zoopla-scraper --silent --output-dataset

```

## MCP server setup

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