# SeLoger Scraper 💰 $0.9/1K France Real Estate + Agencies (`memo23/seloger-scraper`) Actor

À partir de $0.9/1K 💰 Scraper SeLoger : extraction par URL de recherche, d'annonce ou d'agence. Prix, m², pièces, chambres, DPE, charges, honoraires, meublé, balcon, garage, photos, GPS, téléphone et e-mail de l'agence. 99 champs. JSON, CSV ou Excel. Anti-bot et proxy intégrés.

- **URL**: https://apify.com/memo23/seloger-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** Real estate, Automation, Agents
- **Features:** Limited permissions
- **Stats:** 120 total users, 25 monthly users, 99.5% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $0.90 / 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?

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

## SeLoger.com All-in-One Scraper — Search, Properties & Agencies

<details>
<summary><strong>Version française</strong></summary>

Extrait des annonces immobilières de SeLoger.com à partir d'une URL de recherche, d'annonce ou d'agence. Sans navigateur et sans connexion : vous collez l'URL et vous recevez des lignes structurées.

**Comment l'utiliser**

Ouvrez SeLoger, appliquez vos filtres (ville, prix, pièces, surface), copiez l'URL depuis la barre d'adresse et collez-la dans `startUrls`. L'actor reconnaît seul les trois types d'URL.

**Ce que vous obtenez**

Jusqu'à 99 champs par annonce : prix et prix au m², surface, pièces, chambres, DPE et émissions CO2, charges, honoraires, meublé, balcon, garage, photos, coordonnées GPS, plus le téléphone et l'e-mail de l'agence lorsqu'ils sont publiés.

**Offre gratuite**

Avec un compte Apify gratuit, vous obtenez jusqu'à 100 résultats par exécution, sans rien payer. Les offres payantes lèvent la limite : [tarifs Apify](https://apify.com/pricing).

Une question ou un champ manquant : ouvrez une issue sur la page de l'actor.

</details>

#### How it works

## ![How SeLoger Scraper works](https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/how-it-works-seloger.png)

All-in-one SeLoger.com scraper — **75+ data points per property row**, pulled directly from SeLoger's React server-state blob (no DOM scraping). Paste any mix of the **4 URL kinds** below and the actor auto-classifies each one:

| Input | Row(s) emitted |
|---|---|
| **Search page URL** as SeLoger's address bar shows it — `/recherche/{transaction}/{type}/…/{place-id}` (e.g. `/recherche/achat/appartement/ile-de-france/paris-75000/ad08fr31096`) — or the older `/immobilier/{transaction}/immo-{city-dept}/[bien-{type}/]` that redirects there (e.g. `/immobilier/achat/immo-paris-75/`; rentals are `/immobilier/locations/immo-paris-75/`, plural — the singular `/immobilier/location/` is a 404 on SeLoger and the actor rewrites it) | **N property rows** — one per property card. SeLoger shows only page 1 of these (12-14 cards; later page numbers repeat it), so the actor reads the search the page embeds and continues on the equivalent `/classified-search`, newest first. |
| **Filter search URL** — `/classified-search?classifiedBusiness=…&distributionTypes=…&locations=…&priceMin=…&priceMax=…` (the new SeLoger search shape with full filter support) | **N property rows**, same fields as the SEO shape. Paginates via `?page=N`. The DataDome wall is handled by the built-in routing. |
| **Property detail URL** — `/annonce/{transaction}/{region}/{dept}/{city}/{id}` or `/annonces/{type}/{slug}/{city-dept}[/{neighborhood}]/{id}.htm` | 1 property row: price, fees, mortgage, m², rooms, bedrooms, floor, address, neighborhood, postal code, DPE/GES energy ratings, construction year, lat/lng, agency name + profile URL, features, image URLs. |
| **Agency profile URL** — `/professionnels-immobilier/{opaque-id}` | 1 agency row + N property rows for every property the agency has listed. |

