# Properstar - Global Property Listings, URLs & Agency Data (`abotapi/properstar-scraper`) Actor

Extract property listings from Properstar across global domains like .com, .co.uk, .ch, and .fr. Use search pages, listing URLs, or agency pages. Returns prices, multilingual descriptions, photos, floor plans, GPS coordinates, and agency details via Properstar’s data service.

- **URL**: https://apify.com/abotapi/properstar-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Real estate, Developer tools, Automation
- **Features:** Limited permissions
- **Stats:** 4 total users, 0 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Properstar Scraper: Listings, Prices, Photos & Agency Data

Properstar Scraper turns Properstar into structured property data across every country domain it runs on (.com, .co.uk, .ch, .fr, and more), auto-detected from the URL you paste. Paste a search-results page, a single listing page, or an agency or agent page, and get clean records with prices, multilingual titles and descriptions, photos, floor plans, GPS coordinates, energy ratings, and agency contact details. Switch on full detail for amenities, view types, construction year, and floor, then export to JSON, CSV, or Excel, or pull results straight into your app through the API.

### Why This Scraper?

- **Any Properstar country domain**, auto-detected from the URL you paste, no domain allowlist to work around.
- **Three input shapes in one actor**: search-results pages, single listing pages, and agency or agent pages.
- **Multilingual titles and descriptions** captured per language, not just one.
- **50+ named fields per listing**, plus the complete source object so nothing is lost.
- **Optional full detail mode** adds amenities, AI-extracted view types, construction year, and floor to search and agency results (listing-page URLs always include these).
- **Full measurements**: living and land area in both square metres and square feet, alongside rooms, bedrooms, bathrooms, and energy/CO2 ratings.
- **Built for monitoring**: incremental mode returns only new and changed listings on recurring runs, and a fully rejected run fails loudly instead of returning an empty dataset.

### Use Cases

- **Real estate portals and PropTech tools:** aggregate live listings across multiple country domains into one structured feed.
- **Investors and relocation agencies:** compare prices, sizes, and locations across countries and currencies from a single search.
- **Market researchers and analysts:** track price and inventory changes over time with incremental monitoring.
- **Lead generation and CRM tools:** pull agency and agent contact details tied to their current live inventory.
- **Content and comparison sites:** build country or city listing pages from live search results.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live listing.

| Field | Example |
| --- | --- |
| `id` | `"100000001"` |
| `url` | `"https://www.properstar.co.uk/listing/100000001"` |
| `class` | `"PropertyListing"` (also `"ProjectListing"`, `"UnitListing"` on agency/agent inventory) |
| `transactionType` | `"Sell"` (also `"Rent"`) |
| `type` / `subType` | `"Apartment"` / `"Penthouse"` |
| `title` | `"Sample listing title"` |
| `description` | `"Full description text appears here."` |
| `titleByLanguage` / `descriptionByLanguage` | `{ "en": "Sample listing title" }`, one entry per language |
| `price` / `currency` | `1000000` / `"GBP"` |
| `livingAreaSqm` / `landAreaSqm` | `120` / `0` |
| `livingAreaSqft` / `landAreaSqft` | `1291` / `0` |
| `rooms` / `bedrooms` / `bathrooms` | `4` / `3` / `2` |
| `floor` / `constructionYear` | `"3"` / `2020` (full detail mode) |
| `address` / `city` / `postcode` | `"Sample Street 1"` / `"London"` / `"SW1"` |
| `countryISO` | `"GB"` |
| `latitude` / `longitude` | `51.5000` / `-0.1200` |
| `pictures` / `picturesCount` | `["https://images.example.com/0001.jpg"]` / `12` |
| `floorPlans` | `["https://images.example.com/plan-0001.jpg"]` |
| `amenities` / `views` | `["Balcony", "Parking"]` / `["City"]` (full detail mode) |
| `status` | `"Published"` |
| `energyRating` / `co2Rating` | `"C"` / `"B"` |
| `agencyId` / `agencyName` | `1000000` / `"Sample Agency"` |
| `publicationDate` | `"2026-01-01T00:00:00Z"` |
| `changeType` / `changedFields` | `"NEW"` / `["price"]` (incremental mode only) |

