# ZAP Imóveis & VivaReal Scraper – Brazil Property Listings (`kyungminlee/zap-vivareal-property-scraper`) Actor

Scrape Brazilian property listings for sale or rent from ZAP Imóveis and VivaReal in one run, de-duplicated by listing ID. Price, condo fee, IPTU, area, rooms, address, coordinates, amenities, photos, agency. No login, no browser. Unofficial. JSON/CSV/API.

- **URL**: https://apify.com/kyungminlee/zap-vivareal-property-scraper.md
- **Developed by:** [Kyungmin Lee](https://apify.com/kyungminlee) (community)
- **Categories:** Real estate, Lead generation, Business
- **Stats:** 5 total users, 3 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 listings

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

### What does ZAP Imóveis & VivaReal Scraper do?

**ZAP Imóveis & VivaReal Scraper** collects Brazilian property listings for **sale or rent** from [ZAP Imóveis](https://www.zapimoveis.com.br) and [VivaReal](https://www.vivareal.com.br), two of Brazil's largest real estate portals, in a single run. You choose a portal (or both), a transaction, one or more places such as `sp/sao-paulo` or `rj/rio-de-janeiro/zona-sul/copacabana`, and a property type. It returns one clean row per listing with **price, condo fee (condomínio), IPTU, usable area, bedrooms, bathrooms, parking spaces, amenities, street, neighborhood, city, state, coordinates, photos and the advertising agency**.

When you search both portals, a listing published on both (same listing ID) is saved only once.

It reads the same public result pages an anonymous visitor sees. No account, cookies, API key or browser is needed.

This is an **unofficial** tool. It is not affiliated with, endorsed by or connected to ZAP Imóveis, VivaReal or Grupo OLX.

### Why use ZAP Imóveis & VivaReal Scraper?

- **Market research and pricing:** compare asking prices, rent levels, condo fees and price per m² (`pricePerSqm`) by city, neighborhood and property type.
- **Investment screening:** filter by price, bedrooms and area, and export candidates with coordinates for a map.
- **Rental analysis:** monthly rent, condo fee and IPTU side by side for each listing.
- **Lead lists of agencies:** see which real estate companies (imobiliárias) advertise most in an area.
- **Monitoring:** schedule the run daily or weekly to follow new listings and price changes.

Two portals, one schema: field names are English, numbers are real numbers, and texts (titles, neighborhoods) stay in Portuguese as published.

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

1. Click **Try for free**.
2. Pick the portal (ZAP, VivaReal or both), **sale** or **rent**, and the property type.
3. Add one or more locations, one per line, for example `sp/sao-paulo` or `pr/curitiba/batel`.
4. Optionally set price, bedroom or area filters, and turn on **Open each listing page** if you need publication dates.
5. Click **Start** and download the results as JSON, CSV or Excel, or read them through the Apify API.

#### How to write a location

Use the slugs that appear in the portals' own URLs: `state/city`, `state/city/neighborhood` or `state/city/zone/neighborhood`. Accents and capital letters are converted for you, so `SP / São Paulo / Moema` also works.

| You type | Searches |
|---|---|
| `sp/sao-paulo` | São Paulo city |
| `sp/sao-paulo/zona-sul/moema` or `sp/sao-paulo/moema` | Moema (the Actor tries the São Paulo zones for you) |
| `rj/rio-de-janeiro/zona-sul/copacabana` | Copacabana, Rio de Janeiro |
| `pr/curitiba/batel` | Batel, Curitiba (cities without zones) |
| `mg/belo-horizonte` | Belo Horizonte |
| `sc` | the whole state of Santa Catarina |

The state is the two-letter UF (`sp`, `rj`, `mg`, `pr`, `rs`, `sc`, `ba`, `df`...). ZAP shortens some neighborhood words (`vl-mariana`, `jd-paulista`) while VivaReal spells them out (`vila-mariana`); you can type either form and the Actor uses the right one on each portal.

### Input

```json
{
  "site": "both",
  "transaction": "rent",
  "locations": ["sp/sao-paulo", "rj/rio-de-janeiro/zona-sul/copacabana"],
  "propertyType": "apartment",
  "maxListings": 500,
  "minPrice": 2000,
  "maxPrice": 8000,
  "minBedrooms": 2,
  "includeDetails": false,
  "deduplicate": true,
  "proxyConfiguration": { "useApifyProxy": false }
}
```

- `site`: `both` (default), `zap` or `vivareal`.
- `transaction`: `sale` or `rent`.
- `locations`: places as described above. Each location is one search.
- `propertyType`: `apartment`, `house`, `condo-house`, `townhouse`, `penthouse`, `flat`, `kitnet`, `land`, `farm`, `office`, `store`, `warehouse`, `all`, and a few ZAP-only types (`studio`, `loft`, `village-house`, `building`, `garage`, `corporate-floor`, `hotel`).
- `maxListings`: listings to save per location, both portals combined, after filters and de-duplication.
- `minPrice`/`maxPrice` (BRL; monthly rent for rent searches), `minBedrooms`/`maxBedrooms`, `minArea`/`maxArea` (m²): applied after fetching (see Tips).
- `includeDetails`: also open each listing's own page for `publishedAt`, `updatedAt` and the full description.
- `startUrls` (advanced): ZAP or VivaReal search page URLs, for example a street or amenity page such as `https://www.zapimoveis.com.br/aluguel/imoveis/rj+rio-de-janeiro/quintal/`.
- `proxyConfiguration`: off by default (see FAQ).

### Output

One dataset item per listing:

```json
{
  "listingId": "2778240426",
  "site": "zap",
  "url": "https://www.zapimoveis.com.br/imovel/venda-apartamento-3-quartos-com-piscina-moema-zona-sul-sao-paulo-sp-149m2-id-2778240426/",
  "title": "Apartamento para comprar com 149 m², 3 quartos, 4 banheiros, 3 vagas",
  "transaction": "sale",
  "price": 3200000,
  "currency": "BRL",
  "condoFee": 2100,
  "iptu": 1517,
  "pricePerSqm": 21476.51,
  "usableArea": 149,
  "bedrooms": 3,
  "bathrooms": 4,
  "suites": 3,
  "parkingSpaces": 3,
  "amenities": ["GARAGE", "ADULT_POOL", "PETS_ALLOWED", "PARTY_HALL", "AIR_CONDITIONING", "POOL"],
  "propertyType": "APARTMENT",
  "street": "Rua Periquito",
  "streetNumber": "390",
  "neighborhood": "Vila Uberabinha",
  "zone": "Zona Sul",
  "city": "São Paulo",
  "stateCode": "SP",
  "latitude": -23.599786,
  "longitude": -46.671486,
  "imageCount": 34,
  "imageUrl": "https://resizedimgs.zapimoveis.com.br/img/vr-listing/df8a5cdbc2ab82dd12b373f3717d50e5/foto.webp?action=fit-in&dimension=1080x720",
  "advertiserType": "company",
  "advertiserName": "Zimmermann Imóveis",
  "advertiserCreci": "15319-J-SP",
  "publishedAt": null,
  "updatedAt": null,
  "searchLocation": "sp/sao-paulo",
  "totalResults": 455052,
  "scrapedAt": "2026-10-07T00:00:00+00:00"
}
```

A `SUMMARY` record in the run's key-value store lists, per location and portal, the resolved search URL, the portal's total result count, pages read, listings saved, filtered-out and duplicate counts, and request timings.

### Data fields

| Field | Description |
|---|---|
| `listingId`, `site`, `url` | Portal listing ID (shared by ZAP and VivaReal), the portal it was saved from (`zap`/`vivareal`), and the listing URL |
| `title`, `transaction` | Listing title as published; `sale` or `rent` |
| `price`, `currency` | Sale price, or monthly rent for rentals, in BRL |
| `salePrice`, `rentPrice`, `rentPeriod`, `hasPriceRange` | Both prices when published, rent period (almost always `MONTHLY`; a few rentals are priced `YEARLY`), and whether a development shows a price range (then `price` is the lowest) |
| `condoFee`, `iptu`, `pricePerSqm` | Monthly condo fee, IPTU as published, price divided by usable area |
| `usableArea` | Usable area in m² |
| `bedrooms`, `bathrooms`, `suites`, `parkingSpaces`, `unitFloor` | Room counts and floor |
| `amenities` | Amenity codes such as `POOL`, `GYM`, `ELEVATOR`, `PETS_ALLOWED`, `FURNISHED` |
| `propertyType`, `listingType`, `publicationType` | e.g. `APARTMENT`; `USED` or `DEVELOPMENT` (new launch); the listing's ad level |
| `street`, `streetNumber`, `neighborhood`, `zone`, `city`, `state`, `stateCode`, `condominiumName` | Address as published (the street number is often hidden by the advertiser) |
| `latitude`, `longitude`, `isApproximateLocation` | Coordinates and whether the portal marks them as approximate |
| `imageCount`, `imageUrl`, `imageUrls` | Number of photos, first photo and all photo URLs (1080×720) |
| `advertiserType`, `advertiserName`, `advertiserCreci`, `advertiserTier`, `developerName` | `company`, `individual` or `unknown`; company name and company CRECI only (see FAQ); developer of a new launch |
| `externalId`, `stamps` | The advertiser's own reference code; portal badges |
| `publishedAt`, `updatedAt`, `description` | From the listing page, only with `includeDetails` (phone numbers and e-mails in descriptions are removed) |
| `searchLocation`, `searchUrl`, `position`, `totalResults`, `scrapedAt` | Which search found the listing, its position, the portal's total for that search, UTC collection time |

### Pricing: how much does it cost to scrape ZAP Imóveis and VivaReal?

This Actor uses pay-per-event pricing. You pay **$1.20 per 1,000 listings** (`listing` event, $0.0012 each). With **Open each listing page** on, each listing whose page was read adds a `listing-detail` event ($0.0008), so $2.00 per 1,000 listings with dates and full descriptions. Platform usage is included.

Listings removed by your filters, duplicates, and pages that fail are not charged. Set a maximum charge for the run and the Actor stops at that limit.

### Tips and limits

- **1,500 listings per search and portal.** Both portals stop at page 50 (30 listings per page). For a big city, search neighborhoods or property types separately. With `both`, the two portals list the same inventory in a different order, so together they reach up to roughly twice as many unique listings before the limit; in small areas most VivaReal results are duplicates of ZAP ones (skipped, not charged).
- **Filters are applied after fetching.** The portals' filter and sort parameters (`precoMaximo=`, `quartos=`, `ordem=`, `onde=`...) are disallowed for crawlers by their robots.txt, so this Actor never sends them. It reads results in the portal's default order and keeps those that match. Narrow filters therefore need more pages per saved listing, and the 1,500-per-search limit applies before filtering. Path-based pages are fine: for example VivaReal's `.../apartamento_residencial/2-quartos/` can be given in `startUrls`.
- **Only robots.txt-allowed pages.** Every URL is checked against the portal's live robots.txt before it is requested. Start URLs that robots.txt disallows (for some amenity pages, for example) are skipped and reported in the log.
- **Polite by design:** one request at a time with a random 1.5–3 s pause, and longer pauses when a portal slows down.
- Rentals: `price` is the monthly rent. Some sale listings of new developments show a price range; `price` is then the lowest value and `hasPriceRange` is true.

### FAQ

#### Do I need a ZAP or VivaReal account?

No. The Actor only reads public listing pages that anyone can open without logging in.

#### Why is the proxy off by default?

In our tests both portals answered requests from Apify's own servers well, and worse through shared datacenter proxies. If a run gets blocked anyway, the Actor waits, retries, and then switches to Apify Proxy automatically (Brazilian residential IPs if your plan includes them). You can also turn a proxy on yourself; if that proxy is blocked repeatedly, the Actor switches to a direct connection.

#### Why are some advertiser names empty?

Many listings are published by individual brokers (CRECI ending in `-F-`) or private owners. Their names and phone numbers are personal data, so this Actor never collects them. It returns the name and CRECI only for companies (real estate agencies with a company CRECI, or advertisers whose name is clearly a business). Phone numbers are not returned for anyone.

#### Are ZAP Imóveis and VivaReal the same listings?

Both portals belong to the same group and show essentially the same inventory with the same listing IDs (their result totals match), but in a different order. With `site: both` the Actor reads both alternately and keeps each listing ID once; `site` tells you which portal the saved copy came from. For a quick sample of a small area, one portal is enough.

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

The Actor collects public listing data at a polite rate, follows robots.txt and leaves out personal data of individuals. You are responsible for how you use the data and for complying with the laws and terms that apply to you.

#### Is this an official ZAP or VivaReal product?

No. This is an unofficial, independent tool. It is not affiliated with ZAP Imóveis, VivaReal or Grupo OLX. ZAP Imóveis and VivaReal are trademarks of their owners.

Found a problem or need another field? Open an issue on the **Issues** tab.

# Actor input Schema

## `site` (type: `string`):

Which portal(s) to search. With both, pages from ZAP Imóveis and VivaReal are read alternately and a listing that appears on both (same listing ID) is saved once.

## `transaction` (type: `string`):

Listings for sale (`venda`) or for rent (`aluguel`).

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

One place per line as `state/city`, `state/city/neighborhood` or `state/city/zone/neighborhood`, using the slugs from the portals' URLs (accents and capitals are converted for you). Examples: `sp/sao-paulo`, `rj/rio-de-janeiro/zona-sul/copacabana`, `sp/sao-paulo/zona-sul/moema`, `pr/curitiba/batel`, `mg/belo-horizonte`, or just `sc` for a whole state. The state is the two-letter UF (`sp`, `rj`, `mg`...). If you leave out the zone of a São Paulo or Rio neighborhood, the Actor tries the usual zones for you. Each location is one search.

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

The property type, as the portals' own path category. Types marked "ZAP only" have no VivaReal page and are searched on ZAP Imóveis only.

## `maxListings` (type: `integer`):

How many listings to save per location (or per start URL), both portals combined, counted after filters and de-duplication. The portals show at most 50 pages (1,500 listings) per search, so for more, split a city into neighborhoods or property types. The run's maximum charge can lower this.

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

Keep listings whose price is at least this amount: the sale price for sale searches, the monthly rent for rent searches.

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

Keep listings whose price (sale price, or monthly rent for rent searches) is at most this amount.

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

Keep listings with at least this many bedrooms (quartos).

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

Keep listings with at most this many bedrooms (quartos).

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

Keep listings with at least this usable area in square meters.

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

Keep listings with at most this usable area in square meters.

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

Also open every saved listing's own page to add the publication date (`publishedAt`), last update (`updatedAt`) and the full description. This is one extra request per listing, so runs take about 30 times longer. Without it, those three fields are empty.

## `deduplicate` (type: `boolean`):

Save each listing ID only once per run, even if it shows up on both portals or in several locations.

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

Optional ZAP Imóveis or VivaReal search result URLs, for example `https://www.zapimoveis.com.br/aluguel/apartamentos/rj+rio-de-janeiro+zona-sul+copacabana/` or `https://www.vivareal.com.br/venda/sp/sao-paulo/apartamento_residencial/2-quartos/`. Only the path is used: query parameters such as `onde=`, `tipos=` or `precoMaximo=` are removed because the portals' robots.txt disallows them for crawlers, and URLs whose path robots.txt disallows are skipped. Each URL is one search on its own portal.

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

Off by default: in our tests the portals answered best without a proxy. If they block the run anyway (several 403/Cloudflare answers in a row), the Actor automatically switches to Apify Proxy (Brazilian residential if your plan includes it). If you turn a proxy on here, it is used from the first request, and the Actor switches to a direct connection if that proxy gets blocked repeatedly.

## Actor input object example

```json
{
  "site": "both",
  "transaction": "sale",
  "locations": [
    "sp/sao-paulo"
  ],
  "propertyType": "apartment",
  "maxListings": 40,
  "includeDetails": false,
  "deduplicate": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

No description

## `summary` (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": [
        "sp/sao-paulo"
    ],
    "maxListings": 40,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("kyungminlee/zap-vivareal-property-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": ["sp/sao-paulo"],
    "maxListings": 40,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("kyungminlee/zap-vivareal-property-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": [
    "sp/sao-paulo"
  ],
  "maxListings": 40,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call kyungminlee/zap-vivareal-property-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kyungminlee/zap-vivareal-property-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/Teglj4DFZBmiTBBse/builds/7GFbNSvIRYN2PA8ex/openapi.json
