# Fotocasa Spain Property Scraper (`barefoot_grade/fotocasa-es-scraper`) Actor

Property listings search across Fotocasa, one of Spain's largest real-estate portals. 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/fotocasa-es-scraper.md
- **Developed by:** [Philip Kirkbride](https://apify.com/barefoot_grade) (community)
- **Stats:** 2 total users, 1 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

## Fotocasa ES Property Scraper (Spain — residential lane)

A conservative, **non-browser, residential-API lane** Apify Actor that extracts public property listings from Fotocasa.es (Spain) into the Navent family record shape (same schema family as `actors/navent-realestate-scraper/`: price + currency, area, rooms/bedrooms/bathrooms, address, listing URL, image).

### Access lane (verified live 2026-09-04, issue #27)

- `www.fotocasa.es` returns **403** on plain/datacenter egress; **Spanish residential egress** (e.g. DataImpulse `__cr.es` country suffix) returns **200** for both the search page and the internal API.
- The search page is **server-rendered**: listing cards ride the initial HTML, so plain HTTP (httpx + browser-grade headers) is sufficient — no browser, no OAuth, no challenge solving, no Camofox.
- The internal API host is **`web.gw.fotocasa.es`** (not `search.gw`). The Actor uses its location-resolution endpoint (`/v2/propertysearch/urllocationsegments`) as a helper that logs the numeric location ids for the requested location/zone; the full search POST body schema was never captured and is unnecessary — the SSR page takes location slugs directly.
- Listing URL grammar: `/es/comprar/vivienda/{location}/{slug}/{numericId}/d`.

### Egress

The Actor needs Spanish residential egress. Two supported configurations, in order of preference:

1. **External residential proxy** (default for portability): pass `proxyUrl` (+ `proxyUsername`/`proxyPassword` or URL-embedded credentials). For DataImpulse, a username without a country suffix automatically gains `__cr.es` (Spanish exits).
2. **Apify platform proxy**: with no proxy input, the Actor requests a RESIDENTIAL configuration pinned to ES and rides it.

A 403 is treated as a retryable egress verdict: each retry goes out on a **fresh proxy session** (new exit IP), and only a 403 persisting to exhaustion raises `ChallengeError` naming the lane. A local run without any proxy credentials falls back to a direct connection with a loud warning (expect 403 — the lane is residential-only by design).

### Input

- `location` — Fotocasa location slug (`madrid-provincia` default; free text is slugified, accents stripped)
- `zone` — zone slug within the location (`todas-las-zonas` default = all zones)
- `operation` — `sale` (comprar, default) or `rental` (alquiler)
- `sortType` — `scoring` (default), `publication`, `price-asc`, `price-desc`
- `maxPages` — 1–5 search pages (~30 cards per SSR page)
- `maxResults` — 1–200 listings (default 50)
- `requestDelaySecs` — 0.5–10 s between page requests (default 1.0)
- `proxyUrl` / `proxyUsername` / `proxyPassword` — residential egress credentials (see above)

### Output (dataset)

One record per listing card, Navent-family shape:

| field | notes |
|---|---|
| `id` / `listingKey` | Fotocasa numeric id from the URL; `listingKey` = `fotocasa.es:{id}` |
| `url` | canonical listing URL (`/es/{comprar\|alquiler}/vivienda/.../{id}/d`), tracking params stripped |
| `price` + `currency` | Spanish format parsed (`365.000 €` → 365000 EUR; rentals carry monthly `€/mes` amounts with the suffix dropped); null for `Precio a consultar` |
| `areaM2` / `rooms` / `bedrooms` / `bathrooms` | card detail chips (`120 m²`, `3 hab.`, `2 baños`) |
| `address` | card location line |
| `imageUrl` | primary card image (Fotocasa CDN) |
| `operation` / `propertyType` / `portal` / `country` / `source` | `fotocasa` / `ES` / `www.fotocasa.es` by construction |
| `scrapedAt` | UTC ISO-8601 run timestamp |

### Development

```
uv venv && uv pip install -r requirements.txt -r requirements-dev.txt pytest
python -m pytest tests/ -q
```

Fixtures under `tests/fixtures/` mirror the DOM markers verified live in the research thread (article cards with `data-panot-component="link-box"`, `re-Card*` classes, Tailwind `@container/w-full` wrappers, 31 cards per page, 2 JSON-LD blocks incl. BreadcrumbList). The live ES-residential capture was **not** re-run from this workspace (no proxy credentials available); see `docs/FINDINGS.md`.

# Actor input Schema

## `location` (type: `string`):

Location to search, as it appears in Fotocasa URL slugs (e.g. 'madrid-provincia', 'barcelona', 'valencia'). Free text is slugified (lowercase, accents stripped, spaces to dashes); 'madrid' resolves to the whole province.

## `zone` (type: `string`):

Zone slug within the location; 'todas-las-zonas' (all zones) covers the whole location.

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

Listing operation: sale (comprar) or rental (alquiler).

## `sortType` (type: `string`):

Search-result ordering: scoring (relevance, default), publication (newest), price-asc or price-desc.

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

Search-result pages to fetch (about 30 listing cards per SSR page).

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

Stop after this many listings.

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

Minimum 0.5 seconds.

## `proxyUrl` (type: `string`):

Spanish residential proxy for the www.fotocasa.es lane (the site 403s non-ES-residential egress). Either a full URL with embedded credentials (http://user:pass@gw.dataimpulse.com:823) or bare host:port together with proxyUsername/proxyPassword. A DataImpulse username without a country suffix automatically gains \_\_cr.es (Spanish exits). Empty = fall back to the Apify RESIDENTIAL proxy pinned to ES.

## `proxyUsername` (type: `string`):

Username for proxyUrl when the URL does not embed credentials.

## `proxyPassword` (type: `string`):

Password for proxyUrl when the URL does not embed credentials.

## Actor input object example

```json
{
  "location": "madrid-provincia",
  "zone": "todas-las-zonas",
  "operation": "sale",
  "sortType": "scoring",
  "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 = {
    "proxyPassword": ""
};

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

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,barefoot_grade/fotocasa-es-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/VySfDM4IMa0NOQkbO/builds/9S05Bj0RG2e8dHphJ/openapi.json
