# ZAP Imóveis & VivaReal Scraper — Brazil Property Listings (`tortuga/zap-imoveis-scraper`) Actor

Scrape ZAP Imóveis and VivaReal listings for sale or rent from search URLs or city/neighbourhood + filters: price, condo fee, IPTU, area, rooms, address, coordinates, photos, agency.

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

## Pricing

from $1.50 / 1,000 listing 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

## ZAP Imóveis & VivaReal Scraper

Scrape property listings from ZAP Imóveis and VivaReal, Brazil's two biggest real estate portals (both run by Grupo OLX), for sale (venda) or for rent (aluguel). For each listing you get the price, the condo fee (condomínio), the IPTU, the total monthly rent, the usable and total area, bedrooms, bathrooms, suites and parking spaces, and the property type. You also get the address as the advertiser publishes it (street, number, neighbourhood, zone, city, state, CEP), the coordinates, the amenities, every photo URL, and the advertiser's name, CRECI, profile link and type (agency, developer or private owner). The listing date and the listing URL are included too. Start from any ZAP or VivaReal search URL, or type a city or neighbourhood and pick filters. Turn on **Include description and last update** to also get each listing's full description text, its own title, the last update date and the portals it is published on.

The actor reads the same JSON API the two websites load (30 listings per request), not the HTML. That keeps the output complete and stable and runs fast: about 1,400 listings a minute (1,491 listings in 63 seconds in our test). You pay only for the listings you get.

### What data does ZAP Imóveis & VivaReal Scraper extract?

| Field | Description |
|---|---|
| `id`, `url`, `portal` | Listing id (the same on both portals), the listing URL, and `ZAP` or `VIVAREAL` |
| `title` | The portal's headline, e.g. `"Apartamento com 2 Quartos à venda, 69m²"` |
| `businessType` | `SALE` (venda) or `RENTAL` (aluguel) |
| `price`, `currency` | Sale price or rent in BRL (`null` when the advertiser hides the price; then `priceOnRequest` is `true`) |
| `condoFee`, `iptuYearly` | Monthly condo fee (condomínio) and yearly IPTU, when given |
| `rentTotalMonthly`, `rentalPeriod` | Rent + condo + IPTU per month as the portal shows it, and `MONTHLY` / `DAILY` (rentals) |
| `salePrice`, `rentPrice` | Both prices when a property is offered for sale and for rent |
| `propertyType`, `propertyTypeCode` | e.g. `"Apartamento"` / `APARTMENT`, `"Casa de Condomínio"` / `CONDOMINIUM`, `"Sobrado"`, `"Cobertura"`, `"Conjunto Comercial / Sala"` |
| `unitType`, `unitSubType`, `usageType`, `listingType` | The portals' raw type codes; `listingType` is `USED` or `DEVELOPMENT` (lançamento) |
| `usableArea`, `totalArea` | Área útil and área total in m² |
| `bedrooms`, `bathrooms`, `suites`, `parkingSpaces`, `floor` | Counts; `floor` is the unit's floor when given |
| `ranges` | For new developments with several unit sizes: `{"usableArea": [59, 69], "bedrooms": [2, 3]}` |
| `street`, `streetNumber`, `zipCode` | Only as far as the advertiser publishes them (see the privacy FAQ) |
| `neighborhood`, `zone`, `city`, `state`, `stateCode` | e.g. `Pinheiros`, `Zona Oeste`, `São Paulo`, `São Paulo`, `SP` |
| `latitude`, `longitude`, `isApproximateLocation`, `addressDisplay` | Coordinates as the map shows them; `addressDisplay` is `ALL`, `STREET` or `NEIGHBORHOOD` |
| `amenities` | e.g. `["POOL", "GYM", "ELEVATOR", "BARBECUE_GRILL", "PETS_ALLOWED", "FURNISHED"]` |
| `images`, `videos` | All photo URLs (1920 px) and video links |
| `advertiserName`, `advertiserType` | Agency, developer or owner name, and `agency` / `developer` / `owner` |
| `advertiserCreci`, `advertiserUrl`, `advertiserId` | CRECI licence number, the advertiser's page on the portal, id |
| `advertiserTier`, `advertiserVerified`, `advertiserSince`, `advertiserLogo` | Plan tier, verified badge, on the portal since, logo |
| `externalId`, `condominiumName`, `publicationType`, `stamps` | The advertiser's own reference code, building name, ad tier (`STANDARD`, `PREMIUM`, `SUPER_PREMIUM`), badges |
| `createdAt` | When the listing was published (ISO, UTC) |
| `search`, `scrapedAt` | The search the listing came from and the time of the run |

With **Include description and last update** you also get:

