# Zoopla UK Properties Scraper (`khadinakbar/zoopla-scraper`) Actor

Scrape Zoopla.co.uk UK property listings for sale and to rent by location or URL. Returns price, beds, baths, address, coordinates, images; optional detail enrich. Cloudflare cleared via Apify Unblocker (GB). MCP-ready.

- **URL**: https://apify.com/khadinakbar/zoopla-scraper.md
- **Developed by:** [Khadin Akbar](https://apify.com/khadinakbar) (community)
- **Categories:** Real estate, Lead generation, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 property scrapeds

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

## Zoopla UK Properties Scraper

Turn a Zoopla search URL, detail URL, or UK place name into structured for-sale and to-rent listing rows. UK investors, lettings teams, and AI agents get price, bedrooms, bathrooms, address, coordinates, floor size, images, and listing URLs in one Apify dataset, ready for comps, CRM import, or MCP workflows.

### Best fit for this Actor

- Pull current Zoopla for-sale inventory for a city or postcode with price, beds, baths, address, and GPS.
- Monitor Zoopla to-rent stock for lettings comps with monthly rent and listing URLs.
- Enrich known Zoopla detail URLs when search cards need tenure or a longer description.
- For multi-portal UK coverage that also includes sold prices and estate agents, continue with [Rightmove All-in-One Scraper](https://apify.com/khadinakbar/rightmove-all-in-one-scraper) after you finish the Zoopla extract.

### London for-sale comps in one run

A UK investor sets `searchLocation` to `London`, `channel` to `BUY`, and `maxItems` to `10`. The dataset returns one row per listing with `id`, `url`, `price`, `bedrooms`, `bathrooms`, `displayAddress`, and coordinates. Listings under the investor's budget become the shortlist; the rest stay in the sheet for later. A nonsense location finishes `SUCCEEDED` with `VALID_EMPTY` and no `property-scraped` charge, so the workflow stays safe to schedule.

### Quick start input

```json
{
  "searchLocation": "Manchester",
  "channel": "BUY",
  "maxItems": 25,
  "fetchPropertyDetails": false
}
```

Use `startUrls` for Zoopla search or detail pages when you already have links. Leave Start URLs empty and set `searchLocation` plus `channel` when you want the Actor to build the search.

### Input reference

| Field | Type | What it controls |
|---|---|---|
| `startUrls` | array | Zoopla search or detail URLs. Auto-detected mode. |
| `searchLocation` | string | UK place used when Start URLs are empty. Example: `London`. |
| `channel` | enum | `BUY` (for sale) or `RENT` (to rent). Default `BUY`. |
| `minPrice` / `maxPrice` | integer | GBP filters on location searches. |
| `minBedrooms` / `maxBedrooms` | integer | Bedroom filters on location searches. |
| `fetchPropertyDetails` | boolean | Follow detail pages for tenure and longer description. Default false. |
| `maxItems` | integer | Row cap from 1 to 1000. Prefill uses 10. |

### What data you receive

One dataset item is one public Zoopla listing card (or enriched detail row).

```json
{
  "id": "73717271",
  "url": "https://www.zoopla.co.uk/for-sale/details/73717271/",
  "channel": "BUY",
  "propertyTitle": "2 bed flat for sale",
  "displayAddress": "Hampton Tower, South Quay Plaza, Canary Wharf, London E14",
  "price": 1000000,
  "priceCurrency": "GBP",
  "priceDisplay": "£1,000,000",
  "bedrooms": 2,
  "bathrooms": 2,
  "sizeSqFt": 969,
  "latitude": 51.500961,
  "longitude": -0.017234,
  "enriched": false,
  "scrapedAt": "2026-09-23T12:00:00.000Z"
}
```

| Field | Meaning |
|---|---|
| `id` / `url` | Listing id and canonical detail URL |
| `channel` | `BUY` or `RENT` |
| `price` / `priceDisplay` | Numeric GBP asking price and display string |
| `bedrooms` / `bathrooms` / `sizeSqFt` | Core specs when Zoopla publishes them |
| `displayAddress` / `latitude` / `longitude` | Location |
| `description` / `tenure` | Stronger when detail enrich is enabled |
| `images` | Photo URLs when present on the page |

`OUTPUT` and `RUN_SUMMARY` in the default key-value store hold `outcome`, `itemsPushed`, and `chargedEventCounts`.

### Use through the API

```bash
curl -X POST "https://api.apify.com/v2/acts/khadinakbar~zoopla-scraper/runs" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"searchLocation":"London","channel":"BUY","maxItems":10}'
```

Download rows as JSON, CSV, Excel, or HTML from the Dataset tab.

### Use with AI agents through Apify MCP

> Scrape up to 20 Zoopla for-sale listings in Manchester under £400,000 with at least 2 bedrooms. Return id, url, price, bedrooms, displayAddress.

Connect via <https://mcp.apify.com>. Read `OUTPUT.outcome` and `itemsPushed` to interpret empty datasets. Cost signal: about `$0.005` per saved listing with Apify platform usage included. Empty valid searches surface as `VALID_EMPTY` without a `property-scraped` charge.

### Connect the workflow

- After Zoopla for-sale or to-rent rows are in the dataset, expand to sold prices and estate-agent branches with [Rightmove All-in-One Scraper](https://apify.com/khadinakbar/rightmove-all-in-one-scraper).
- For Spanish portal inventory in the same research pack, add [Idealista Scraper](https://apify.com/khadinakbar/idealista-scraper).
- For US listing search parallel to this UK workflow, use [Zillow Search Scraper](https://apify.com/khadinakbar/zillow-search-scraper).

### Pricing

#### Startup billing and empty runs

The `apify-actor-start` event costs **$0.00005 per startup unit**. Apify charges one startup unit per GB of allocated memory, with a minimum of one unit, so a 2 GB run incurs two units. This startup charge still applies when input is invalid, no results are found, or an upstream request fails. Statements about free, unbilled, or skipped results refer only to the corresponding result event; they do not waive the startup charge. Apify platform usage is included in the event prices and is not passed through as an additional customer charge. Check the live [Pricing tab](https://apify.com/khadinakbar/zoopla-scraper/pricing) for current events, tiers, and rates. Result limits cap result events, not the entire run cost.

This Actor uses Pay per event with Apify platform usage included. The live Pricing tab is the current source of truth for billing details.

- `apify-actor-start`: $0.00005 per run
- `property-scraped`: **$0.005** per saved listing

A ten-listing London prefill is about $0.05 in result events with Apify platform usage included. Search cards already carry price, beds, baths, address, and GPS; detail enrich adds request usage without a second PPE event.

### Best results

- Prefer Zoopla search URLs when you already have radius or postcode filters dialed in.
- Keep `maxItems` near 10–25 on first runs so pagination stays cheap while you confirm the area.
- Leave the built-in Unblocker GB route enabled; it is tuned for Zoopla's public pages.
- Typo or empty locations finish `SUCCEEDED` with `VALID_EMPTY` and zero listing charges.

### Builder's note

I found that Zoopla's public search HTML embeds a JSON-LD `SearchResultsPage` ItemList with Product cards and related RealEstateListing fields, which is enough for price, beds, baths, address, and GPS without a browser. Location searches resolve reliably through Zoopla's `/search/?q=&section=` geo path rather than bare property-slug URLs, so cities such as Leeds and Croydon return the same structured cards as London. Keeping detail enrich optional lets agents pay Unblocker usage only when tenure or a longer description is needed.

### Responsible use

Use this Actor on public Zoopla listing pages you are authorized to process, follow applicable law and Zoopla's terms, and keep the output in your own compliance workflow. Zoopla is a trademark of its owners. This independent Actor is not affiliated with, associated with, or endorsed by Zoopla Limited.

Issues and feature requests: use the Actor Issues tab on Apify.

# Actor input Schema

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

Zoopla URLs to scrape. Auto-detects /for-sale/property/... and /to-rent/property/... search pages plus /for-sale/details/{id}/ and /to-rent/details/{id}/ detail pages. Pagination uses the pn query param. If empty, the Actor builds a search from searchLocation + channel. Only zoopla.co.uk URLs are supported.

## `searchLocation` (type: `string`):

UK place name or area used when startUrls is empty (e.g. London, Manchester, Croydon). Converted to a Zoopla URL slug. For precise postcode areas, paste a Zoopla search URL into Start URLs instead.

## `channel` (type: `string`):

BUY = properties for sale. RENT = properties to rent. Used only when building a search from searchLocation; ignored when startUrls is set.

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

Minimum price in GBP. For BUY: asking price. For RENT: monthly rent. Leave empty for no minimum. Location searches only.

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

Maximum price in GBP. For BUY: asking price. For RENT: monthly rent. Leave empty for no maximum. Location searches only.

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

Minimum bedrooms (0–10). Location searches only.

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

Maximum bedrooms (0–10). Location searches only.

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

Optional Zoopla path segment replacing 'property' (houses, flats, bungalows, land, commercial, farms-land). Leave empty for all types. Location searches only.

## `includeSoldOrLet` (type: `boolean`):

Include sold (BUY) or let-agreed (RENT) listings when Zoopla exposes them on the search. Defaults to false.

## `sort` (type: `string`):

Zoopla results_sort value for location searches.

## `fetchPropertyDetails` (type: `boolean`):

For each search hit, fetch the detail page to enrich description, tenure, and floor size when present. One extra Unblocker request per listing. Off by default — search JSON-LD already includes price, beds, baths, address, and coordinates.

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

Maximum dataset rows to return (1–1000). Zoopla searches typically paginate ~25 listings per page and soft-cap near 1,000.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.zoopla.co.uk/for-sale/property/london/?q=London&results_sort=newest_listings&search_source=home"
    }
  ],
  "searchLocation": "London",
  "channel": "BUY",
  "propertyType": "",
  "includeSoldOrLet": false,
  "sort": "newest_listings",
  "fetchPropertyDetails": false,
  "maxItems": 10
}
```

# Actor output Schema

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

Zoopla property rows in the default dataset.

## `output` (type: `string`):

Final outcome, itemsPushed, and chargedEventCounts.

## `runSummary` (type: `string`):

Diagnostic RUN_SUMMARY with pagesFetched, blockHits, and billing counters.

# 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/?q=London&results_sort=newest_listings&search_source=home"
        }
    ],
    "searchLocation": "London",
    "channel": "BUY",
    "maxItems": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("khadinakbar/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/?q=London&results_sort=newest_listings&search_source=home" }],
    "searchLocation": "London",
    "channel": "BUY",
    "maxItems": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("khadinakbar/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/?q=London&results_sort=newest_listings&search_source=home"
    }
  ],
  "searchLocation": "London",
  "channel": "BUY",
  "maxItems": 10
}' |
apify call khadinakbar/zoopla-scraper --silent --output-dataset

```

## MCP server setup

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