# ZOOPLA | Buy | Rent | Sell | House Price | Agent | UPRN Scraper (`memo23/zoopla-scraper`) Actor

Scrape Zoopla buy, rent, new homes, sold house prices, and estate agents. Search by town or postcode — no URL required. Full listing rows with UPRN price history, branch phone and address, optional agent emails, and an instant UK agent database when you skip the live crawl.

- **URL**: https://apify.com/memo23/zoopla-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** Real estate, Lead generation, Automation
- **Stats:** 81 total users, 2 monthly users, 100.0% runs succeeded, 2 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

$25.00/month + usage

To use this Actor, you pay a monthly rental fee to the developer. The rent is subtracted from your prepaid usage every month after the free trial period. You also pay for the Apify platform usage, which gets cheaper the higher Apify subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#rental-actors

## 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 | Buy | Rent | Sell | House Price | Agent | UPRN Scraper

Turn Zoopla.co.uk into structured UK property and agent rows. Type a town or postcode — no Zoopla URL required — or paste a search, listing, house-prices, or find-agents page.

You get listing details, UPRN-backed price history, estate-agent contacts, optional emails, and an instant UK agent database when you do not want a live crawl.

### Why Use This Scraper?

- Search by town, city, region, or postcode — `London`, `NW3`, `Oxfordshire` — without building a Zoopla URL
- One actor for for-sale, to-rent, new homes, sold house prices, and estate agents
- Full listing rows: price, beds, baths, tenure, EPC, floorplans, branch phone, UPRN
- Optional full price history via UPRN
- Optional agent emails (`contactEmail` + `contactWebsite`)
- Instant agent database: pre-collected UK branches (phone, address, brand) with no proxy

### Overview

Built for investors, agencies, lead-gen teams, and analysts who need Zoopla data as dataset rows — JSON or CSV.

Output shape follows the start type:

- **For sale / to rent / new homes / listing URLs** → one row per property (GraphQL listing payload)
- **Sold / house-prices** → one row per UPRN property on that page
- **Find-agents / `searchType: agents`** → one row per branch
- **Instant agent database** → one row per stored branch; no live Zoopla request

`startUrls` and `locationQueries` can be mixed in the same run. Location filters (beds, price, radius) apply only to location queries, not to pasted URLs.

### Supported Inputs

#### Location queries (no URL)

| Field | Example | Result |
|---|---|---|
| Town / city | `London` | `/for-sale/property/london/` (or rent / new-homes / sold / agents) |
| Region | `Yorkshire and the Humber` | hyphenated Zoopla slug |
| Postcode | `NW3` or `SW1A 1AA` | outward or full postcode slug |

Set `searchType` to `for-sale` (default), `to-rent`, `new-homes`, `sold`, or `agents`.

#### URL types

| URL type | Pattern | Example |
|---|---|---|
| Sale search | `/for-sale/property/{location}/` | `https://www.zoopla.co.uk/for-sale/property/oxfordshire/?q=Oxfordshire` |
| Rent search | `/to-rent/property/{location}/` | `https://www.zoopla.co.uk/to-rent/property/manchester/` |
| New homes | `/new-homes/property/{location}/` | `https://www.zoopla.co.uk/new-homes/property/birmingham/` |
| Listing detail | `/for-sale/details/{id}/` | `https://www.zoopla.co.uk/for-sale/details/69477553/` |
| House prices | `/house-prices/{location}/` | `https://www.zoopla.co.uk/house-prices/north-east-england/` |
| Agents | `/find-agents/{location}/` | `https://www.zoopla.co.uk/find-agents/oxfordshire/` |

#### Copy-pasteable location search

```json
{
  "locationQueries": ["London", "NW3"],
  "searchType": "for-sale",
  "minBeds": 2,
  "maxPrice": 750000,
  "maxItems": 50
}
```

#### Copy-pasteable startUrls