Pagination is handled automatically — `/classified-search` URLs page with `page`, and SEO listings continue on their `/classified-search` equivalent after page 1. The older `/list.htm?projects=…` query-style URL is **WAF-blocked at the edge** and rejected up-front by the URL classifier — use `/classified-search?...` instead (it's the modern replacement and works). JSON + CSV out.

> Pure HTTP. No Puppeteer, no Playwright, no headless Chromium, no third-party Cloudflare-bypass service.

#### Why 75+ fields per row?

Every SeLoger detail page server-renders a `window["__UFRN_LIFECYCLE_SERVERREQUEST__"]` JSON blob — 66 KB of structured property + agency data that the React frontend hydrates from. The actor parses **that JSON directly**, not the rendered DOM, which is why each row carries far more than HTML scrapers can pull:

- **Property core** — propertyId, externalRef, title, propertyType, transactionType, rooms, bedrooms, livingArea (m²), plotSurface (m²), floor, neighborhood/district, city, postalCode, country, region
- **Pricing** — price (€), priceFormatted, pricePerM2, notaryFees, totalProjectCost, monthlyMortgage
- **Energy** — dpeRating (A–G), gesRating, dpeIndex (0–6), primaryEnergyKwh, finalEnergyKwh, co2EmissionsKgPerM2, annualEnergyCost (€ band), energyDiagnosticDate, energySources, constructionYear, buildingState
- **Geo** — latitude, longitude, isAddressPublished, geoIdHierarchy (7-level SeLoger location IDs)
- **Transport** — metro, bus, tramway, RER lines (with line numbers and brand colors) within walking distance
- **Co-ownership** — coOwnershipLots, coOwnershipChargesPerYear, coOwnershipProceedings
- **Media** — `imageUrls` (full carousel, up to 30 images) + `images[].classification` (AI-tagged: `BEDROOM` / `KITCHEN` / `LIVING_ROOM` / `BATHROOM` / `BALCONY` / …), floorplanImageUrls, photosCount
- **Tags** — isExclusive, has3DVisit, hasBrokerageFee, isNew
- **Agency** — agencyName, agencyType (Agence / Mandataire / Network), agencyProfileUrl, agencyWebsiteUrl, agencyFeeUrl, agencyImprintUrl, agencyLogoUrl, agencyAddress (street), agencyRcs, agencySiret, clientType (pro/particulier), clientId, publisherType, isPrivateOwner, phoneNumbers
- **Agency enrichment (when `enrichAgency: true`)** — agencyDescription (full bio), agencyServices, agencyOpeningHours (structured per day), agencyTeamMembers (with names + roles), agencyEmailAddresses, intermediaryType, network, spokenLanguages, memberships, social links
- **Timestamps** — createdAt, updatedAt (ISO)

***

### Pipeline

SeLoger is **geo-fenced to France** and sits behind an anti-bot wall (DataDome on CloudFront) that answers plain residential traffic with a 403 challenge stub about half the time. The actor handles this with a hardened 3-stack approach:

1. **Built-in routing, Apify Unblocker only as the last resort.** With `proxy` left empty each page goes through the actor's own page-fetch service first, then its own French residential proxy, and only when both miss through Apify's `UNBLOCKER` proxy group (skipped when your account does not have it). Apify Proxy groups set in `proxy` (`RESIDENTIAL`, datacenter, or no proxy) are ignored, because the wall rejects them: runs on `RESIDENTIAL` + `apifyProxyCountry: "FR"` came back with zero rows. Only your own proxy URLs replace the built-in routing.
2. **impit Chrome fingerprint.** All requests use the impit Chrome TLS profile (the firefox profile is blocked on this site). Real Chrome 144 ClientHello — no header spoofing.
3. **Sliding-window parallelism.** Up to 10 property pages crawl in parallel via a global concurrency window. Listing pages are streamed: as soon as a listing page returns, its 25 detail URLs fan out into the window while page N+1 fetches in parallel.
4. **Per-agency cache.** Each unique agency profile (`/professionnels-immobilier/{id}`) is fetched **once**, with all properties from that agency awaiting the same promise — a 50-property batch from one agency costs one extra fetch, not 50.
5. **Retries that fit the route.** On the residential proxy (and with your own proxies), if the first 3 impit attempts fail (CF challenge / 5xx / timeout), the request escalates to a parallel race across three independent HTTP stacks: **impit** (Rust + rustls) + **gotScraping** (Node http/2 + header generator) + **impers** (libcurl FFI with `curl-impersonate` Chrome 131 TLS). First success wins. Through Apify Unblocker each attempt stays one request, since the Unblocker fetches and retries the page itself.
6. **Search-page rows (optional).** With `scrapeDetails: false` the actor builds property rows from the search result pages alone, 30 properties per request, instead of opening each listing. Several times faster on big searches; the detail-only fields (listed under Input) stay empty.

Every property page is parsed via a mix of:

- **JSON-LD** for the minimal `RealEstateListing` block (title + description) on detail pages, and the page-level `AggregateOffer` on listing pages (total count, price band, currency).
- **`data-testid` anchors** for the rich React-rendered data: `cdp-hardfacts-keyfacts`, `cdp-location-address`, `cdp-energy`, `cdp-price`, `cdp-co-ownership`, `cdp-classified-keys`, plus the `aviv.CDP.Contacting.ProviderSection.*` family for agency contact.
- **Inline Mapbox static-map URL** for lat/lng (handles both Point and MultiPolygon GeoJSON shapes).

No DOM execution, no DOMContentLoaded wait, no headless browser.

***

### Input

| Field | Type | Required | Notes |
|---|---|---|---|
| `startUrls` | `string[]` | **yes** | Any mix of search / detail / agency URLs. Auto-classified. Every filter in a pasted `/classified-search?…` URL is kept. A URL the actor cannot read (e.g. the WAF-blocked `/list.htm?…`) is skipped and named in the run's status message. |
| `scrapeDetails` | `boolean` | no | `true` (default): every row comes from the listing's own page. `false`: rows come from the search result pages alone, 30 per request. Empty in that mode: full description (cut at about 500 characters), `features`, `energySources`, `gesRating`, DPE kWh / CO₂ / annual energy cost, `notaryFees`, `totalProjectCost`, `monthlyMortgage`, co-ownership fields, `latitude` / `longitude`. Each row says which it got in `detailsScraped`. Detail and agency URLs always open each listing. |
| `enrichAgency` | `boolean` | no | When `true` (default), each unique agency profile is fetched once and merged into every property row from that agency. Adds `agencyAddress`, `agencyDescription`, `agencyServices`, `agencyOpeningHours`, `agencyTeamMembers`, `agencyLogoUrl`. |
| `maxItems` | `integer` | no | Hard cap on property rows. Agency-only rows do not count. Default `1000`. Free-tier users are capped at `100`. |
| `maxConcurrency` | `integer` | no | Max parallel property fetches. Default `10`. |
| `minConcurrency` | `integer` | no | Default `1`. |
| `maxRequestRetries` | `integer` | no | Retry budget for the CheerioCrawler pipeline (`useInternalHandler: false`). The default pipeline asks its page-fetch service first, then retries each URL on its residential proxy (3-stack race from the 4th attempt), then through Apify Unblocker. Default `5`. |
| `useInternalHandler` | `boolean` | no | `true` (default) → impit-only internal pipeline. `false` → CheerioCrawler with ImpitHttpClient. |
| `proxy` | `object` | no | **Leave empty.** The actor routes through its own page-fetch service and French residential proxy, with Apify Unblocker as the last resort. Apify Proxy groups set here (`RESIDENTIAL`, datacenter, or no proxy) are ignored because SeLoger blocks them; only your own proxy URLs replace the built-in routing. |

#### Example input

```json
{
    "startUrls": [
        "https://www.seloger.com/immobilier/achat/immo-paris-75/",
        "https://www.seloger.com/immobilier/locations/immo-paris-75/",
        "https://www.seloger.com/professionnels-immobilier/3XZa2Es7QReNKaxmQ8dWcC"
    ],
    "enrichAgency": true,
    "maxItems": 50
}
```

That yields up to **50 property rows** from the Paris listing (with the direct detail and agency URLs interleaved). One row per property, each enriched with the agency profile when `enrichAgency` is on.

***

### Output schema

Two row shapes, distinguishable by the presence of `kind: "agency"`:

#### Property row (one per property URL)

```json
{
  "propertyId": "274896393",
  "externalRef": "264MU5Q7CYFN",
  "title": "Appartement à vendre",
  "propertyType": "Appartement",
  "transactionType": "achat",
  "rooms": 2,
  "bedrooms": 1,
  "livingArea": 66,
  "floor": "4/4",
  "address": "Saint Thomas d'Aquin, Paris 7ème arrondissement (75007)",
  "neighborhood": "Saint Thomas d'Aquin",
  "city": "Paris 7ème arrondissement",
  "postalCode": "75007",
  "description": "Rue de Varenne, mitoyen de l'hôtel de Matignon, un atelier d'artiste au dernier étage d'un immeuble de style Directoi...",
  "features": [
    "Dernier étage, 4ème étage/4 étages",
    "Cuisine équipée, Cuisine ouverte"
  ],
  "constructionYear": null,
  "energySources": "Gaz",
  "dpeRating": "D",
  "gesRating": "D",
  "price": 1695000,
  "notaryFees": 135600,
  "totalProjectCost": 1830600,
  "monthlyMortgage": 8612,
  "coOwnershipLots": null,
  "coOwnershipChargesPerYear": 2932,
  "coOwnershipProceedings": "Pas de procédure en cours",
  "latitude": null,
  "longitude": [
    [
      [
        2.32416,
        48.85024
      ],
      [
        2.32401,
        48.85017
      ]
    ]
  ],
  "agencyName": "P B",
  "agencyType": "Proposé par une agence immobilière",
  "agencyProfileUrl": "https://www.seloger.com/professionnels-immobilier/LuiAdTBkt3sQQYyUoxjzaK",
  "agencyWebsiteUrl": "http://www.patrice-besse.com/",
  "agencyFeeUrl": "https://www.patrice-besse.com/mentions-legales/",
  "imageUrls": [
    "https://mms.seloger.com/e/d/1/1/ed11e149-7590-4004-9f8f-328a3a0142a5.jpg?ci_seal=e09dc47557dd82df4f9dfe9e82a989fab765...",
    "https://mms.seloger.com/7/7/7/8/77784c42-ac4b-4235-b87c-abbe08e0e661.jpg?ci_seal=cd9a76d1c79252e8f8725ba501b0e340cb5b..."
  ],
  "url": "https://www.seloger.com/annonces/achat/appartement/paris-7eme-75/saint-thomas-d-aquin/274896393.htm",
  "jsonLd": {
    "@context": "https://schema.org",
    "@type": "RealEstateListing",
    "datePosted": "2026-07-13T14:22:00.000Z",
    "description": "Rue de Varenne, mitoyen de l'hôtel de Matignon, un atelier d'artiste au dernier étage d'un immeuble de style Directoi...",
    "name": "Appartement à vendre T2/F2 66 m² 1695000 € Saint Thomas d'Aquin Paris (75007)",
    "itemOffered": {
      "@type": "APARTMENT",
      "name": "Appartement à vendre T2/F2 66 m² 1695000 € Saint Thomas d'Aquin Paris (75007)",
      "numberOfRooms": "2",
      "floorSize": {
        "@type": "QuantitativeValue",
        "value": "66",
        "unitCode": "MTK"
      }
    },
    "offers": {
      "@type": "Offer",
      "price": "1695000",
      "priceCurrency": "EUR",
      "availability": "https://schema.org/InStock"
    }
  },
  "headline": "Rue de Varenne, mitoyen de l'hôtel de Matignon, un atelier d'ar",
  "pricePerM2": "25 682 €/m²",
  "annualEnergyCost": null,
  "energyDiagnosticDate": "Après le 1er juillet 2021",
  "primaryEnergyKwh": "149 kWh/m².an",
  "finalEnergyKwh": null,
  "co2EmissionsKgPerM2": "31 kg CO₂/m².an",
  "dpeIndex": 3,
  "publisherType": "AGENCY",
  "isPrivateOwner": false,
  "phoneNumbers": [
    "+33 1 42 84 80 85"
  ],
  "agencyAddress": "7 rue Chomel, Paris 75007",
  "agencyImprintUrl": null,
  "agencyLegalInformations": [
    "Sarl P.B. SAS au capital de 9 147 €",
    "Carte professionnelle 7501 2017 000 016 72 délivrée par la Préfecture de Paris"
  ],
  "agencyRcs": null,
  "agencySiret": "34350396700048",
  "createdAt": "2026-07-13T14:22:00Z",
  "updatedAt": "2026-07-13T14:26:05.284Z",
  "tags": {
    "isExclusive": false,
    "has3DVisit": false,
    "hasBrokerageFee": false,
    "isNew": false
  },
  "rawPropertyType": "APARTMENT",
  "rawDistributionType": "BUY",
  "rawDistributionSubType": "RESALE",
  "geoIdHierarchy": [
    {
      "id": "AD09FR32",
      "typeKey": "AD09"
    },
    {
      "id": "AD08FR31096",
      "typeKey": "AD08"
    }
  ],
  "region": "Ile-de-France",
  "buildingState": null,
  "priceFormatted": "1 695 000 €",
  "plotSurface": null,
  "photosCount": 12,
  "clientType": "pro",
  "clientId": "RC-031771",
  "images": [
    {
      "url": "https://mms.seloger.com/e/d/1/1/ed11e149-7590-4004-9f8f-328a3a0142a5.jpg?ci_seal=e09dc47557dd82df4f9dfe9e82a989fab765...",
      "key": "ed11e149-7590-4004-9f8f-328a3a0142a5",
      "alt": "Appartement à vendre 1 695 000 € 2 pièces 1 chambre 66 m² Étage 4/4 Saint Thomas d'Aquin Paris 7ème arrondissement 75007",
      "classification": "EXTERIOR_VIEW"
    },
    {
      "url": "https://mms.seloger.com/7/7/7/8/77784c42-ac4b-4235-b87c-abbe08e0e661.jpg?ci_seal=cd9a76d1c79252e8f8725ba501b0e340cb5b...",
      "key": "77784c42-ac4b-4235-b87c-abbe08e0e661",
      "alt": null,
      "classification": "LIVING_ROOM"
    }
  ],
  "floorplanImageUrls": [],
  "transport": {
    "metro": [
      {
        "name": "12",
        "color": "#007a53"
      },
      {
        "name": "13",
        "color": "#71c5e8"
      }
    ],
    "bus": [
      {
        "name": "63",
        "color": "#cec92a"
      },
      {
        "name": "68",
        "color": "#ffbe02"
      }
    ]
  },
  "isAddressPublished": false,
  "country": "FRA",
  "agencyDescription": "Partout en France\r\nDepuis 1924, trois générations se sont succédé pour développer un réseau de vente national\r\nspécia...",
  "agencyServices": [
    "BUY SELL REAL ESTATE",
    "ESTIMATION"
  ],
  "agencyOpeningHours": [
    "Lundi: 10:00–13:00 / 14:00–19:00",
    "Mardi: 10:00–13:00 / 14:00–19:00"
  ],
  "agencyTeamMembers": [],
  "agencyLogoUrl": "https://mms.seloger.com/5/7/c/4/57c42c3a-c22b-41fe-9deb-bb38d6c075c1.jpg?ci_eqs=&ci_seal=52702b181ab560027a483437ef73...",
  "scrapedAt": "2026-08-17T15:29:00.361Z"
}
```

#### Agency row (one per agency URL, emitted only when an agency URL is in `startUrls`)

```jsonc
{
  "kind":                 "agency",
  "agencyId":             "FEW3VPchH8roDn265N8Hnj",
  "name":                 "IMOSS",
  "address":              "…",
  "description":          "…",
  "services":             ["…"],
  "openingHours":         ["…"],
  "teamMembers":          ["…"],
  "propertyUrls":         ["https://www.seloger.com/annonces/…/269476619.htm", "…"],
  "websiteUrl":           null,
  "profileUrl":           "https://www.seloger.com/professionnels-immobilier/FEW3VPchH8roDn265N8Hnj",
  "imageUrl":             "https://mms.seloger.com/…",
  "jsonLd":               { "@type": "RealEstateAgent", "name": "IMOSS", "address": "…", "image": "…" },
  "scrapedAt":            "2026-05-20T05:38:00.000Z"
}
```

When you paste an agency URL into `startUrls`, the actor emits this row and then enqueues **every property URL visible on the agency profile page** as a detail fetch — so one agency URL becomes 1 agency row + N property rows.

***

### ✨ Why use this scraper?

Trying to track the French property market without paying for a real-estate-data SaaS? Building a comp tool for a Paris arrondissement? Need clean DPE / GES energy-rating data for sustainable-finance work?

- 🇫🇷 **France-native.** Real fr-FR Accept-Language, built-in anti-bot routing over French residential exits, French currency / surface units (€ + m²) preserved verbatim.
- 🎯 **Three URL kinds, one actor.** Search URLs, individual property URLs, agency profile URLs — paste any mix and the actor classifies and routes each.
- 📋 **DPE / GES first-class.** Energy diagnostic letters (A–G) and construction year are parsed from the `cdp-energy` block — most competitors skip this entirely.
- 📍 **Latitude / longitude on every row.** Decoded from the inline Mapbox static-map URL — handles both Point and MultiPolygon GeoJSON shapes.
- 🏢 **Per-agency cache.** Each unique agency profile is fetched once, even if 50 of your properties are from the same agency. Costs scale with unique agencies, not properties.
- 🛡️ **Geo-fence + WAF aware.** Drops the `/list.htm?…` URL shape (WAF-blocked) and forces FR proxy by default. Recon-proven combos baked into the URL classifier.
- 🔁 **3-stack HTTP fallback.** On the residential fallback route, when impit alone hits a wall, the request escalates to a parallel race across impit + gotScraping + impers. No headless browser ever.
- 📤 **Clean exports.** Dataset rows are stable JSON; CSV export is generated automatically.

***

### 🎯 Use cases

| Team | What they build |
|------|-----------------|
| **Real-estate analysts** | Price-per-m² dashboards by arrondissement, neighborhood, DPE rating |
| **Investors / property funds** | Daily new-listing feeds for buy-to-let or value-add acquisitions |
| **PropTech startups** | Comparable-sales engines fed by a clean French listing dataset |
| **Sustainable-finance teams** | DPE / GES distributions across Paris / Lyon / Marseille — input to green-mortgage products |
| **Mortgage brokers** | Lead pipelines from new high-end listings (`monthlyMortgage` + `notaryFees` already computed) |
| **Agencies (competitive intel)** | What's listed where, by which competitor, at what price |
| **Journalists / researchers** | Longitudinal datasets on FR housing-market shifts |

***

### Notes & limitations

- **Geo-fence and anti-bot wall are real.** SeLoger blocks non-FR IPs at the edge and challenges plain residential traffic. Leave `proxy` empty so the actor uses its built-in routing. Datacenter, non-FR residential, and most caller-supplied residential proxies return the 403 challenge stub.
- **`imageUrls` returns the SSR-rendered subset** — typically 1–3 of the full carousel. The remaining images lazy-load via JS after hydration, which a no-browser HTTP scraper can't trigger. The hero image is always present.
- **Phone numbers are not extracted.** SeLoger gates direct-dial phone behind a "Show number" interaction that requires JS. Email and contact-form CTAs are reachable; the agency profile URL is always present and contains opening hours + website.
- **Pagination stops on empty page.** SeLoger doesn't server-render a total-page count — the actor follows `?page=N` on `/classified-search` (SEO listings continue there after page 1) until a page returns zero property cards.
- **Listing-page JSON-LD is page-level metadata only.** The `RealEstateListing` block on a search results page describes the **search**, not individual properties. Per-property data comes from the cards' `data-testid` anchors. (Detail-page JSON-LD is thin but populated for the row.)
- **The `/list.htm?projects=…` URL shape is WAF-blocked at the edge** and rejected by the URL classifier. Paste the `/recherche/…` or `/classified-search?…` URL your browser shows instead.
- **Agency URL must be the new shape** `/professionnels-immobilier/{opaque-id}`. The old `/professionnels/agences-immobilieres/{city-slug}/` city-directory URL returns 404 today and is rejected.

***

### Free tier

You can run this scraper on a free Apify account before paying anything:

| | Free account |
|---|---|
| Results per run | up to **100** |
| Start URLs per run | up to **100** |
| Cost | nothing — the free plan includes monthly platform credit |

That is enough to check the field list and the data quality against your own URLs. Paid plans lift both caps; see [Apify pricing](https://apify.com/pricing).

### Support

Issues, feature requests, or custom output shapes? Open a ticket on the actor's [Apify Console issues page](https://console.apify.com/actors/) or message the maintainer.

***

### 🤖 For AI Agents & LLM Apps

Compact reference for AI agents calling this actor via the [Apify MCP server](https://mcp.apify.com) or the Apify API (actor: `memo23/seloger-scraper`).

**Purpose:** Extract SeLoger.com property listings, individual property pages, and agency profiles across France, parsing 75+ fields per row from the page's React server-state JSON rather than the rendered DOM.

**Minimal input:**

```json
{
  "startUrls": ["https://www.seloger.com/immobilier/achat/immo-paris-75/"],
  "maxItems": 50
}
```

Alternatively, leave `startUrls` empty and build a search from filters: set `locations` (place names as you would type them in SeLoger's search box, such as `Cergy`, `Marseille 7ème` or `13007`, or place IDs such as `AD08FR31096`) plus `distributionType` (`Buy`/`Rent`/`Buy_Auction`), and optionally `estateTypes`, `priceMin`, `priceMax`, `roomsMin`. Names are looked up with SeLoger's own place search; the run log shows the place each one matched, and a name SeLoger does not know is named in the run's status message. Or set `instantDatabase: true` with the `db*` filters to return pre-collected agencies with no scraping.

**Output:** one dataset row per property — propertyId, externalRef, title, propertyType, transactionType, rooms, bedrooms, livingArea, floor, address, neighborhood, city, postalCode, region, country, price, priceFormatted, pricePerM2, notaryFees, totalProjectCost, monthlyMortgage, dpeRating, gesRating, primaryEnergyKwh, co2EmissionsKgPerM2, latitude, longitude, agencyName, agencyType, agencyProfileUrl, agencyAddress, phoneNumbers, features, imageUrls, images (each with `classification`), transport (metro/bus/tramway/RER), tags, url, scrapedAt. Agency URLs additionally emit an agency row carrying `kind: "agency"`.

**Behaviors an agent should know:**

- Always set `maxItems` (default 1000; agency-only rows do not count). Each `/classified-search?` URL caps at ~370 properties; enable `splitByPrice` to run 5 price bands for ~1,850 (cost scales ~5×).
- `startUrls` takes precedence — when any URL is supplied, all filter fields (`locations`, `distributionType`, etc.) are ignored.
- Four URL kinds auto-classify: search page (`/recherche/…` or `/immobilier/…`), `/classified-search?` filter search, property detail (`/annonce/…` or `….htm`), agency profile (`/professionnels-immobilier/{id}`). The old `/list.htm?projects=…` shape is WAF-blocked; it and any other unreadable input are skipped and named in the run's status message.
- Proxy routing is built in (own page-fetch service, then French residential, Apify Unblocker last); leave `proxy` empty. Apify Proxy groups passed in `proxy` are ignored; only your own proxy URLs replace the routing.
- `enrichAgency` (default true) fetches each agency profile once, cached, and merges it into every property row from that agency. `enrichEmails` (default false) is billed per contact email found, only on a hit, never for misses.
- Phone direct-dial and the full image carousel are JS-gated; `imageUrls` returns the SSR subset (hero always present).

***

### ⚠️ Disclaimer

This Actor is an independent tool and is **not affiliated with, endorsed by, or sponsored by** SeLoger.com, AVIV Group, Axel Springer SE, or any of their subsidiaries. All trademarks mentioned are the property of their respective owners.

The scraper extracts only **publicly visible** data from SeLoger.com — property listings, individual property pages, and agency profiles as they appear to any logged-out visitor. No login, no CAPTCHA solving, no API-key forgery, no private-endpoint probing. The actor uses an Apify Residential FR proxy with TLS-fingerprinted HTTP requests, honouring `robots.txt` and rate-limits via concurrency cap to avoid burdening SeLoger's infrastructure.

Users are responsible for:

- Complying with SeLoger.com's Terms of Service and French law (loi Hoguet, Code de la consommation, RGPD)
- Following GDPR / French CNIL guidance when storing or processing data about private individuals (vendors, agents)
- Not contacting agents, vendors, or team members listed in scraped data without their consent
- Not republishing scraped data in a way that competes commercially with SeLoger or misrepresents listing authorship
- Honouring any opt-out signals or `noindex` directives on the source pages
- Refreshing scraped data regularly — property availability, prices, and energy ratings change frequently

***

### SEO Keywords

seloger scraper, scrape seloger, seloger.com scraper, seloger api, seloger immobilier scraper, scraping seloger, seloger.fr scraper, french real estate scraper, paris property scraper, immobilier scraper, scrape paris real estate, paris listings scraper, france real estate api, real estate france scraper, seloger achat scraper, seloger location scraper, seloger annonces scraper, seloger agences scraper, french housing data scraper, DPE scraper, GES scraper, energy rating scraper, real estate listings france, french property data, real estate market data france, paris housing market scraper, real estate analytics france, comparable sales scraper france, seloger.com data extraction, french housing market api, real estate intelligence france, JSON-LD real estate scraper, RealEstateListing scraper, RealEstateAgent scraper, Apify seloger, seloger cheerio scraper, seloger HTTP scraper, no-browser seloger scraper, Apify Residential FR, french rental market, paris rental scraper, neuf scraper, viager scraper, colocation scraper, prestige scraper, french property investment, parisian real estate, real estate lead generation france, agent immobilier scraper, agence immobilière scraper, ile-de-france property scraper, lyon property scraper, marseille property scraper, bordeaux property scraper

***

#### 💬 Support

- For issues or feature requests, please use the **Issues** tab on the actor's Apify Console page.
- Author's website: <https://muhamed-didovic.github.io/>
- Email: <muhamed.didovic@gmail.com>

#### 🛠 Additional services

- Custom output shape, additional fields, or a private build: <muhamed.didovic@gmail.com>
- Need other DACH property platforms (Immowelt, Immonet, Immobilien.de, Wohnungsbörse, Comparis, Homegate, willhaben.at)? Drop an email — most are already in the [portfolio](https://apify.com/memo23).
- For API access (no Apify fee, just a usage fee for the API): <muhamed.didovic@gmail.com>

#### 🔎 Explore more scrapers

If this ImmoScout24 scraper was useful, see other real-estate scrapers at [memo23's Apify profile](https://apify.com/memo23) — Immowelt (DE), Immonet (DE), Immobiliare (IT), Immoweb (BE), LoopNet US + UK, Rightmove UK, and more.

# Actor input Schema

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

Accepted URL shapes:
• Search page as SeLoger's address bar shows it — `/recherche/{transaction}/{type}/…/{place-id}` (e.g. `/recherche/achat/appartement/ile-de-france/paris-75000/ad08fr31096`), or the older `/immobilier/{transaction}/immo-{city-dept}/[bien-{type}/]` that redirects there (e.g. `/immobilier/achat/immo-paris-75/`; rentals are `/immobilier/locations/immo-paris-75/`, plural). SeLoger shows only page 1 of these, so the actor continues on the equivalent `/classified-search` search, newest first.
• Filter search — `/classified-search?distributionTypes=…&locations=…&priceMax=…&numberOfRoomsMin=…`. Every filter in a pasted URL is kept (rooms, bedrooms, surface, energy class, furnished, …). Paginates via `?page=N`.
• Property detail — `/annonce/{transaction}/{region}/{dept}/{city}/{id}` or `/annonces/{type}/{slug}/{city-dept}[/{neighborhood}]/{id}.htm`.
• Agency profile — `/professionnels-immobilier/{opaque-id}`. Emits one agency row + every property the agency has listed.

The older `/list.htm?projects=…` query-style URL is WAF-blocked and rejected.

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

Place names as you would type them in SeLoger's search box: a town (`Cergy`), an arrondissement (`Marseille 7ème`), a postcode (`13007`), a département (`Rhône`) or a region, **or** a raw SeLoger place ID (e.g. `AD08FR31096`). Names are looked up with SeLoger's own place search, and the run log shows the place each one matched. A name SeLoger does not know is skipped and named in the run's status message. Multiple locations search the union.

## `distributionType` (type: `string`):

Buy = achat, Rent = location, Buy_Auction = vente aux enchères.

## `estateTypes` (type: `array`):

Filter by property type. Leave empty for both Apartment + House.

## `priceMin` (type: `integer`):

Lower price bound in euros. Leave empty for no lower bound.

## `priceMax` (type: `integer`):

Upper price bound in euros. Leave empty for no upper bound.

## `roomsMin` (type: `integer`):

Minimum number of rooms (pièces). 1 = studio, 2 = 1-bed, 3 = 2-bed, etc.

## `splitByPrice` (type: `boolean`):

SeLoger caps every `/classified-search?` URL at ~20 pages × ~20 = **~370 properties**. Enable splitting to run 5 narrower searches in one go — each gets its own page-20 budget, multiplying total yield to **~1,850 properties**. Cost scales ~5× because each band is a separate set of fetches.

Default bands (log-distributed for the FR market): \[0–200K], \[200K–400K], \[400K–700K], \[700K–1.2M], \[1.2M+]. If you supplied `priceMin`/`priceMax` above, the user range is split into 5 equal bands instead.

## `scrapeDetails` (type: `boolean`):

On (default): every property row comes from the listing's own page, with all fields. Off: rows are built from the search result pages alone, 30 properties per request, so large searches finish several times faster. Off leaves these fields empty: full description (cut at about 500 characters), features list, energy source, GES rating, DPE kWh / CO₂ / annual energy cost, notary fees, total project cost, monthly mortgage, co-ownership lots / charges / proceedings, and GPS coordinates. Price, rooms, surface, floor, DPE letter, photos, address, transport, and the agency name, phone, SIRET and website are still there. Applies to search URLs and filters; listing and agency URLs always open each listing.

## `enrichAgency` (type: `boolean`):

When enabled, each unique agency profile (`/professionnels-immobilier/{id}`) is fetched once and merged into every property row from that agency — adds `agencyAddress`, `agencyDescription`, `agencyServices`, `agencyOpeningHours`, `agencyTeamMembers`, `agencyLogoUrl`. Cached per agency, so a 50-property batch from one agency costs one extra fetch.

## `enrichEmails` (type: `boolean`):

If enabled, finds a contact email for each result from its own website (or by discovering it from the name). Adds contactEmail + contactWebsite columns plus a detailed emailEnrichment object. Billed per contact email found; only charged when an email is returned, never for misses.

## `instantDatabase` (type: `boolean`):

Return pre-collected French real-estate agencies (name, address, phone, email, website, SIRET/RCS, socials) straight from our accumulated database instead of scraping live. Instant, no proxy. Combine with the db\* filters below.

## `dbName` (type: `string`):

Instant-database mode only: return only agencies whose name contains this text (e.g. 'Century 21').

## `dbNetwork` (type: `string`):

Instant-database mode only: return only agencies in a network matching this text (e.g. 'Orpi', 'Laforêt').

## `dbHasPhone` (type: `boolean`):

Instant-database mode only: return only agencies that have a phone number.

## `dbHasEmail` (type: `boolean`):

Instant-database mode only: return only agencies that have an email.

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

Hard cap on the number of rows saved to the dataset. Every saved row (property, agency, or instant-database) counts toward this cap and is billed as a result.

## `maxConcurrency` (type: `integer`):

Maximum number of property pages processed in parallel.

## `minConcurrency` (type: `integer`):

Minimum number of pages processed in parallel.

## `maxRequestRetries` (type: `integer`):

Retry budget for the legacy CheerioCrawler pipeline. The default pipeline asks its page-fetch service first, then retries each URL up to 4 times on its French residential proxy (3-stack race on the 4th), then up to 4 times through Apify Unblocker.

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

Leave empty: the actor gets past SeLoger's anti-bot wall by itself, through its own page-fetch service and French residential proxy, with Apify Unblocker only as a last resort. Apify Proxy groups chosen here (RESIDENTIAL, datacenter, or no proxy) are ignored, because SeLoger blocks them. Only your own proxy URLs replace the built-in routing.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.seloger.com/immobilier/achat/immo-paris-75/",
    "https://www.seloger.com/classified-search?classifiedBusiness=Private&distributionTypes=Buy&estateTypes=House,Apartment&locations=AD08FR1977&priceMax=300000&priceMin=100000&order=DateDesc",
    "https://www.seloger.com/immobilier/locations/immo-paris-75/",
    "https://www.seloger.com/professionnels-immobilier/3XZa2Es7QReNKaxmQ8dWcC"
  ],
  "locations": [
    "Paris"
  ],
  "distributionType": "Buy",
  "splitByPrice": false,
  "scrapeDetails": true,
  "enrichAgency": true,
  "enrichEmails": false,
  "instantDatabase": false,
  "dbHasPhone": false,
  "dbHasEmail": false,
  "maxItems": 1000,
  "maxConcurrency": 10,
  "minConcurrency": 1,
  "maxRequestRetries": 5
}
```

# 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.seloger.com/immobilier/achat/immo-paris-75/",
        "https://www.seloger.com/classified-search?classifiedBusiness=Private&distributionTypes=Buy&estateTypes=House,Apartment&locations=AD08FR1977&priceMax=300000&priceMin=100000&order=DateDesc",
        "https://www.seloger.com/immobilier/locations/immo-paris-75/",
        "https://www.seloger.com/professionnels-immobilier/3XZa2Es7QReNKaxmQ8dWcC"
    ],
    "locations": [
        "Paris"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/seloger-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.seloger.com/immobilier/achat/immo-paris-75/",
        "https://www.seloger.com/classified-search?classifiedBusiness=Private&distributionTypes=Buy&estateTypes=House,Apartment&locations=AD08FR1977&priceMax=300000&priceMin=100000&order=DateDesc",
        "https://www.seloger.com/immobilier/locations/immo-paris-75/",
        "https://www.seloger.com/professionnels-immobilier/3XZa2Es7QReNKaxmQ8dWcC",
    ],
    "locations": ["Paris"],
}

# Run the Actor and wait for it to finish
run = client.actor("memo23/seloger-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.seloger.com/immobilier/achat/immo-paris-75/",
    "https://www.seloger.com/classified-search?classifiedBusiness=Private&distributionTypes=Buy&estateTypes=House,Apartment&locations=AD08FR1977&priceMax=300000&priceMin=100000&order=DateDesc",
    "https://www.seloger.com/immobilier/locations/immo-paris-75/",
    "https://www.seloger.com/professionnels-immobilier/3XZa2Es7QReNKaxmQ8dWcC"
  ],
  "locations": [
    "Paris"
  ]
}' |
apify call memo23/seloger-scraper --silent --output-dataset

```

## MCP server setup

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