Every record also carries a typed set of extra fields (`placeId`, `terraceAreaSqm`, `pricePerSquareMeter`, `areaValues`, `pictureItems`, `mediaItems`, `advertiser`). `advertiser` is the advertising agency (`id`, `name`, `originalId` (the site's own account reference, not a URL slug), `type`) and `pricePerSquareMeter` is the price divided by the living area (empty when either is missing). `updateDate` and `terraceAreaSqm` are filled only when the site supplies them: `updateDate` comes with **Fetch full detail**, and `terraceAreaSqm` only when a listing declares a terrace area. The output dataset schema declares all fields. The Storage tab has three views: **overview**, **changes** (`changeType`, `changedFields`, first/last seen) and **detail** (amenities, views, floor, construction year). In incremental mode the record kinds are `NEW`, `UPDATED`, `REAPPEARED`, plus optional `UNCHANGED` and `EXPIRED`.

Every record also carries `agencySlug`, `agencyType`, and `agencyStatistics` for the listing's agency; `firstSeenAt` and `lastSeenAt` in incremental mode; and the complete source object as `raw`, so nothing from Properstar's own data is lost.

### How to Use

1. Paste one or more Properstar URLs into **Properstar URLs**: search-results pages, single listing pages, or agency/agent pages, from any country domain.
2. For search-page URLs, optionally refine with **Transaction type**, **Property types**, price and bedroom range, or **Luxury listings only**.
3. Turn on **Fetch full detail** if you want amenities, view types, construction year, and floor on search or agency results (listing-page URLs always include these).
4. Set **Max listings** and **Max pages per URL** to control run size and cost, then click **Start**.

**Default URLs:** **Properstar URLs** has a non-empty default. If an API call omits `startUrls`, or you leave the prefilled list untouched in the Console, the actor works through these 10 example URLs (one per supported URL shape) in order until **Max listings** is reached. Max listings counts across all URLs, so with its default of 20 only the first search is usually reached; raise it (or set `0`) to reach the later shapes:

1. a city buy search
2. the same search with a page number (`?p=2`)
3. a city rent search
4. a country-level buy search
5. a search with `price` and `bedrooms` options
6. a search with a `sort` option
7. a search without a property-type segment (redirected by the site to the default type)
8. a search on an alternate region (Switzerland)
9. a single listing page (`/listing/<id>`)
10. an agency page (`/agency/<slug>/<id>`)

Replace the list with your own URLs to scrape only those. Pasted-URL options: a page number (`?p=2`) starts the walk at that page (such a run never marks listings `EXPIRED`, see Resume and recurring updates), `price` (for example `price=500000-1000000`) and `bedrooms` (for example `bedrooms=2`) options are applied as filters on top of the page's own filters (the site's price filter compares euro-equivalent prices, so listings priced in another currency can sit slightly outside the stated range), and a `sort` option is ignored with a warning in the run log (the site's default order is used).

**Basic search page:**

```json
{
  "startUrls": ["https://www.properstar.co.uk/united-kingdom/london/buy/apartment-house"],
  "maxItems": 50
}
```

**Search page with refinement filters:**

```json
{
  "startUrls": ["https://www.properstar.com/spain/barcelona/buy/apartment-house"],
  "transactionType": "buy",
  "propertyTypes": ["apartment"],
  "minPrice": 300000,
  "maxPrice": 800000,
  "minBedrooms": 2,
  "maxItems": 200
}
```

**Single listing (always full detail):**

```json
{
  "startUrls": ["https://www.properstar.co.uk/listing/100000001"]
}
```

**Agency or agent inventory:**

```json
{
  "startUrls": ["https://www.properstar.co.uk/agency/sample-agency/1000000"],
  "maxItems": 100,
  "enrichDetail": true
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/properstar-scraper").call(run_input={
    "startUrls": ["https://www.properstar.co.uk/united-kingdom/london/buy/apartment-house"],
    "maxItems": 50,
})
for listing in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(listing["title"], listing["price"], listing["currency"])
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/properstar-scraper').call({
    startUrls: ['https://www.properstar.co.uk/united-kingdom/london/buy/apartment-house'],
    maxItems: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### Caps and cost controls

- **Max listings** (`maxItems`) stops the run once this many listings, across all URLs, have been returned. `0` is unlimited.
- **Max pages per URL** (`maxPagesPerUrl`) caps how many 25-listing pages are walked per search or agency URL. Leave it empty (`0`) to walk every page until the site's own end of results, capped at roughly 2000 retrievable listings per URL.
- **Residential traffic budget** (`maxResidentialMb`) caps the residential-exit browser traffic used for connection setup and, when **Fetch full detail** is on, per-listing detail fetches. The budget is estimated per page opened, not metered byte for byte. Once spent, the run keeps going and finishes the already-cheap list pagination, but stops fetching further detail pages. `0` means no cap.

**Known limitation: overlapping URLs end early.** "Already collected" is checked against everything collected so far in the whole run, not just the current URL. Once a later search or agency URL has returned at least one new listing, a following page of that URL made up only of listings an earlier URL already returned is read as the end of its results, and its deeper pages are silently skipped with no error. Example: you run a London search and then a Westminster search in one run; page 1 of Westminster has a few new listings, but every listing on page 2 was already returned by London, so the actor never reads page 3 of Westminster, even if it holds listings London never showed. The first URL in a run is never affected, and a later URL whose opening pages are all duplicates keeps going. The same rule applies to a **Resume** run: if new listings have been added near the top of the results since the interrupted run, the pages that follow them can consist entirely of already-saved listings, and the walk stops there before reaching the listings it was meant to add. If your URLs overlap and you need every listing, run each one as its own run (or its own scheduled task); for Resume, resume soon after the interruption.

#### Resume and recurring updates

- **Resume** (`resumeFromRunId`) continues one interrupted full-catalogue pull in a brand-new run: paste a previous run ID or dataset ID, and this run skips every listing `id` already saved there, appending only new listings. A checkpoint is also saved automatically as the run progresses, so an Apify platform migration or a **Resurrect** of a failed run picks back up in the same run without re-saving already-collected listings. No input is needed for that part; it is automatic.
- **Incremental mode** (`incrementalMode`) is a different workflow: it re-runs the *same* `startUrls` and filters on a schedule (for example daily) and tells you only what changed, using state the actor keeps for you. The first run for a given search returns every listing as `NEW`. Later runs return only `NEW`, `UPDATED` (with `changedFields`), and `REAPPEARED` listings by default. Turn on `emitUnchanged` to also get `UNCHANGED` rows, or `emitExpired` to also get `EXPIRED` rows for listings that disappeared. `EXPIRED` detection only fires once a run has fully scanned every tracked URL; it is skipped on a `resumeFromRunId` run, a run capped by **Max listings**, a run where any URL stopped at **Max pages per URL**, a URL with more results than the roughly 2000 the site lets you retrieve, a run where any start URL carries a page number (`?p=2` starts mid-catalogue, so earlier pages were not scanned), or a run where a page or connection was rejected mid-scan, since none of those prove a listing is actually gone. A listing marked `EXPIRED` that shows up again later is returned as `REAPPEARED`; without `emitExpired`, listings are never marked gone, so `REAPPEARED` does not occur.
- **Known limitation: false `EXPIRED` rows with overlapping URLs.** A URL that stops early because of the overlap rule above (a page holding only listings an earlier URL already returned) still counts as having reached the end of its results. So with `emitExpired` on, listings on that URL's skipped deeper pages can be marked `EXPIRED` (and billed) while they are still live. Example: you track a London search and a Westminster search together, and a flat appears only on page 3 of Westminster. On a later run, page 2 of Westminster holds only listings London already returned, the walk stops there, and that flat comes back as `EXPIRED` although nothing on Properstar changed; it returns as `REAPPEARED` the next time a run reaches it. If you rely on `EXPIRED`, track each area or filter set in its own run (each gets its own saved state automatically), and treat an `EXPIRED` row as "not seen this run" rather than proof of delisting.
- **What counts as a change:** every returned field except `raw` is compared (fields added in later versions, such as `advertiser` and `pricePerSquareMeter`, are compared from the second run after they first appear, so rolling them out never causes a wave of `UPDATED` rows; `updateDate` is compared once the detail page has supplied it). `raw` (the complete source object) is always excluded from the comparison because it carries per-search ranking scores and per-viewer notification flags that change between runs while the listing itself does not; it is still returned in full on every row. Add your own exclusions with `ignoreFieldsForChanges`; adding a field there never causes a one-time wave of `UPDATED` rows.
- Incremental mode and **Resume** can be combined only to seed a brand-new baseline from an earlier dataset. If saved incremental state already exists for the same search, a run with both set fails straight away with a message; remove `resumeFromRunId` or set a different `stateKey`.
- State is namespaced automatically per distinct `startUrls` + filter + `enrichDetail` combination, so two different searches never mix baselines. Set `stateKey` to name a campaign explicitly, or to share state deliberately across a different configuration.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape: the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each record, not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify → Settings → Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `startUrls` | array | required | Properstar search, listing, or agency/agent URLs from any country domain. Multiple URLs are processed in order. |
| `transactionType` | string | `any` | Refine search-page URLs to `buy` or `rent`. `any` keeps the URL's own setting. |
| `propertyTypes` | array | `[]` | Restrict to apartment, house, land, commercial, office, parking, building, or room. Leave empty to use the URL's own types. |
| `minPrice` | integer | (none) | Minimum price in the listing's currency. |
| `maxPrice` | integer | (none) | Maximum price in the listing's currency. |
| `minBedrooms` | integer | (none) | Minimum number of bedrooms. |
| `maxBedrooms` | integer | (none) | Maximum number of bedrooms. |
| `luxuryOnly` | boolean | `false` | Return only listings flagged as luxury. |
| `enrichDetail` | boolean | `false` | Fetch each listing's detail page for amenities, AI-extracted view types, construction year, and floor. Adds a per-listing surcharge and is slower. Listing-page URLs always include full detail regardless of this setting. |
| `maxItems` | integer | `20` | Maximum listings to return across all URLs. `0` = unlimited. |
| `maxPagesPerUrl` | integer | `0` | Maximum result pages to walk per search/agency URL (25 listings per page). `0` walks every page until the site's own end of results, capped at roughly 2000 retrievable listings per URL. |
| `maxResidentialMb` | integer | `0` | Soft, estimated cap on residential-exit browser traffic (connection setup, plus per-listing detail fetches when `enrichDetail` is on). Once reached, detail fetches stop for the rest of the run; list pagination is unaffected. `0` = no cap. |
| `proxy` | object | Residential | Connection settings. The one-time setup needs a residential exit (the default); pick a country to pin the exit region, or leave it unset to rotate. |
| `resumeFromRunId` | string | (empty) | A previous run ID or dataset ID to continue a full-catalogue walk across separate runs. Listings already collected there (matched by `id`) are skipped, so this run only saves the new ones. |
| `incrementalMode` | boolean | `false` | Turn on for recurring monitoring of the same `startUrls`/filters. Only `NEW`, `UPDATED`, and `REAPPEARED` listings are returned after the first run. |
| `stateKey` | string | (empty) | Name an incremental-mode monitoring campaign, or share state deliberately across differently-configured runs. Auto-derived from `startUrls` + filters + `enrichDetail` when empty. |
| `emitUnchanged` | boolean | `false` | Incremental mode only. Also return (and bill) listings with no change, marked `UNCHANGED`. |
| `emitExpired` | boolean | `false` | Incremental mode only. Also return (and bill) listings no longer found, marked `EXPIRED`, only when the run fully scanned every tracked URL. See "Resume and recurring updates" for the exceptions and a known limitation with overlapping URLs. |
| `ignoreFieldsForChanges` | string list | (empty) | Incremental mode only. Field names (or dotted paths such as `agencyStatistics.listingCount`) that do not count as a change: they are left out of the comparison and out of `changedFields`, but are still returned in every row. Exact-name match. |
| `mcpConnectors` | array | (none) | Optional: send a summary of each record to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | (none) | Notion connector only: page under which records are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. Does not affect the dataset. |

### Output Example

> Sample shape: values are illustrative placeholders, not from a live listing.

```json
{
  "id": "100000001",
  "url": "https://www.properstar.co.uk/listing/100000001",
  "class": "PropertyListing",
  "transactionType": "Sell",
  "type": "Apartment",
  "subType": "Penthouse",
  "title": "Sample listing title",
  "description": "Full description text appears here.",
  "titleByLanguage": { "en": "Sample listing title" },
  "descriptionByLanguage": { "en": "Full description text appears here." },
  "price": 1000000,
  "currency": "GBP",
  "livingAreaSqm": 120,
  "livingAreaSqft": 1291,
  "rooms": 4,
  "bedrooms": 3,
  "bathrooms": 2,
  "floor": "3",
  "constructionYear": 2020,
  "address": "Sample Street 1",
  "city": "London",
  "postcode": "SW1",
  "countryISO": "GB",
  "latitude": 51.5,
  "longitude": -0.12,
  "pictures": ["https://images.example.com/0001.jpg"],
  "picturesCount": 12,
  "floorPlans": ["https://images.example.com/plan-0001.jpg"],
  "amenities": ["Balcony", "Parking"],
  "views": ["City"],
  "agencyId": 1000000,
  "agencyName": "Sample Agency",
  "agencySlug": "sample-agency",
  "agencyStatistics": { "numberOfListings": 0 },
  "publicationDate": "2026-01-01T00:00:00Z"
}
```

### Plan Requirement

Properstar protects its pages with a browser challenge that a plain HTTP request cannot pass from any connection type (direct, datacenter or residential); only a real browser that waits a few seconds clears it. The actor therefore opens a browser for a one-time setup, and the default Connection setting selects a residential exit for it; a different setting may return no listings. Once setup is complete, the bulk of the data pull automatically switches to a lower-cost connection pool. If the challenge does not clear in time, the run retries on a fresh connection and fails with a clear message when every attempt is rejected. Pick a proxy country under Connection to pin the exit region, or leave it unset to rotate automatically.

### FAQ

#### How much does it cost?

Charges follow what the actor delivers: you pay per listing returned (the result event, one per row saved to the dataset), and the Actor start event is charged when the run starts (it is based on the run's memory, so it can be more than one unit per run). When **Fetch full detail** is switched on, the `detail-enrichment` event is charged once per listing, at the moment that enriched listing is saved to the dataset, and only for enriched listings that are actually saved to the dataset (never for rows that are suppressed, such as `UNCHANGED` rows when **Emit unchanged** is off, and never for deduplicated or failed rows; an `UNCHANGED` row returned because **Emit unchanged** is on is charged like any other enriched row). In incremental mode, `UNCHANGED` and `EXPIRED` rows are returned and billed only when you turn on **Emit unchanged** or **Emit expired**. The **Pricing** tab shows the current rates. Use **Max listings** and **Max pages per URL** to cap the cost of any run.

#### Is it legal to scrape Properstar?

This actor collects only publicly available listing data. You are responsible for how you use it: follow Properstar's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Photos, floor plans, and agency contact details may be subject to third-party rights.

#### Can I monitor the same search for new or changed listings?

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. Each run then returns only new, updated, and reappeared listings, and unchanged ones are not billed unless you turn on **Emit unchanged**.

#### Why did my run return fewer listings than I expected?

Check **Max listings** and **Max pages per URL**, which both cap a run by design. A single search or agency URL is also capped at roughly the site's own 2000-listing retrievable ceiling. If you pass several overlapping URLs in one run, a later URL can also stop early once a whole page of it repeats listings an earlier URL already returned (see "Known limitation: overlapping URLs end early" under Caps and cost controls), and with **Emit expired** on, listings it skipped this way can come back as `EXPIRED` even though they are still live. Run overlapping areas or filters as separate runs to avoid both.

#### Why did my run fail instead of returning an empty dataset?

If every request to Properstar was rejected, the run fails with a clear message so "no listings match" is never confused with "the connection could not be established". Run it again in a few minutes. If only some of your URLs failed, you still get what was found from the others.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear, or Airtable.

### 🔗 Want more real estate data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🏠 <a href="https://apify.com/abotapi/immowelt-de-scraper"><b>Immowelt.de Scraper</b></a><br>Scrape detailed property listings from Immowelt.de into clean structured data. Extract...</td><td>🏠 <a href="https://apify.com/abotapi/seloger-france-scraper"><b>SeLoger Scraper</b></a><br>Scrape SeLoger.com properties for sale and rent. Search by location with price, room and...</td></tr>
<tr><td>🏠 <a href="https://apify.com/abotapi/domclick-scraper"><b>Domclick RU Property Scraper</b></a><br>Extract property listings from domclick.ru, one of Russia’s largest real estate portals...</td><td>🏠 <a href="https://apify.com/abotapi/fotocasa-es-scraper"><b>Fotocasa.es</b></a><br>Scrape Spain property listings from fotocasa.es. Get price, surface, rooms, baths...</td></tr>
<tr><td>🏠 <a href="https://apify.com/abotapi/fundainbusiness-nl-commercial-property-scraper"><b>Funda in Business Scraper</b></a><br>Scrape Funda in Business commercial listings: offices, retail, industrial, catering and...</td><td>🏠 <a href="https://apify.com/abotapi/zonaprop-scraper"><b>Zonaprop Argentina</b></a><br>Scrape Zonaprop.com.ar property listings across Argentina. Extract 140+ fields including...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/properstar-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

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

Paste Properstar URLs from any country domain (.com, .co.uk, .ch, .fr, ...). Supported: search result pages (e.g. /united-kingdom/london/buy/apartment-house), single listing pages (/listing/{id}), and agency or agent pages (/agency/{slug}/{id}). Multiple URLs are processed in order. The filter fields below refine search-page URLs. Leave this field untouched to work through 10 example URLs (one per supported URL shape) in order until Max listings is reached; with the default Max listings only the first search is usually reached. Replace or clear the list to scrape only your own URLs. A page number (?p=2) starts the walk at that page, and price and bedrooms options are applied as filters. A ?sort= option in a pasted URL is ignored.

## `transactionType` (type: `string`):

Refine search-page URLs to buy or rent. 'Any' keeps the URL's own setting.

## `propertyTypes` (type: `array`):

Restrict to these property types. Leave empty to use the URL's own types.

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

Minimum price. The site compares euro-equivalent prices, so listings priced in another currency can sit slightly outside the range.

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

Maximum price. The site compares euro-equivalent prices, so listings priced in another currency can sit slightly outside the range.

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

Minimum number of bedrooms.

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

Maximum number of bedrooms.

## `luxuryOnly` (type: `boolean`):

Return only listings flagged as luxury.

## `enrichDetail` (type: `boolean`):

Fetch each listing's detail page for amenities, AI-extracted views, construction year and floor. Slower and adds a per-listing surcharge. Listing-page URLs always include full detail.

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

Maximum listings to return across all URLs. Set 0 for unlimited.

## `maxPagesPerUrl` (type: `integer`):

Maximum result pages to walk per search/agency URL (25 listings per page). Leave empty to walk every result page (the walk still stops at the site's own ~2000-listing retrievable ceiling).

## `maxResidentialMb` (type: `integer`):

Soft cap on Residential proxy traffic. When reached, the actor downgrades to the lower-cost connection pool for the rest of the run. 0 = no cap.

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

Connection settings. The one-time connection setup requires a Residential exit (recommended default). The bulk data extraction then runs automatically over the lower-cost Datacenter pool. Pick a country to pin the exit region; leave it unset to rotate.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue a full-catalogue walk across separate runs. Listings already collected there (matched by listing id) are skipped, so this run only saves the new ones. Leave empty for a normal fresh run. For recurring daily/weekly monitoring of the same URLs, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring of the same startUrls/filters. The first run returns every matching listing as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED listings. Turn on "Emit unchanged" or "Emit expired" only when you also want those listings returned (and billed). State is kept separately per distinct startUrls/filters/enrichDetail setup; use State key when you want to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Leave empty to let the actor derive a key automatically from startUrls, the filter fields, and enrichDetail — different searches then never mix state with each other.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return listings that have not changed since the last run, marked UNCHANGED. This returns — and bills — extra rows you already have, so leave it off unless you specifically want the full snapshot every run.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return listings that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked startUrls — not when Max listings capped it, when Resume was used, when a start URL carries a page number (?p=N), or when a page/connection was rejected mid-scan. This returns — and bills — extra synthetic rows, so leave it off unless you need expiry tracking.

## `ignoreFieldsForChanges` (type: `array`):

Incremental mode only. Field names that must NOT count as a change, so a listing is not reported UPDATED just because one of them moved. Use top-level names (for example "description") or dotted paths into nested objects (for example "agencyStatistics.listingCount"). Matching is exact on the whole name. Ignored fields are still returned in every row; they only stop being compared and stop appearing in changedFields. The full source payload `raw` is always ignored for change detection.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON — the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.properstar.co.uk/united-kingdom/london/buy/apartment-house",
    "https://www.properstar.co.uk/united-kingdom/london/buy/apartment-house?p=2",
    "https://www.properstar.co.uk/united-kingdom/london/rent/apartment-house",
    "https://www.properstar.co.uk/united-kingdom/buy/apartment-house",
    "https://www.properstar.co.uk/united-kingdom/london/buy/apartment-house?price=500000-1000000&bedrooms=2",
    "https://www.properstar.co.uk/united-kingdom/london/buy/apartment-house?sort=price-asc",
    "https://www.properstar.co.uk/united-kingdom/london/buy",
    "https://www.properstar.co.uk/switzerland/buy/apartment-house",
    "https://www.properstar.co.uk/listing/121377505",
    "https://www.properstar.co.uk/agency/the-private-realtor-ltd/6482545"
  ],
  "transactionType": "any",
  "propertyTypes": [],
  "luxuryOnly": false,
  "enrichDetail": false,
  "maxItems": 20,
  "maxPagesPerUrl": 0,
  "maxResidentialMb": 0,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (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": [
        "https://www.properstar.co.uk/united-kingdom/london/buy/apartment-house",
        "https://www.properstar.co.uk/united-kingdom/london/buy/apartment-house?p=2",
        "https://www.properstar.co.uk/united-kingdom/london/rent/apartment-house",
        "https://www.properstar.co.uk/united-kingdom/buy/apartment-house",
        "https://www.properstar.co.uk/united-kingdom/london/buy/apartment-house?price=500000-1000000&bedrooms=2",
        "https://www.properstar.co.uk/united-kingdom/london/buy/apartment-house?sort=price-asc",
        "https://www.properstar.co.uk/united-kingdom/london/buy",
        "https://www.properstar.co.uk/switzerland/buy/apartment-house",
        "https://www.properstar.co.uk/listing/121377505",
        "https://www.properstar.co.uk/agency/the-private-realtor-ltd/6482545"
    ],
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/properstar-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": [
        "https://www.properstar.co.uk/united-kingdom/london/buy/apartment-house",
        "https://www.properstar.co.uk/united-kingdom/london/buy/apartment-house?p=2",
        "https://www.properstar.co.uk/united-kingdom/london/rent/apartment-house",
        "https://www.properstar.co.uk/united-kingdom/buy/apartment-house",
        "https://www.properstar.co.uk/united-kingdom/london/buy/apartment-house?price=500000-1000000&bedrooms=2",
        "https://www.properstar.co.uk/united-kingdom/london/buy/apartment-house?sort=price-asc",
        "https://www.properstar.co.uk/united-kingdom/london/buy",
        "https://www.properstar.co.uk/switzerland/buy/apartment-house",
        "https://www.properstar.co.uk/listing/121377505",
        "https://www.properstar.co.uk/agency/the-private-realtor-ltd/6482545",
    ],
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/properstar-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": [
    "https://www.properstar.co.uk/united-kingdom/london/buy/apartment-house",
    "https://www.properstar.co.uk/united-kingdom/london/buy/apartment-house?p=2",
    "https://www.properstar.co.uk/united-kingdom/london/rent/apartment-house",
    "https://www.properstar.co.uk/united-kingdom/buy/apartment-house",
    "https://www.properstar.co.uk/united-kingdom/london/buy/apartment-house?price=500000-1000000&bedrooms=2",
    "https://www.properstar.co.uk/united-kingdom/london/buy/apartment-house?sort=price-asc",
    "https://www.properstar.co.uk/united-kingdom/london/buy",
    "https://www.properstar.co.uk/switzerland/buy/apartment-house",
    "https://www.properstar.co.uk/listing/121377505",
    "https://www.properstar.co.uk/agency/the-private-realtor-ltd/6482545"
  ],
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/properstar-scraper --silent --output-dataset

```

## MCP server setup

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