```json
{
  "startUrls": [
    { "url": "https://www.zoopla.co.uk/for-sale/property/oxfordshire/?q=Oxfordshire&search_source=for-sale" },
    { "url": "https://www.zoopla.co.uk/to-rent/property/manchester/" },
    { "url": "https://www.zoopla.co.uk/for-sale/details/69477553/" },
    { "url": "https://www.zoopla.co.uk/find-agents/oxfordshire/" },
    { "url": "https://www.zoopla.co.uk/house-prices/north-east-england/" }
  ]
}
```

#### Not supported

- Commercial Zoopla pages (`/for-sale/commercial/…`) as a dedicated mode
- Logged-in / My Zoopla pages
- Automated Zoopla valuations by address (no `estimates` endpoint)
- Hosts other than `zoopla.co.uk`

### Use Cases

| Audience | What they pull |
|---|---|
| Lead-gen / agencies | Branch phone, address, optional email; or the instant agent database |
| Investors | For-sale stock plus UPRN price history |
| Lettings | To-rent listings with furnished state and branch contacts |
| Comp analysts | House-prices / sold rows by area |
| Data teams | Mixed sale + rent + agent runs into one dataset |

### How It Works

![How the Zoopla Scraper works](https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/how-it-works-zoopla.png)

1. **Resolve starts.** Location queries become Zoopla search URLs. Pasted URLs are kept. Placeholder `crawlee.dev` / `example.com` URLs are dropped.
2. **Route.** Sale/rent/new-homes go through Zoopla's mobile search API, then listing details. House-prices and find-agents use those page types.
3. **Enrich (optional).** `includeWholePriceHistory` loads UPRN history. `enrichEmails` looks up a public agent email.
4. **Cap.** `maxItems` is claimed before each dataset push so you are not billed past the limit.
5. **Instant DB shortcut.** `instantDatabase` (or a `dbBranchName` filter) serves stored UK branches and exits — no crawl.

Location slugs follow Zoopla's own pattern: lowercase, hyphenated, `&` → `and`. `Yorkshire and the Humber` becomes `yorkshire-and-the-humber`. A full postcode such as `SW1A 1AA` becomes `sw1a-1aa`. If Zoopla's autocomplete would pick a more specific area than the slug, paste the URL from the site instead.

### Input Configuration

| Field | Type | Default | Description |
|---|---|---|---|
| `locationQueries` | string\[] | — | Town, city, region, or postcode. One per line. |
| `searchType` | enum | `for-sale` | `for-sale`, `to-rent`, `new-homes`, `sold`, `agents`. Ignored for pasted URLs. |
| `minBeds` / `maxBeds` | integer | — | Bedroom range for location listing searches. |
| `minPrice` / `maxPrice` | integer | — | GBP asking price or monthly rent for location listing searches. |
| `radiusMiles` | integer | — | Miles around the location. Listing searches only. |
| `startUrls` | array | — | Zoopla URLs. Optional if `locationQueries` is set. |
| `includeWholePriceHistory` | boolean | `true` | Fetch UPRN price history on listings that have a UPRN. |
| `enrichEmails` | boolean | `false` | Best-effort public agent email + website. |
| `instantDatabase` | boolean | `false` | Serve stored agent branches and exit. |
| `dbBranchName` | string | — | Instant-DB: name contains (e.g. `Foxtons`). |
| `dbHasPhone` / `dbHasEmail` | boolean | `false` | Instant-DB: keep only rows with phone / email. |
| `maxItems` | integer | `100` | Max rows pushed. Always set this. |
| `maxConcurrency` | integer | `10` | Parallel pages. |
| `minConcurrency` | integer | `1` | Floor for parallel pages. |
| `maxRequestRetries` | integer | `100` | Retries per failed request. |
| `proxy` | object | Residential | Not used in instant-DB mode. |

Either `locationQueries` or Zoopla `startUrls` is required, unless you are in instant-database mode.

#### Example — town search, no URL

```json
{
  "locationQueries": ["Manchester"],
  "searchType": "to-rent",
  "minBeds": 1,
  "maxPrice": 1500,
  "maxItems": 50
}
```

#### Example — emails on a pasted search

```json
{
  "startUrls": [
    { "url": "https://www.zoopla.co.uk/for-sale/property/london/?q=London" }
  ],
  "enrichEmails": true,
  "includeWholePriceHistory": true,
  "maxItems": 25
}
```

