Realtor.com est l'un des plus grands portails immobiliers des États-Unis, et ses pages d'annonces portent exactement les données structurées qui alimentent le suivi des prix, la recherche de marché et l'analyse d'investissement : le prix affiché, les chambres, les salles de bains, la superficie, l'adresse de rue et un lien vers chaque annonce. Ces données publiques sont la matière première de toute lecture sérieuse d'un marché immobilier local, mais les pages s'affichent côté client et le site se défend durement contre le trafic automatisé, donc une simple requête HTTP vous remet une coquille légère au lieu des annonces que vous cherchiez.
Ce guide vous montre comment scraper Realtor.com avec Python de la façon fiable. Vous construisez un petit scraper exécutable qui récupère une page de recherche rendue via la Crawling API, lit les données d'annonce que Realtor.com intègre dans un script caché __NEXT_DATA__, extrait les champs que vous voulez, gère la pagination et exporte du JSON et du CSV propres. Nous gardons toute la présentation cantonnée aux données d'annonce publiques, et la section sur la légalité, vers la fin, n'est pas du remplissage, alors lisez-la avant de pointer ceci vers tout volume réel.
Ce que vous allez construire
Un script Python qui prend une URL de recherche Realtor.com publique pour une ville et un État, récupère le HTML rendu via la Crawling API, analyse le jeu de données d'annonces intégré et écrit un enregistrement structuré par bien. Nous utiliserons une seule ville comme exemple courant et extrairons ces champs :
- Prix le prix affiché sur l'annonce.
- Chambres le nombre de chambres.
- Salles de bains le nombre de salles de bains consolidé.
- Sqft la superficie intérieure du logement en pieds carrés.
- Adresse la rue, la ville, l'État et le code postal.
- Lien l'URL canonique de l'annonce, reconstruite à partir de son permalien.
Pourquoi une simple requête échoue sur Realtor.com
Si vous demandez une URL de recherche Realtor.com avec un client HTTP nu, vous obtenez une réponse avec le statut 200 et presque aucune des données d'annonce dans le balisage visible. Deux choses jouent contre vous. Premièrement, Realtor.com est une application Next.js qui hydrate ses annonces dans le navigateur, donc les données résident dans un bloc JSON à l'intérieur d'une balise cachée <script id="__NEXT_DATA__"> plutôt que dans des éléments HTML rendus que vous pouvez lire directement. Deuxièmement, le site signale vite le trafic automatisé : les IP de centre de données et les schémas de requêtes qui ne ressemblent pas à un vrai navigateur se font défier ou servir un captcha avant même d'atteindre une page complète.
Un scraper Realtor.com fonctionnel a donc besoin de deux choses en une seule requête : un navigateur qui rend réellement la page, et une IP que la plateforme lit comme un vrai visiteur. Vous pouvez assembler cela vous-même avec un navigateur sans interface plus un pool de proxys résidentiels tournants, mais coudre tout cela ensemble et le maintenir en bonne santé est l'essentiel du travail. La Crawling API réunit les deux en un seul appel : vous lui envoyez l'URL avec un token JavaScript, elle rend la page derrière une IP de confiance, et elle renvoie du HTML fini avec la charge utile __NEXT_DATA__ intacte pour que vous l'analysiez.
Faites un clic droit sur une page Realtor.com et choisissez Inspecter, puis cherchez __NEXT_DATA__ dans le HTML. Ce seul script caché contient le jeu de données complet d'annonces à partir duquel la page se rend, y compris des champs que la mise en page visible ne montre jamais. Le lire est bien plus stable que de scraper des éléments DOM individuels, car les clés JSON changent moins souvent que les noms de classes CSS autour d'eux.
Prérequis
Vous avez besoin de quelques éléments en place avant d'écrire le moindre code. Aucun ne prend longtemps.
Python de base. Vous devriez être à l'aise pour écrire et exécuter un script Python et installer des paquets avec pip. Si vous débutez dans le langage, le guide du web scraping en Python couvre les bases que ce tutoriel suppose.
Python 3.8 ou ultérieur. Confirmez votre version avec python --version. Si vous ne l'avez pas, installez-le depuis python.org ou via une distribution comme Anaconda.
Un compte Crawlbase et un token JS. Inscrivez-vous, ouvrez votre tableau de bord et copiez votre token JavaScript (JS). Vous obtenez jusqu'à 20 000 requêtes gratuites et aucune carte n'est requise. Traitez le token comme un mot de passe : il authentifie vos requêtes, alors gardez-le hors du contrôle de version.
Configurer le projet
Créez un environnement virtuel pour que les dépendances du projet restent isolées, puis installez les deux bibliothèques dont le scraper a besoin.
python --version python -m venv realtor_env source realtor_env/bin/activate pip install crawlbase
Sous Windows, activez l'environnement avec realtor_env\Scripts\activate au lieu de la ligne source. Nous n'avons besoin que d'une seule dépendance tierce ici : crawlbase est le client officiel de la Crawling API. Comme les données d'annonce arrivent en JSON à l'intérieur du script __NEXT_DATA__, les modules intégrés json et re de Python gèrent l'analyse sans bibliothèque HTML séparée.
Étape 1 : récupérer la page de recherche rendue
Commencez par obtenir la page finie. Importez la classe CrawlingAPI, initialisez-la avec votre token JS et demandez une URL de recherche Realtor.com. Vérifier le code de statut avant d'analyser rend les échecs bruyants plutôt que silencieux.
from crawlbase import CrawlingAPI api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) def crawl(page_url): options = {"ajax_wait": "true", "page_wait": 5000} response = api.get(page_url, options) if response["status_code"] == 200: return response["body"].decode("utf-8") print(f"Request failed: {response['status_code']}") return None if __name__ == "__main__": search_url = "https://www.realtor.com/realestateandhomes-search/Los-Angeles_CA/pg-1" html = crawl(search_url) print(html[:500] if html else "No HTML returned")
Les deux options d'attente comptent pour une cible Next.js rendue côté client comme celle-ci. ajax_wait indique à l'API d'attendre que le contenu asynchrone finisse de charger, et page_wait patiente un nombre fixe de millisecondes après le chargement pour que les données hydratées soient en place avant que la page ne soit capturée. Cinq secondes est un début raisonnable ; augmentez-le si le jeu de données revient vide. Exécutez le script avec python realtor_scraper.py et vous devriez voir du vrai balisage de page contenant le script __NEXT_DATA__, pas la coquille vide qu'une simple récupération renvoie. Cela confirme que le rendu fonctionne avant que vous n'écriviez une seule ligne d'analyse.
Realtor.com a besoin d'une page Next.js rendue derrière une IP de confiance, en un seul appel, avant même que cette charge utile __NEXT_DATA__ ne soit présente à lire. La Crawling API prend un token JS, exécute la page dans un vrai navigateur, fait tourner les IP résidentielles côté serveur, et vous remet du HTML fini, vous évitant ainsi de gérer une flotte sans interface et un pool de proxys vous-même. Pointez-la d'abord vers une page de recherche publique sur l'offre gratuite.
Étape 2 : extraire le jeu de données d'annonces intégré
Une fois le HTML rendu en main, sortez le JSON du script __NEXT_DATA__ et descendez jusqu'à la partie qui contient les résultats de recherche. Realtor.com imbrique ses résultats sous props.pageProps, avec un chemin de repli sous searchResults.home_search pour les pages qui utilisent la forme alternative. Enveloppez les recherches pour qu'une clé manquante renvoie None au lieu de planter l'exécution.
import re import json def extract_next_data(html): # The listing dataset lives in a hidden __NEXT_DATA__ script. match = re.search( r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>', html, re.DOTALL, ) if not match: print("No hidden web data found.") return None return json.loads(match.group(1)) def get_results(data): # Prefer the pageProps path, fall back to the home_search shape. page_props = data.get("props", {}).get("pageProps", {}) results = page_props.get("properties") if results: return results search = data.get("searchResults", {}).get("home_search", {}) return search.get("results", [])
La fonction extract_next_data utilise une seule expression régulière pour saisir le contenu du script __NEXT_DATA__ et l'analyser comme du JSON, ce qui évite d'importer un analyseur HTML juste pour lire un bloc JSON. L'aide get_results essaie ensuite le tableau properties sous pageProps d'abord et se rabat sur home_search.results, car Realtor.com sert les deux formes selon la façon dont la page a été atteinte. Chaque recherche utilise dict.get avec une valeur par défaut, donc une page qui décale sa structure renvoie une liste vide plutôt que de lever une KeyError.
Étape 3 : analyser chaque bien en un enregistrement plat
Chaque élément du tableau de résultats porte un bloc description (chambres, salles de bains, sqft), un bloc location.address, le list_price et un permalink que vous pouvez retransformer en une URL d'annonce complète. Mappez-les dans un dictionnaire plat pour que la sortie soit facile à écrire en JSON ou en CSV.
def parse_property(item): description = item.get("description") or {} location = item.get("location") or {} address = location.get("address") or {} parts = [ address.get("line"), address.get("city"), address.get("state_code"), address.get("postal_code"), ] full_address = ", ".join(p for p in parts if p) permalink = item.get("permalink") link = ( f"https://www.realtor.com/realestateandhomes-detail/{permalink}" if permalink else None ) return { "price": item.get("list_price"), "beds": description.get("beds"), "baths": description.get("baths_consolidated"), "sqft": description.get("sqft"), "address": full_address or None, "link": link, }
Les garde-fous or {} comptent car Realtor.com met certains de ces objets imbriqués à null sur les annonces qui n'ont pas la donnée, et appeler .get sur None lèverait une erreur. Les chambres, salles de bains et sqft viennent directement du bloc description, où baths_consolidated est le champ que Realtor.com utilise pour regrouper salles de bains complètes et demi-bains en un seul chiffre. L'adresse est construite en joignant la ligne de rue, la ville, le code d'État et le code postal, en sautant toute partie manquante, et le lien est reconstruit à partir du permalink que Realtor.com attribue à chaque bien. Le résultat est un enregistrement plat par annonce, la forme que vous voulez pour l'export.
La structure __NEXT_DATA__ est plus stable que les sélecteurs CSS, mais elle n'est pas figée. Si price ou sqft commence à revenir à None sur chaque enregistrement, videz le JSON brut d'un bien et revérifiez les noms de clés. Lire le jeu de données de manière défensive avec .get fait qu'un renommage de clé dégénère en champs vides plutôt qu'en plantage, ce qui est exactement ce que vous voulez pour une exécution sans surveillance.
Étape 4 : tout assembler avec la pagination
Une page est une démo ; un vrai travail parcourt l'ensemble des résultats d'une ville. Realtor.com pagine sa recherche avec un suffixe propre /pg-<PAGE>, donc vous construisez l'URL de chaque page à partir d'une ville et d'un État, vous la crawlez, vous extrayez le jeu de données et vous analysez chaque bien. Une courte pause entre les pages cadence l'exécution.
import re import json import time from crawlbase import CrawlingAPI api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) def crawl(page_url): options = {"ajax_wait": "true", "page_wait": 5000} response = api.get(page_url, options) if response["status_code"] == 200: return response["body"].decode("utf-8") print(f"Request failed: {response['status_code']}") return None def find_properties(city, state, max_pages=1): listings = [] for page in range(1, max_pages + 1): url = ( "https://www.realtor.com/realestateandhomes-search/" f"{city}_{state.upper()}/pg-{page}" ) html = crawl(url) if not html: continue data = extract_next_data(html) if not data: continue for item in get_results(data): listings.append(parse_property(item)) print(f"Page {page}: {len(listings)} listings so far") time.sleep(2) return listings def main(): listings = find_properties("Los-Angeles", "CA", max_pages=3) print(json.dumps(listings, indent=2)) if __name__ == "__main__": main()
La fonction find_properties reflète l'approche héritée : une boucle parcourt la plage de pages, construit l'URL {city}_{state}/pg-{page} pour chacune, et ajoute chaque bien analysé à une liste courante. Le time.sleep(2) entre les pages est délibéré, il cadence l'exécution pour que vous ne matraquiez pas le site, ce qui est la seule habitude la plus efficace pour rester non bloqué. Déposez les fonctions extract_next_data, get_results et parse_property des étapes précédentes et c'est un scraper complet et exécutable.
À quoi ressemble la sortie
Exécutez le script complet avec python realtor_scraper.py et vous obtenez une liste propre d'enregistrements structurés, un par annonce.
[ { "price": 139000000, "beds": 12, "baths": "17", "sqft": null, "address": "1200 Bel Air Rd, Los Angeles, CA, 90077", "link": "https://www.realtor.com/realestateandhomes-detail/1200-Bel-Air-Rd_Los-Angeles_CA_90077_M17839-35941" } ]
Exporter en JSON et CSV
Une fois que chaque annonce est un dictionnaire plat, l'export tient en deux courtes fonctions. Le JSON garde la forme complète et propice à l'imbrication ; le CSV l'aplatit en un tableau prêt pour un tableur avec une colonne par champ.
import csv import json def save_json(listings, path="realtor_listings.json"): with open(path, "w", encoding="utf-8") as f: json.dump(listings, f, indent=2) def save_csv(listings, path="realtor_listings.csv"): if not listings: return fields = ["price", "beds", "baths", "sqft", "address", "link"] with open(path, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fields) writer.writeheader() writer.writerows(listings)
Appelez save_json(listings) et save_csv(listings) à la fin de main et vous avez les deux formats sur disque. La liste explicite fields garde l'ordre des colonnes CSV stable d'une exécution à l'autre, ce qui compte si vous ajoutez des résultats au même fichier ou les chargez dans un outil qui attend un en-tête fixe. À partir d'ici, les données sont prêtes pour un notebook, une base de données ou un modèle de tarification.
Rester non bloqué à grande échelle
Même avec le rendu géré, Realtor.com guette le trafic en forme de scraper, et sa mise en page et ses défenses évoluent avec le temps. Quelques habitudes maintiennent une exécution plus longue en bonne santé, et elles s'appliquent à toute cible commerciale difficile.
-
Cadencez vos requêtes. Matraquer les pages dans une boucle serrée est le moyen le plus rapide de se faire limiter ou servir un captcha. Gardez le
sleepentre les pages et évitez de crawler une ville à pleine vitesse. - Appuyez-vous sur la rotation. Un pool d'IP résidentielles répartit les requêtes sur de nombreuses adresses d'utilisateurs réels pour qu'aucune ne déclenche une limite de débit. La Crawling API s'en charge pour vous ; si vous montez votre propre stack, c'est la partie à bien faire.
- Lisez les codes de statut. Une exécution qui se met à renvoyer des défis ou des erreurs vous dit que le débit ou le palier d'IP actuel ne suffit plus. Traitez cela comme un signal pour lever le pied, pas comme du bruit à ignorer.
-
Attendez-vous aux changements de structure. Realtor.com rafraîchit son site régulièrement, alors revérifiez les clés
__NEXT_DATA__quand des champs se vident plutôt que de supposer que le scraper est cassé.
Pour le manuel plus large, voir comment scraper des sites web sans se faire bloquer et le guide pour crawler les sites web JavaScript. Pour passer à l'échelle au-delà d'une seule ville, regroupez vos URL de recherche et passez-les dans la même boucle find_properties.
Est-il légal de scraper Realtor.com ?
Que le scraping de Realtor.com soit autorisé dépend des conditions d'utilisation de Realtor.com, de votre juridiction et de ce que vous faites des données. Les conditions restreignent l'accès automatisé, donc le scraping peut aller à l'encontre de ces conditions quelle que soit la prudence de votre outillage. Aucun des codes présentés ici ne change cela ; il fait juste fonctionner la partie technique. Lisez les Conditions d'utilisation de Realtor.com et son robots.txt, respectez toute limite de débit indiquée, et traitez les deux comme la frontière de ce que vous collectez.
Quelques lignes à respecter. Ne collectez que les données d'annonce publiques : le prix affiché, les chambres, les salles de bains, la superficie, l'adresse et le lien d'annonce que tout le monde peut voir sans compte. Une large part des données sous-jacentes de Realtor.com provient de flux MLS qui sont sous licence, pas librement réutilisables, donc un enregistrement de bien peut porter des restrictions d'usage même quand la page est publique ; traitez les champs issus du MLS comme du contenu sous licence plutôt que des données ouvertes. Évitez tout ce qui est lié à des individus identifiables, y compris les noms et coordonnées des agents, courtiers ou propriétaires affichés sur une page, qui sont des données personnelles sous des régimes comme le RGPD et le CCPA. Si vous prévoyez de réutiliser les données commercialement ou en masse, obtenez une autorisation ou un flux sous licence plutôt que de supposer que le silence vaut consentement.
Ce guide est délibérément cantonné aux pages d'annonces publiques car c'est la ligne qui rend le travail défendable. Il ne couvre rien derrière une connexion, les données de recherche enregistrée ou de compte, les détails personnels ou de contact des agents et propriétaires, ni aucune tentative de contourner l'authentification. Uniquement des données d'annonce publiques. Si votre projet a besoin de plus que cela, Realtor.com et les systèmes MLS derrière lui proposent des partenariats de données officiels et des flux sous licence, qui sont la bonne voie pour un volume de production, pas un scraper plus malin.
Points clés
-
Realtor.com cache ses données dans
__NEXT_DATA__. Les annonces résident dans un bloc JSON au sein d'un script caché, donc vous lisez cette charge utile au lieu de scraper des éléments DOM. -
Vous avez besoin du rendu et d'une IP de confiance ensemble. La Crawling API avec un token JS rend la page Next.js et fait tourner les IP en un seul appel ;
ajax_waitetpage_waitcontrôlent combien de temps elle attend. -
Analysez de manière défensive. Mappez le prix, les chambres, les salles de bains, le sqft, l'adresse et le lien avec des garde-fous
.getetor {}pour qu'un champ null ou un renommage de clé dégénère en valeurs vides, pas en plantage. -
Paginez avec
/pg-Net exportez les deux formats. Parcourez le suffixe de page, analysez chaque bien, puis écrivez du JSON et du CSV à partir des mêmes enregistrements plats. - Restez sur les données publiques. Respectez les CGU et le robots.txt de Realtor.com, traitez les champs issus du MLS comme sous licence, et ne collectez jamais les détails personnels des agents ou propriétaires.
Foire aux questions
Pourquoi une simple requête ne renvoie-t-elle aucune annonce de Realtor.com ?
Parce que Realtor.com est une application Next.js qui hydrate ses annonces dans le navigateur. Les données ne sont pas dans les éléments HTML statiques qu'une requête brute renvoie ; elles se trouvent dans un script JSON caché __NEXT_DATA__ qui n'apparaît qu'une fois la page rendue. Pour les obtenir, vous devez d'abord rendre la page, ce dont se charge le token JS de la Crawling API, puis lire le JSON hors de ce script.
Qu'est-ce que le script __NEXT_DATA__ et pourquoi le scraper ?
C'est la charge utile JSON que les sites Next.js intègrent pour que la page puisse s'hydrater dans le navigateur. Sur Realtor.com, elle contient le jeu de données complet des résultats de recherche, y compris le prix, les chambres, les salles de bains, le sqft, l'adresse et le permalien de chaque annonce. Le lire est plus stable qu'analyser le HTML visible, car les clés JSON changent moins souvent que les noms de classes CSS autour d'elles.
Ai-je besoin du token normal ou du token JS pour Realtor.com ?
Le token JS. Le token normal récupère du HTML statique, qui sur Realtor.com n'inclut pas le contenu hydraté __NEXT_DATA__ dont vous avez besoin. Le token JS rend d'abord la page dans un vrai navigateur, de sorte que le jeu de données intégré est présent quand vous l'extrayez et l'analysez.
Comment gérer la pagination sur l'ensemble des annonces d'une ville ?
Realtor.com utilise un suffixe /pg-<PAGE> sur ses URL de recherche, donc vous construisez {city}_{state}/pg-{page} pour chaque page et bouclez sur le numéro de page. La fonction find_properties ci-dessus fait exactement cela : elle crawle chaque page, extrait le jeu de données, analyse chaque bien et fait une pause entre les pages pour que vous restiez dans un débit de requêtes poli.
Quels champs puis-je extraire d'une annonce Realtor.com ?
Des champs d'annonce publics : le prix affiché, le nombre de chambres et de salles de bains, la superficie, l'adresse complète et le lien d'annonce reconstruit à partir du permalien. Restez sur les données visibles par tout visiteur sans compte, traitez les champs issus du MLS comme du contenu sous licence, et évitez les détails personnels des agents, courtiers ou propriétaires, qui sortent du périmètre de l'annonce publique que ce guide couvre.
Puis-je suivre les changements de prix et d'annonces dans le temps ?
Oui. Exécutez le scraper selon une planification, indexez chaque annonce par son permalien, et comparez les champs de prix et de statut entre les exécutions pour capturer les nouvelles annonces, les baisses de prix et les biens vendus. Gardez le débit de requêtes modeste et ne stockez que les champs d'annonce publics dont vous avez besoin. Pour des cibles immobilières connexes, voir les guides sur comment scraper Zillow et comment scraper Redfin.
Crawlez n'importe quel site à grande échelle, sans combattre l'infrastructure.
Crawlbase gère les proxies, les empreintes et les CAPTCHA afin que votre équipe livre des pipelines de données au lieu de maintenir la plomberie de crawl. 1 000 requêtes gratuites, sans carte requise.