| Field | Description |
|---|---|
| `description` | Full description text (line breaks kept) |
| `advertiserTitle` | The advertiser's own title for the listing |
| `updatedAt` | Last update of the listing |
| `publishedOn` | The portals it is published on, e.g. `["OLX", "VIVAREAL", "ZAP"]` |

### How to scrape ZAP Imóveis and VivaReal listings

1. Open [zapimoveis.com.br](https://www.zapimoveis.com.br/) or [vivareal.com.br](https://www.vivareal.com.br/), search for a city or neighbourhood, set any filters (type, price, bedrooms, area, amenities, sort, ...) and copy the address from your browser. Or skip this step and type the place in **Locations** and pick the filters in the input.
2. Paste the URL into **Start URLs**. You can add several searches from both sites, and single listing URLs too.
3. Set **Max items** and start the run. Download the results as JSON, CSV or Excel, or read them through the API.

The Locations search covers the filters most people use: sale or rent, property types, price, usable area, bedrooms, bathrooms and parking spaces. Any other filter on the websites works through a search URL: amenities, condo fee, total rent with condo fee, new developments (lançamentos), virtual tour, near the metro and more.

### Input example

```json
{
  "locations": ["Pinheiros, São Paulo", "Copacabana, Rio de Janeiro"],
  "portal": "zap",
  "businessType": "RENTAL",
  "propertyTypes": ["APARTMENT"],
  "maxPrice": 6000,
  "bedrooms": ["2", "3"],
  "parkingSpaces": ["1"],
  "sort": "newest",
  "maxItems": 500,
  "includeDetails": false
}
```

Or with URLs:

```json
{
  "startUrls": [
    { "url": "https://www.zapimoveis.com.br/venda/apartamentos/sp+sao-paulo+zona-oeste+pinheiros/?quartos=2,3&precoMaximo=1500000" },
    { "url": "https://www.vivareal.com.br/aluguel/rj/rio-de-janeiro/zona-sul/copacabana/apartamento_residencial/" },
    { "url": "https://www.zapimoveis.com.br/imovel/venda-apartamento-3-quartos-mobiliado-agua-verde-curitiba-pr-92m2-id-2916766574/" }
  ],
  "maxItems": 1000
}
```

Locations accept a city (`Curitiba`, `Curitiba - PR`), a neighbourhood with its city (`Moema, São Paulo`), a zone (`Zona Sul, Rio de Janeiro`) or a street. The actor looks each one up with the portal's own autocomplete; the SUMMARY record in the key-value store shows which place was used.

### Output example

```json
{
  "id": "2916768579",
  "url": "https://www.zapimoveis.com.br/imovel/venda-apartamento-2-quartos-com-churrasqueira-ecoville-curitiba-pr-69m2-id-2916768579/",
  "portal": "ZAP",
  "title": "Apartamento com 2 Quartos à venda, 69m²",
  "businessType": "SALE",
  "price": 780000,
  "priceOnRequest": false,
  "condoFee": 1175,
  "iptuYearly": 200,
  "salePrice": 780000,
  "currency": "BRL",
  "propertyType": "Apartamento",
  "propertyTypeCode": "APARTMENT",
  "listingType": "USED",
  "usableArea": 69,
  "totalArea": 100,
  "bedrooms": 2,
  "bathrooms": 2,
  "suites": 1,
  "parkingSpaces": 1,
  "floor": 14,
  "street": "Rua Paulo Ziliotto",
  "streetNumber": "85",
  "neighborhood": "Campina do Siqueira",
  "city": "Curitiba",
  "state": "Paraná",
  "stateCode": "PR",
  "zipCode": "80740310",
  "latitude": -25.437667,
  "longitude": -49.312622,
  "isApproximateLocation": false,
  "addressDisplay": "ALL",
  "amenities": ["GATED_COMMUNITY", "PLAYGROUND", "ELEVATOR", "GOURMET_SPACE", "BARBECUE_GRILL", "PARTY_HALL"],
  "images": [
    "https://resizedimgs.zapimoveis.com.br/img/vr-listing/86a709acbfa4115f23558a06b3675f6d/foto.webp?action=fit-in&dimension=1920x1080"
  ],
  "videos": [],
  "advertiserType": "agency",
  "advertiserName": "Valphi Negócios Imobiliários",
  "advertiserCreci": "8430-J-PR",
  "advertiserUrl": "https://www.zapimoveis.com.br/imobiliaria/786904/",
  "advertiserTier": "diamond",
  "advertiserVerified": true,
  "advertiserSince": "2022-11-14T17:55:26Z",
  "externalId": "47",
  "publicationType": "STANDARD",
  "createdAt": "2026-10-10T01:13:21.294+00:00",
  "advertiserTitle": "Apartamento de 02 quartos à venda no Ecoville",
  "description": "Apartamento de 02 quartos, sendo 01 suíte, à venda no Ecoville, Curitiba:\n\n- 69m² de área útil\n- Sala para 02 ambientes\n...",
  "updatedAt": "2026-10-10T01:35:59.662Z",
  "publishedOn": ["OLX", "VIVAREAL", "ZAP"],
  "detailsFetched": true,
  "search": "ZAP Imóveis: Curitiba - PR (sale)",
  "scrapedAt": "2026-10-10T01:43:28Z"
}
```

(Shortened: real items carry every field from the table above. Detail fields are absent when **Include description and last update** is off.)

### ZAP Imóveis or VivaReal: which one should I scrape?

Either. Both portals belong to Grupo OLX and list the same properties under the same listing ids (São Paulo for sale: 822,316 listings on ZAP Imóveis and 822,315 on VivaReal in our test; Curitiba: 34,860 on both). The only difference is the site the links point to. If you give searches from both sites, each listing is output once (duplicates are dropped by id, so you are not charged twice). With details on, `publishedOn` tells you on which portals a listing appears.

### How many listings can I get per search?

The portals serve at most **1,500 listings per search** (50 pages of 30). When a search has more and **Max items** asks for more, the actor cuts it into price bands of up to 1,500 listings each, using a few tiny count requests, and collects them all: for Curitiba rentals (8,003 listings when the run started) the actor returned 8,017 listings in 8 price bands, as new listings arrived during the run. Results then come band by band, cheapest band first, and each band keeps your sort order. Very broad searches (all of São Paulo for sale: 822,000 listings) need hundreds of bands; the actor uses up to 150 count requests per search (about 100,000 listings), so split those yourself by neighbourhood, type or price. Turn off **Split searches over 1,500 results** to stop at the first 1,500.

### Does it work without login? Does it collect phone numbers or addresses?

No login and no account are needed: the actor reads only what anyone sees on the websites. It does **not** export phone or WhatsApp numbers, which the portals show only after a click. Street, number, CEP and exact coordinates are exported only when the advertiser publishes them on the listing (`addressDisplay` = `ALL`; `STREET` shows the street without number; `NEIGHBORHOOD` shows only the neighbourhood and the approximate map point). For private owners (`advertiserType` = `owner`) the actor never exports the street, number or CEP, and rounds the coordinates to about 1 km. Agencies' and developers' names, CRECI and profile pages are business data and are included.

### Do I need a proxy?

Yes. Both portals sit behind Cloudflare, which refuses many datacenter IP addresses (about half of the requests in our tests; the actor retries them, so runs still finish, only slower). The default proxy, Apify **Residential** proxy in Brazil, worked on every request in our tests (over 300 requests, no retries). Residential traffic is billed by Apify per GB, and this actor uses little of it: about 1 MB per 1,000 listings, about 8 MB per 1,000 with descriptions, and about 0.15 MB per start URL (the search page is opened once to read its filters). A refused request is retried automatically on a fresh IP and, if needed, on the other portal's API host.

### Monitor changes on a schedule

Turn on **Only new or changed items** to watch a search. Each run remembers what earlier runs returned and outputs only new listings and listings whose price or condo fee changed. Unchanged listings are dropped before any detail request, so they are not charged. **Max items** then sets how many listings are checked per run; keep the "Newest first" sort, e.g. 100-300 for a neighbourhood. Save the input as a task in Apify and add a Schedule (for example daily). Give each watch its own **Monitoring key** if several schedules use the same search. Every output item gets `changeType` (`new` or `changed`), and changed items get `previousValues` with the old values, e.g. `{"price": 780000}`. The actor also remembers a few listings just below the window without outputting or charging them, so an older listing that moves into the window because a newer one was taken down is not reported as new. Sold or removed listings simply stop appearing.

### Pricing

Pay per result: you are charged for each listing saved to the dataset, plus a small extra per listing when **Include description and last update** is on (and for single listing URLs, which always load the full record). See the Pricing tab for the current prices. There is no start fee. Platform usage is small: at the default 512 MB memory our test runs used about $0.016 per 1,000 listings without descriptions (residential proxy traffic, compute and dataset writes) and about $0.08 per 1,000 with descriptions.

### Integrations and API

Run it from the Apify API or a schedule, and send the results to Google Sheets, Slack, Make, Zapier, n8n or a webhook. Use `maxItems` to cap the cost of each run. Typical uses are price per m² studies by neighbourhood, rent vs. sale yield analysis, agency inventory tracking, new-listing alerts and lead lists of real estate agencies.

### Is it legal to scrape ZAP Imóveis and VivaReal?

This actor collects publicly visible listing data that anyone can see on the websites without logging in. It does not log in, it does not collect phone numbers or anything else behind a click, and it does not export private owners' street addresses. Names of private owners and of individual brokers are personal data under the LGPD: you are responsible for having a legitimate purpose and a legal basis for processing them, and for respecting the portals' terms of use. If in doubt, ask a lawyer.

### Support

Found a bug or need a field that is not there? Open an issue on the Issues tab with the input you used and the run link.

# Actor input Schema

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

ZAP Imóveis or VivaReal search URLs with any filters set in the browser (open the search on zapimoveis.com.br or vivareal.com.br and copy the address bar, e.g. https://www.zapimoveis.com.br/venda/apartamentos/sp+sao-paulo+zona-oeste+pinheiros/), or single listing URLs (.../imovel/...-id-2916766574/), which return that listing with full details. Leave empty if you use Locations below.

## `locations` (type: `array`):

Cities, neighbourhoods (bairros), zones or streets, one per line, e.g. 'Pinheiros, São Paulo', 'Copacabana, Rio de Janeiro', 'Curitiba - PR', 'Zona Sul, Rio de Janeiro'. Each is looked up with the portal's own autocomplete and searched separately.

## `portal` (type: `string`):

Which site the Locations searches run on (start URLs always use their own site). Both portals list the same properties under the same listing IDs; the choice decides which site's links you get.

## `businessType` (type: `string`):

Listings for sale (venda) or for rent (aluguel). Applies to Locations searches; start URLs keep their own.

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

Only these property types. Leave empty for all.

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

Minimum sale price, or minimum monthly rent for rentals, in reais.

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

Maximum sale price, or maximum monthly rent for rentals, in reais.

## `minArea` (type: `integer`):

Minimum usable area (área útil) in square metres.

## `maxArea` (type: `integer`):

Maximum usable area (área útil) in square metres.

## `bedrooms` (type: `array`):

Number of bedrooms, as on the portals: pick one or more; '4+' means 4 or more. Leave empty for any.

## `bathrooms` (type: `array`):

Number of bathrooms; '4+' means 4 or more. Leave empty for any.

## `parkingSpaces` (type: `array`):

Number of parking spaces; '4+' means 4 or more. Leave empty for any.

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

Order of the results. Applies to Locations searches and to start URLs that have no sort of their own. 'Newest first' is stable between pages and best for monitoring; 'Relevance' is the portals' default order and reshuffles between requests.

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

Stop after this many listings in total (across all searches). Keeps cost predictable. The portals serve at most 1,500 listings per search; larger searches are split into price bands automatically (see below).

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

Also load each listing's own record: the advertiser's title and full description text, last update date, and the portals it is published on (ZAP, VivaReal, OLX). One extra request per listing; charged as an extra 'detail' event (see pricing).

## `splitLargeSearches` (type: `boolean`):

The portals serve at most 1,500 listings per search. When a search has more and Max items asks for more, cut it into price bands of up to 1,500 listings each (a few tiny count requests) so you can get every listing. Results then come band by band, cheapest band first.

## `onlyNewOrChanged` (type: `boolean`):

Remember what earlier runs of this search returned and output only new listings or listings whose price or condo fee changed. Use with a Schedule; unchanged items are not charged. With this on, Max items is the number of listings checked per run (e.g. the newest 200).

## `monitoringKey` (type: `string`):

Name for this watch, so several schedules on the same search stay separate; defaults to a key derived from the search input

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

ZAP Imóveis and VivaReal sit behind Cloudflare, which refuses many datacenter IPs (about half of the requests in tests; they are retried, so runs are slower). Apify RESIDENTIAL proxy (Brazil) is the default and worked on every request in tests; residential traffic is billed by Apify per GB, and this actor uses about 1 MB per 1,000 listings (about 8 MB with descriptions) plus about 0.15 MB per start URL.

## Actor input object example

```json
{
  "locations": [
    "Pinheiros, São Paulo"
  ],
  "portal": "zap",
  "businessType": "SALE",
  "sort": "newest",
  "maxItems": 100,
  "includeDetails": false,
  "splitLargeSearches": true,
  "onlyNewOrChanged": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "BR"
  }
}
```

# 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 = {
    "locations": [
        "Pinheiros, São Paulo"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "BR"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("tortuga/zap-imoveis-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 = {
    "locations": ["Pinheiros, São Paulo"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "BR",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("tortuga/zap-imoveis-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 '{
  "locations": [
    "Pinheiros, São Paulo"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "BR"
  }
}' |
apify call tortuga/zap-imoveis-scraper --silent --output-dataset

```

## MCP server setup

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