#### Example — instant agent database

```json
{
  "instantDatabase": true,
  "dbHasPhone": true,
  "dbBranchName": "Savills",
  "maxItems": 200
}
```

### Output Overview

Listing runs push Zoopla's listing-detail object: identifiers, pricing, room counts, branch, location (including `uprn` when Zoopla has it), NTS (tenure, council tax), media, and optional `propertyHistory` / `contactEmail`.

Agent and instant-DB runs push branch-shaped rows. Sold runs push the house-prices property object plus the search-card `basicInfo`.

### Output Samples

Sale listing (trimmed; start: location query / for-sale URL):

```json
{
  "listingId": "69488533",
  "title": "5 bed detached house for sale",
  "publicationStatus": "Live",
  "pricing": {
    "qualifier": "guide_price",
    "internalValue": 705000,
    "valueLabel": "£705,000",
    "currencyCode": "GBP"
  },
  "counts": { "numBedrooms": 5, "numBathrooms": 3, "numLivingRooms": 2 },
  "branch": {
    "branchId": "106691",
    "name": "Savills - Summertown New Homes",
    "phone": "01865 680232",
    "address": "256 Banbury Road, Oxford",
    "postcode": "OX2 7DE"
  },
  "analyticsTaxonomy": {
    "displayAddress": "The Fairfax, Heyford Fields, Heyford Park, Upper Heyford, Bicester OX25",
    "outcode": "OX25",
    "propertyType": "detached",
    "tenure": "freehold"
  },
  "location": { "uprn": "100061180044" },
  "contactEmail": "oxford@example.com",
  "contactWebsite": "https://www.savills.co.uk"
}
```

`contactEmail` / `contactWebsite` appear only when `enrichEmails` is on and a public address is found.

Instant-DB branch (start: `instantDatabase`):

```json
{
  "branch_id": "106691",
  "branch_name": "Savills - Summertown New Homes",
  "phone": "01865 680232",
  "email": null,
  "website": null,
  "address": "256 Banbury Road, Oxford",
  "postcode": "OX2 7DE",
  "member_type": "agent"
}
```

### Key Output Fields

#### Listing

- `listingId`, `title`, `publicationStatus`, `detailedDescription`
- `pricing.internalValue`, `pricing.valueLabel`, `pricing.qualifier`
- `counts.numBedrooms`, `counts.numBathrooms`, `counts.numLivingRooms`

#### Location and identity

- `analyticsTaxonomy.displayAddress`, `outcode`, `incode`, `postTownName`
- `location.uprn` — used for price history when present

#### Agent on a listing

- `branch.name`, `branch.phone`, `branch.address`, `branch.postcode`, `branch.branchId`
- `contactEmail`, `contactWebsite`, `emailEnrichment` — only with `enrichEmails`

#### Compliance / media

- `ntsInfo` (tenure, council tax band), `derivedEPC`, `listingUris.detail`
- Floorplans / images live on the listing payload when Zoopla sends them

#### Instant DB

- `branch_id`, `branch_name`, `phone`, `email`, `website`, `address`, `postcode`

### FAQ

#### Do I need a Zoopla URL?

No. Set `locationQueries` to a town or postcode and pick `searchType`. Paste URLs only when you already have a filtered Zoopla link.

#### Can I mix locations and URLs?

Yes. Real Zoopla `startUrls` run first, then each location query.

#### What does `searchType: sold` return?

House-prices pages, then a detail row per UPRN. Not the same shape as a live for-sale listing.

#### Are emails always filled?

No. Zoopla does not publish agent emails on the listing. Enrichment discovers a public site from the branch name and scrapes it. Many branches have no public email.

#### What is the instant agent database?

A stored set of UK Zoopla branches harvested from previous runs. Toggle `instantDatabase` (or set `dbBranchName`) to read it without hitting Zoopla. Coverage grows over time; it is not a live directory dump of every UK branch.

#### Does price history always come back?

