# Adondevivir Peru Property Scraper (`barefoot_grade/adondevivir-pe-scraper`) Actor

Property listings search across Peru's largest real-estate portal. Structured records: price, area, rooms, address, images and listing URLs. Part of a multi-country real-estate scraper fleet.

- **URL**: https://apify.com/barefoot\_grade/adondevivir-pe-scraper.md
- **Developed by:** [Philip Kirkbride](https://apify.com/barefoot_grade) (community)
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.40 / 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.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Navent Real-Estate Scraper (Adondevivir + ZonaProp + Inmuebles24)

A conservative, plain-HTTP Apify Actor that extracts public property listings from the Navent real-estate family across three countries into one normalized dataset. One codebase, two parser dialects, one output contract — per-country Apify records are push-time decisions, not code forks (same pattern as the VTEX fleet in `actors/metro-peru-scraper/`).

### Supported portals

| `portal` | API host | Country | Local currency | Parser dialect |
|---|---|---|---|---|
| `adondevivir` (default) | api.adondevivir.com | PE | PEN | `product-dom` |
| `zonaprop` | api.zonaprop.com.ar | AR | ARS | `realestate-ldjson` |
| `inmuebles24` | api.inmuebles24.com | MX | MXN | `realestate-ldjson` |

All three lanes were verified live (2026-09-04, issue #25). The `portal` input determines the API host, country, local currency, parser dialect and search-URL grammar of every record **by construction** — `country`/`currency`/`dialect` are never inputs. A record's `currency` is parsed from the listing's own price tag (`S/`/`MN`/bare `$` → the portal's local currency; `USD` stays USD — dollar pricing is common on PE/AR listings), because the first currency shown on dual-priced PE cards (`S/ 2,010,000 · USD 600,000`) is the local one.

#### Access lane

`www.<domain>` returns 403 (gzip challenge) on all three portals; the **`api.<domain>` subdomain serves the full site** over plain HTTP — no proxy, no challenge solving — but requires a browser-grade User-Agent (a generic client UA gets 403 there too). Listing URLs emitted on records use the api subdomain (the verified plain-HTTP lane); the same paths resolve in a browser on `www.<domain>`.

#### Two dialects, one schema

The family shares one listado platform ("RPLIS"): identical DOM card markup everywhere, so cards (price, features, location, gallery) are the record base on every portal. The JSON-LD side differs and is dispatched per config:

- **`product-dom` (PE)** — JSON-LD is an aggregate `Product` + per-listing `SellAction` blocks (url, title, floor size, structured address, bedroom/bathroom counts). No per-listing image or date exists in PE JSON-LD, so `imageUrl` comes from the card and `postedAt` is `null`.
- **`realestate-ldjson` (AR/MX)** — per-listing `RealEstateListing.mainEntity` (url, image, publisher name, `datePosted`) plus `Apartment`-style unit blocks (title, postal address) joined by image URL. `postedAt` is normalized from US-formatted `M/D/YY` JSON-LD dates.

Both dialects map into one output schema; `tests/test_scraper.py::CrossDialectParityTest` guards that the three per-country datasets stay field-for-field compatible.

Search-URL grammar also rides in `SITE_CONFIG`: Argentina drops the `en-` infix (`departamentos-venta.html`, `departamentos-alquiler.html`) while PE/MX keep it (`departamentos-en-venta.html`, `casas-en-alquiler.html`). Pagination is `-pagina-N.html` on all three.

### Input

- `portal`: one of the three slugs above (default `adondevivir`)
- `operation`: `sale` (default) or `rental`
- `propertyType`: `departamentos` (default) or `casas`
- `maxPages`: 1–5 search-result pages (30 postings per page)
- `maxResults`: 1–200 total listings (default 50)
- `requestDelaySecs`: 0.5–10 seconds between page requests (default 1.0)

Exactly these inputs are implemented; `tests/test_actor_schemas.py` guards the schema ↔ `normalize_input()` contract (no phantom inputs).

### Output

Each dataset item contains the posting id, a composite `listingKey` (`source:id`, e.g. `zonaprop.com.ar:59132079`), the listing URL on the api subdomain (tracking parameters removed), title, price + parsed currency, `areaM2` (upper bound for ranges like `100 a 129 m² lote`), `rooms` (Argentinian ambientes; null where the portal lacks the concept), `bedrooms` (dorm./rec.), `bathrooms`, `parkings`, address line, publisher (AR/MX; null on PE, whose cards carry only a logo), `postedAt` (ISO date on AR/MX; null on PE), image URL, the run's `operation`/`propertyType`, and portal/country/dialect/source provenance plus `collectedAt`. Unavailable fields are `null`; "Consultar precio" listings carry `price: null`.

Join on `listingKey`, never the bare `id` — posting id spaces overlap across portals of the family.

### Local development

```bash
uv sync --dev
PYTHONPATH=src .venv/bin/python -m unittest discover -s tests -v
```

The dialect fixtures are captured from the live api-subdomain pages (descriptions truncated). Deployment: `APIFY_TOKEN=... python tools/navent_fanout_push.py` from the repo root — see `tools/navent_fanout_push.py --dry-run` and `docs/FINDINGS.md`.

### Crawl policy

- Public search pages on the api subdomain only, no login or private APIs
- Maximum five search-result pages per run
- At least 0.5 seconds between page requests; short exponential backoff (2s/4s) on transient transport errors and 429/5xx only
- No anti-bot bypasses (the api lane needs none)

# Actor input Schema

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

Navent portal to search. The portal determines the domain, country, local currency, parser dialect and search-URL grammar of every emitted record by construction; currency on a record is parsed from the listing's own price tag (USD listings are common in PE/AR).

## `operation` (type: `string`):

Listing operation to search: sale or rental.

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

Property type to search: apartments (departamentos) or houses (casas).

## `maxPages` (type: `integer`):

Search-result pages to fetch (30 postings per page).

## `maxResults` (type: `integer`):

Stop after this many listings.

## `requestDelaySecs` (type: `number`):

Minimum 0.5 seconds.

## Actor input object example

```json
{
  "portal": "adondevivir",
  "operation": "sale",
  "propertyType": "departamentos",
  "maxPages": 1,
  "maxResults": 50,
  "requestDelaySecs": 1
}
```

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("barefoot_grade/adondevivir-pe-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("barefoot_grade/adondevivir-pe-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 '{}' |
apify call barefoot_grade/adondevivir-pe-scraper --silent --output-dataset

```

## MCP server setup

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