Only when the listing has a `uprn` and `includeWholePriceHistory` is true. New builds often have no UPRN yet.

#### Can I mix sale and rent in one run?

Yes — paste both URL types, or run two location queries in two runs with different `searchType`. One `searchType` applies to all location queries in that run.

#### Private or paid Zoopla content?

No. Public pages only. No My Zoopla, saved searches, or login-gated tools.

#### How do I stop a huge London run?

Set `maxItems`. Detail requests already queued are not pushed once the cap is hit.

### Support

Found a bug or want a field added? Open an issue on the [Issues tab](https://apify.com/memo23/zoopla-scraper/issues) or email <muhamed.didovic@gmail.com>.

Website: <https://muhamed-didovic.github.io/>

### Additional Services

Custom export shape, a one-off UK dataset, or a scheduled monitor: <muhamed.didovic@gmail.com>. Same address for API access billed on usage only.

### Explore More Scrapers

- [**Rightmove Scraper**](https://apify.com/memo23/rightmove-scraper) — UK portal listings
- [**Idealista Scraper**](https://apify.com/memo23/idealista-scraper) — ES / IT / PT listings
- [**Zillow Agents Leads**](https://apify.com/memo23/zillow-agents-leads-scraper-ppe) — US agent contacts
- [**UK Property Deal Finder**](https://apify.com/memo23/uk-property-deal-finder) — cross-portal UK deals
- Full list at [apify.com/memo23](https://apify.com/memo23)

### 🤖 For AI Agents & LLM Apps

Compact reference for AI agents calling this actor via the [Apify MCP server](https://mcp.apify.com) or the Apify API (actor: `memo23/zoopla-scraper`).

**Purpose:** Scrapes Zoopla.co.uk sale, rent, new-home, sold-price, and estate-agent pages into one dataset row per listing or branch. Accepts town/postcode queries or Zoopla URLs. Optional UPRN price history, agent emails, and an instant stored agent database.

**Minimal input:**

```json
{
  "locationQueries": ["London"],
  "searchType": "for-sale",
  "maxItems": 25
}
```

**Output:** one row per listing or branch — `listingId`, `title`, `pricing {internalValue, valueLabel, currencyCode}`, `counts {numBedrooms, numBathrooms}`, `branch {name, phone, address, postcode}`, `location.uprn`, `analyticsTaxonomy.displayAddress`, optional `contactEmail`, `contactWebsite`. Instant-DB rows use `branch_id`, `branch_name`, `phone`, `email`, `address`.

**Behaviors an agent should know:**

- Always set `maxItems`. Default is 100; a city-wide uncapped search can be tens of thousands of rows.
- `locationQueries` + `searchType` build Zoopla URLs. `startUrls` are used as-is and can be mixed with location queries.
- `instantDatabase: true` (or a non-empty `dbBranchName`) skips the crawl and returns stored agent branches.
- `enrichEmails` is best-effort and often empty. `includeWholePriceHistory` needs a UPRN.
- Billing: rental $25/mo until 2026-09-29, then PPE — $0.005 start + $0.002 per dataset row. Instant-DB rows are still dataset items. A `contact-email` add-on is wired in code; it bills only after that event is priced on the actor.
- Public Zoopla pages only. No commercial-mode input, no address-to-valuation endpoint.

### ⚠️ Disclaimer

This Actor is an independent tool and is not affiliated with, endorsed by, or sponsored by Zoopla Limited, ZPG Limited, or any of their subsidiaries. All trademarks mentioned are the property of their respective owners.

The scraper accesses only publicly available Zoopla.co.uk pages — no authenticated endpoints, paid features, or content behind the Zoopla login wall. Users are responsible for ensuring their use complies with zoopla.co.uk's Terms of Service, applicable data-protection law (GDPR, CCPA, etc.), and any contractual obligations of their own organisation.

### SEO Keywords

Zoopla scraper, scrape Zoopla, Zoopla API, zoopla.co.uk scraper, Apify Zoopla, Zoopla property scraper, Zoopla sold prices, Zoopla house prices scraper, Zoopla estate agent scraper, UK property listings scraper, UPRN property data, estate agent emails UK, UK sold house prices, Rightmove alternative scraper, UK real estate lead gen, Zoopla to-rent scraper, Zoopla for-sale scraper, UK property market data

# Actor input Schema

## `locationQueries` (type: `array`):

Town, city, region, or UK postcode to search. One per line. Example: London, Manchester, NW3, Oxfordshire. Combined with Search type and the filters below. You can also paste Zoopla URLs in Start URLs instead (or as well).

## `searchType` (type: `string`):

What to collect for each location query. for-sale = listings for sale. to-rent = rentals. new-homes = new-build listings. sold = Zoopla house-prices / sold-price pages. agents = estate-agent directory. Default: for-sale. Ignored for pasted Start URLs, which keep their own path.

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

Minimum bedrooms for location searches. Applied to for-sale, to-rent, and new-homes only. Example: 2. Leave empty for any.

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

Maximum bedrooms for location searches. Applied to for-sale, to-rent, and new-homes only. Example: 4. Leave empty for any.

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

Minimum price in GBP for location searches. Sale = asking price. Rent = monthly rent. Example: 300000. Leave empty for any.

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

Maximum price in GBP for location searches. Sale = asking price. Rent = monthly rent. Example: 750000. Leave empty for any.

## `radiusMiles` (type: `integer`):

Search radius in miles around the location. Applied to for-sale, to-rent, and new-homes only. Example: 1. Leave empty to keep Zoopla's default (exact area).

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

Zoopla URLs to start with. Accepts for-sale and to-rent search pages, listing detail pages, /new-homes/, /find-agents/, and /house-prices/. Example: https://www.zoopla.co.uk/for-sale/property/london/. Optional if Locations is set.

## `includeWholePriceHistory` (type: `boolean`):

If true, fetch Zoopla's property history for each listing that has a UPRN. Adds sold and listing-price history on the row. Default: true. Extra page per listing.

## `enrichEmails` (type: `boolean`):

If true, look up a contact email for the listing's estate agent from the agent's public website (or by discovering the site from the branch name). Adds contactEmail, contactWebsite, and emailEnrichment. Best-effort, not guaranteed. Default: false.

## `instantDatabase` (type: `boolean`):

If true, return pre-collected UK estate-agent branches (office, phone, address, brand) from the accumulated database and exit. No Zoopla crawl, no proxy. Combine with the db\* filters below. Default: false.

## `dbBranchName` (type: `string`):

Instant-database mode only. Return branches whose name contains this text. Example: Foxtons.

## `dbHasPhone` (type: `boolean`):

Instant-database mode only. If true, return only branches that have a phone number. Default: false.

## `dbHasEmail` (type: `boolean`):

Instant-database mode only. If true, return only branches that have an enriched email. Default: false.

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

Maximum listings, sold-price rows, or agent branches to push per run. Default: 100. Always set this — an uncapped city-wide search can be tens of thousands of rows.

## `maxConcurrency` (type: `integer`):

Maximum pages processed at the same time. Default: 10. Lower this if you see blocks.

## `minConcurrency` (type: `integer`):

Minimum pages processed at the same time. Default: 1.

## `maxRequestRetries` (type: `integer`):

Times to retry a failed request before giving up. Default: 100.

## `proxy` (type: `object`):

Proxy used for live Zoopla requests. Residential is the default. Instant-database mode does not use a proxy.

## Actor input object example

```json
{
  "locationQueries": [
    "London"
  ],
  "searchType": "for-sale",
  "includeWholePriceHistory": true,
  "enrichEmails": false,
  "instantDatabase": false,
  "dbHasPhone": false,
  "dbHasEmail": false,
  "maxItems": 100,
  "maxConcurrency": 10,
  "minConcurrency": 1,
  "maxRequestRetries": 100,
  "proxy": {
    "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 = {
    "locationQueries": [
        "London"
    ],
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/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 = {
    "locationQueries": ["London"],
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("memo23/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 '{
  "locationQueries": [
    "London"
  ],
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call memo23/zoopla-scraper --silent --output-dataset

```

## MCP server setup

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