Homes.com répertorie des données immobilières à travers les États-Unis, et ses pages de recherche et d'annonces contiennent exactement les champs structurés qui alimentent le suivi des prix, l'étude de marché et l'analyse d'investissement : un titre d'annonce, l'adresse postale, le prix demandé, le nombre de chambres, de salles de bains, la superficie et le lien vers chaque bien. Regroupez ces informations dans un tableur et vous pouvez comparer des quartiers, observer l'évolution des prix dans le temps et repérer les annonces méritant un examen approfondi sans avoir à cliquer sur des centaines de pages à la main.
Ce guide vous montre comment scraper Homes.com avec Python de manière fiable. Vous construisez un petit scraper fonctionnel qui récupère une page de recherche rendue via la Crawling API, analyse les champs voulus avec BeautifulSoup, parcourt la pagination et écrit les résultats en JSON et CSV. L'ensemble du tutoriel se limite aux données d'annonces publiques, et la section sur la légalité proche de la fin n'est pas une clause de style : lisez-la avant de lancer ce scraper sur des volumes importants.
Ce que vous allez construire
Un script Python qui prend une URL de recherche publique sur Homes.com, récupère le HTML rendu via la Crawling API, parcourt plusieurs pages de résultats et extrait un enregistrement structuré par annonce. Nous utiliserons une recherche dans une ville unique comme exemple fil rouge et extrairons ces champs :
- Title le type d'annonce, par exemple "House for Rent" ou "Condo for Rent".
- Address l'adresse postale du bien.
- Price le prix demandé ou le loyer mensuel affiché sur la carte.
- Beds le nombre de chambres.
- Baths le nombre de salles de bains.
- Size la superficie en pieds carrés, lorsque l'annonce la mentionne.
- Link l'URL absolue vers la page complète du bien.
Pourquoi une requête ordinaire échoue sur Homes.com
Si vous demandez une URL de recherche Homes.com avec un client HTTP nu, vous obtenez une réponse avec le statut 200 et presque aucune donnée d'annonce dans le corps. Deux obstacles se dressent contre vous. Premièrement, Homes.com affiche la majeure partie de son contenu dans le navigateur via JavaScript, de sorte que le HTML initial n'est qu'une enveloppe vide qui ne se remplit qu'après l'exécution des scripts de la page. Deuxièmement, le site repère rapidement le trafic automatisé : les IPs de datacenter et les patterns de requêtes qui ne ressemblent pas à un vrai navigateur sont limités, mis au défi ou confrontés à un captcha avant même d'atteindre le contenu rendu.
Un scraper Homes.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 considère comme un visiteur réel. Vous pouvez assembler cela vous-même avec un navigateur headless et un pool de proxies résidentiels rotatifs, mais connecter ces éléments et les maintenir en bon état représente l'essentiel du travail. La Crawling API regroupe 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 renvoie un HTML finalisé à parser. Pour en savoir plus sur les raisons pour lesquelles les sites dynamiques ont besoin de cela, consultez comment crawler des sites web JavaScript.
Crawlbase propose deux types de tokens. Le token normal récupère le HTML statique ; le token JavaScript (JS) rend d'abord la page dans un vrai navigateur. Homes.com renseigne ses champs d'annonces côté client, vous avez donc besoin du token JS ici. Utiliser le token normal renvoie la même enveloppe vide qu'une requête ordinaire, et il n'y a rien d'utile à en parser.
Prérequis
Quelques éléments doivent être en place avant d'écrire du code. Aucun ne prend longtemps.
Python de base. Vous devez être à l'aise pour écrire et exécuter un script Python et installer des packages avec pip. Si vous débutez dans ce langage, le tutoriel scraper un site web avec Python couvre les bases que ce tutoriel suppose acquises.
Python 3.8 ou version ultérieure. Confirmez votre version avec python --version. Si vous ne l'avez pas, installez-la 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) depuis la page de documentation de votre compte. Vous obtenez jusqu'à 20 000 requêtes gratuites et aucune carte bancaire 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 isoler les dépendances du projet, puis installez les deux bibliothèques dont le scraper a besoin.
python --version python -m venv homes_scraping_env source homes_scraping_env/bin/activate pip install crawlbase beautifulsoup4
Sous Windows, activez l'environnement avec homes_scraping_env\Scripts\activate à la place de la ligne source. Deux dépendances font le travail : crawlbase est le client officiel pour la Crawling API, et beautifulsoup4 analyse le HTML renvoyé pour vous permettre d'extraire des champs individuels par sélecteur CSS. Si vous n'avez pas encore utilisé ce parser, le guide BeautifulSoup est un bon complément à ce tutoriel.
Étape 1 : Récupérer la page de recherche rendue
Commencez par obtenir la page finale. Importez la classe CrawlingAPI, initialisez-la avec votre token JS et demandez l'URL de recherche. Les deux options d'attente sont importantes pour une cible rendue côté client : ajax_wait indique à l'API d'attendre la fin du chargement du contenu asynchrone, et page_wait maintient un délai fixe en millisecondes après le chargement afin que les éléments à rendu tardif apparaissent avant la capture de la page. Vérifier le statut avant de parser permet de rendre les échecs visibles plutôt que silencieux.
from crawlbase import CrawlingAPI crawling_api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) options = { "ajax_wait": "true", "page_wait": 10000, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", } def make_crawlbase_request(url): response = crawling_api.get(url, options) if response["headers"]["cb_status"] == "200": return response["body"].decode("utf-8") print(f"Failed to fetch the page. Crawlbase status: {response['headers']['cb_status']}") return None if __name__ == "__main__": url = "https://www.homes.com/los-angeles-ca/homes-for-rent/p1/" html = make_crawlbase_request(url) print(html[:500] if html else "No HTML returned")
La fonction lit response["headers"]["cb_status"], le statut par requête que la Crawling API renvoie avec le corps, et ne retourne du HTML que lorsqu'il indique "200". Dix secondes de page_wait constituent un point de départ raisonnable pour Homes.com ; augmentez si les champs d'annonces reviennent vides. Exécutez le script avec python homes_scraper.py et vous devriez voir de véritables balises de recherche, pas l'enveloppe vide qu'une requête ordinaire renvoie. Cela confirme que le rendu fonctionne avant d'écrire un seul sélecteur.
Ce seul appel make_crawlbase_request s'occupe de la partie difficile à votre place. Homes.com nécessite une page rendue derrière une IP de confiance, et la Crawling API prend votre token JS, exécute la page dans un vrai navigateur, effectue une rotation des IPs résidentielles côté serveur et retourne un HTML finalisé, vous dispensant d'exploiter vous-même une flotte de navigateurs headless et un pool de proxies. Pointez-la sur une page de recherche publique dans le cadre du niveau gratuit d'abord.
Étape 2 : Parser les cartes d'annonces avec BeautifulSoup
Une fois le HTML rendu en main, chargez-le dans BeautifulSoup et extrayez chaque carte. Inspectez une page de recherche Homes.com dans les outils de développement de votre navigateur et vous trouverez chaque annonce enveloppée dans un div avec la classe for-rent-content-container. Sélectionnez-les tous, puis lisez les champs individuels de chacun. Le titre se trouve dans un p.property-name, l'adresse dans un p.address, et le prix, les chambres et les salles de bains proviennent des éléments li à l'intérieur de ul.detailed-info-container, dans cet ordre.
from bs4 import BeautifulSoup BASE_URL = "https://www.homes.com" def parse_listings(html): soup = BeautifulSoup(html, "html.parser") cards = soup.select("div.for-rent-content-container") properties = [] for card in cards: title_elem = card.select_one("p.property-name") address_elem = card.select_one("p.address") info_container = card.select_one("ul.detailed-info-container") info = info_container.find_all("li") if info_container else [] link_elem = card.select_one("a") properties.append({ "title": title_elem.text.strip() if title_elem else "N/A", "address": address_elem.text.strip() if address_elem else "N/A", "price": info[0].text.strip() if len(info) > 0 else "N/A", "beds": info[1].text.strip() if len(info) > 1 else "N/A", "baths": info[2].text.strip() if len(info) > 2 else "N/A", "size": info[3].text.strip() if len(info) > 3 else "N/A", "link": BASE_URL + link_elem["href"] if link_elem and link_elem.get("href") else "N/A", }) return properties
Chaque garde renvoie "N/A" au lieu de lever une exception lorsqu'un élément est absent, ce qui évite qu'un champ manquant ne fasse planter l'exécution. La ligne de détail est positionnelle : Homes.com présente le prix, les chambres, les salles de bains et la superficie sous forme d'éléments li ordonnés, le code les lit donc par index en vérifiant la longueur d'abord. Le lien est extrait de l'ancre de la carte sous forme de chemin relatif, et le préfixe BASE_URL vous donne une URL absolue que vous pouvez suivre directement jusqu'à la page du bien.
Les noms de classes Homes.com (la carte for-rent-content-container, les champs property-name et address, les lignes detailed-info-container) changent sans préavis. Traitez les sélecteurs ci-dessus comme un modèle de départ, pas comme un contrat. Quand un champ revient en "N/A", ré-inspectez la page en direct dans les outils de développement de votre navigateur et mettez à jour le sélecteur. La maintenance périodique des sélecteurs est normale pour tout scraper en production, ce n'est pas le signe que quelque chose est cassé.
Étape 3 : Parcourir la pagination
Une page, c'est une démo ; un vrai travail s'étend sur l'ensemble des résultats. Homes.com ajoute un segment de page au chemin de recherche, donc les annonces d'une ville se trouvent à .../homes-for-rent/p1/, .../p2/, et ainsi de suite. Bouclez sur un nombre fixe de pages, récupérez chacune via la même fonction de requête, parsez ses cartes et collectez le tout dans une liste unique. Une courte pause entre les pages évite de surcharger le site.
import time SEARCH_URL = "https://www.homes.com/los-angeles-ca/homes-for-rent" MAX_PAGES = 3 def scrape_search(): properties = [] for page in range(1, MAX_PAGES + 1): url = f"{SEARCH_URL}/p{page}/" print(f"Scraping page {page}: {url}") html = make_crawlbase_request(url) if html: properties.extend(parse_listings(html)) time.sleep(2) return properties
Le time.sleep(2) entre les pages est délibéré : il espace l'exécution pour ne pas surcharger le site, ce qui est l'habitude la plus efficace pour rester débloqué. Ajustez MAX_PAGES et le slug de ville dans SEARCH_URL selon votre cible. Pour scraper les locations à Chicago à la place, remplacez par chicago-il ; pour scraper des biens à vendre, changez homes-for-rent par le chemin de vente correspondant.
Étape 4 : Exporter en JSON et CSV
Une fois une liste d'enregistrements en main, écrivez-la dans le format adapté à votre travail en aval. Le JSON préserve la structure pour le code qui le relit ; le CSV s'intègre directement dans un tableur pour le tri et les graphiques. Deux petits utilitaires couvrent les deux cas.
import json import csv def save_to_json(properties, filename="properties.json"): with open(filename, "w") as f: json.dump(properties, f, indent=4) def save_to_csv(properties, filename="properties.csv"): if not properties: return with open(filename, "w", newline="") as f: writer = csv.DictWriter(f, fieldnames=properties[0].keys()) writer.writeheader() writer.writerows(properties)
Le writer CSV lit ses en-têtes de colonnes depuis les clés du premier enregistrement, ainsi les colonnes restent en phase avec les champs parsés à l'étape 2. Si vous ajoutez ou renommez un champ, les deux exports suivent automatiquement.
Assembler le tout
Voici le scraper complet et fonctionnel : récupérez chaque page de recherche via la Crawling API, parsez les cartes, parcourez la pagination et écrivez les résultats en JSON et CSV. Insérez votre token et exécutez-le.
import json import csv import time from crawlbase import CrawlingAPI from bs4 import BeautifulSoup crawling_api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) BASE_URL = "https://www.homes.com" SEARCH_URL = "https://www.homes.com/los-angeles-ca/homes-for-rent" MAX_PAGES = 3 options = { "ajax_wait": "true", "page_wait": 10000, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", } def make_crawlbase_request(url): response = crawling_api.get(url, options) if response["headers"]["cb_status"] == "200": return response["body"].decode("utf-8") print(f"Failed to fetch the page. Crawlbase status: {response['headers']['cb_status']}") return None def parse_listings(html): soup = BeautifulSoup(html, "html.parser") cards = soup.select("div.for-rent-content-container") properties = [] for card in cards: title_elem = card.select_one("p.property-name") address_elem = card.select_one("p.address") info_container = card.select_one("ul.detailed-info-container") info = info_container.find_all("li") if info_container else [] link_elem = card.select_one("a") properties.append({ "title": title_elem.text.strip() if title_elem else "N/A", "address": address_elem.text.strip() if address_elem else "N/A", "price": info[0].text.strip() if len(info) > 0 else "N/A", "beds": info[1].text.strip() if len(info) > 1 else "N/A", "baths": info[2].text.strip() if len(info) > 2 else "N/A", "size": info[3].text.strip() if len(info) > 3 else "N/A", "link": BASE_URL + link_elem["href"] if link_elem and link_elem.get("href") else "N/A", }) return properties def scrape_search(): properties = [] for page in range(1, MAX_PAGES + 1): url = f"{SEARCH_URL}/p{page}/" print(f"Scraping page {page}: {url}") html = make_crawlbase_request(url) if html: properties.extend(parse_listings(html)) time.sleep(2) return properties def save_to_json(properties, filename="properties.json"): with open(filename, "w") as f: json.dump(properties, f, indent=4) def save_to_csv(properties, filename="properties.csv"): if not properties: return with open(filename, "w", newline="") as f: writer = csv.DictWriter(f, fieldnames=properties[0].keys()) writer.writeheader() writer.writerows(properties) if __name__ == "__main__": listings = scrape_search() save_to_json(listings) save_to_csv(listings) print(f"Saved {len(listings)} listings to properties.json and properties.csv")
À quoi ressemble le résultat
Exécutez le script complet avec python homes_scraper.py et vous obtenez un enregistrement propre par annonce, prêt à écrire en JSON, CSV ou dans une base de données.
[ { "title": "Condo for Rent", "address": "3824 Keystone Ave Unit 2, Culver City, CA 90232", "price": "$3,300 per month", "beds": "2 Beds", "baths": "1.5 Baths", "size": "1,100 Sq Ft", "link": "https://www.homes.com/los-angeles-ca/homes-for-rent/property/3824-keystone-ave-culver-city-ca-unit-2/2er2mwklw8zq6/" }, { "title": "House for Rent", "address": "3901 Alonzo Ave, Encino, CA 91316", "price": "$17,000 per month", "beds": "4 Beds", "baths": "3.5 Baths", "size": "3,400 Sq Ft", "link": "https://www.homes.com/los-angeles-ca/homes-for-rent/property/3901-alonzo-ave-encino-ca/879negnf45nee/" } ]
La version CSV des mêmes données comporte une ligne par annonce avec les colonnes title, address, price, beds, baths, size et link, ce qui s'ouvre proprement dans n'importe quel tableur pour trier par prix ou filtrer par quartier.
Passer aux pages de détail des biens
Le scraper de recherche vous fournit les champs au niveau de la carte. Quand vous voulez le détail complet d'un bien, suivez le link déjà capturé et parsez la page du bien, qui contient des champs plus riches comme la superficie du terrain, une description plus longue et la ligne de contact public de l'agent. La page du bien utilise ses propres sélecteurs : l'adresse se trouve dans div.property-info-address, le prix dans span#price, les chambres et salles de bains dans span.feature-beds et span.feature-baths, et la superficie du terrain dans span.property-info-feature.lotsize. Réutilisez make_crawlbase_request pour récupérer la page, puis mappez ces sélecteurs de la même manière que pour les cartes. Espacez chaque requête de bien avec une courte pause, comme le fait la boucle de recherche.
Rester débloqué
Même avec le rendu pris en charge, Homes.com surveille le trafic qui ressemble à des scrapers. Quelques habitudes maintiennent une exécution saine, et elles s'appliquent à toute cible commerciale difficile.
-
Espacez vos requêtes. Enchaîner les pages dans une boucle serrée est le moyen le plus rapide d'être limité ou confronté à un captcha. Espacez les requêtes, comme le fait le
sleepci-dessus, et variez vos cibles plutôt que de crawler un seul chemin à pleine vitesse. - Misez sur la rotation. Un pool d'IPs résidentielles répartit les requêtes sur de nombreuses adresses d'utilisateurs réels, de sorte qu'aucune ne déclenche une limite de débit. La Crawling API s'en charge pour vous ; si vous gérez votre propre stack, c'est la partie à soigner.
- Lisez les codes de statut. Une exécution qui commence à retourner des défis ou des erreurs vous indique que le débit ou le niveau d'IP actuel n'est plus suffisant. Traitez-le comme un signal pour ralentir, pas comme du bruit à ignorer.
Pour le guide de référence, consultez comment scraper des sites web sans être bloqué. Si vous préférez acheminer votre propre trafic via un pool rotatif plutôt que d'utiliser l'API gérée, le Smart AI Proxy (aussi appelé AI Proxy) vous offre la même rotation d'IPs résidentielles comme endpoint proxy direct. La même approche s'étend aux autres sites immobiliers : consultez nos guides sur scraper Zillow, scraper Redfin et scraper Realtor.com.
Est-il légal de scraper Homes.com ?
La légitimité du scraping de Homes.com dépend des conditions d'utilisation de Homes.com, de votre juridiction et de ce que vous faites des données. Les conditions restreignent l'accès automatisé, le scraping peut donc aller à l'encontre de ces conditions quelle que soit la prudence de vos outils. Aucun code ici ne change cela ; il permet simplement que la partie technique fonctionne. Lisez les conditions d'utilisation de Homes.com et son fichier robots.txt, respectez les limites de débit indiquées et traitez les deux comme la frontière de ce que vous collectez.
Quelques lignes valent la peine d'être tenues. Collectez uniquement des données d'annonces publiques : le titre, l'adresse, le prix, les chambres, les salles de bains, la superficie et le lien que tout le monde peut voir sans compte. Maintenez votre volume de requêtes suffisamment bas pour ne pas surcharger les serveurs du site. Évitez tout ce qui est lié à des individus identifiables, y compris les noms et coordonnées des agents d'annonce, des propriétaires ou des gestionnaires immobiliers affichés sur une page. Ces éléments constituent des données personnelles, et leur collecte ou stockage peut faire intervenir des lois sur la vie privée comme le RGPD et le CCPA, alors laissez-les de côté sauf si vous avez une base légale claire et un besoin réel.
Un dernier point spécifique à l'immobilier : une grande partie des données d'annonces sous-jacentes provient de Multiple Listing Services (MLS), et ces données sont fréquemment soumises à des licences qui restreignent leur redistribution. Si votre projet nécessite ce niveau de détail ou toute réutilisation commerciale à grande échelle, la voie correcte est un flux MLS sous licence ou un accord de données officiel, pas un scraper plus astucieux. Ce guide est délibérément limité 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 compte ou de recherche sauvegardée, les coordonnées personnelles des agents ou propriétaires, ou toute tentative de contournement de l'authentification. Données d'annonces publiques uniquement.
Points clés
- Homes.com est rendu côté client. Une requête ordinaire retourne une enveloppe vide, vous devez donc rendre la page avant de la parser.
-
Vous avez besoin du rendu et d'une IP de confiance ensemble. La Crawling API avec un token JS fait les deux en un seul appel ;
ajax_waitetpage_waitcontrôlent la durée d'attente du contenu. -
BeautifulSoup se charge de l'extraction. Mappez le titre, l'adresse, le prix, les chambres, les salles de bains, la superficie et le lien sur les sélecteurs de la carte
for-rent-content-container, et attendez-vous à ce que ces sélecteurs évoluent. -
Parcourez la pagination, puis exportez. Itérez sur le segment
p{page}, collectez chaque carte, espacez l'exécution avec une courte pause et écrivez les résultats en JSON et CSV. - Restez sur les données publiques. Respectez les CGU et le robots.txt de Homes.com, collectez uniquement les champs d'annonces publics, laissez de côté les coordonnées personnelles des agents et propriétaires, et utilisez un flux MLS sous licence pour tout usage commercial ou en volume.
Foire aux questions
Pourquoi une requête ordinaire ne retourne-t-elle aucune donnée de Homes.com ?
Parce que Homes.com affiche son contenu d'annonces côté client avec JavaScript. Le HTML initial n'est qu'une enveloppe qui ne se remplit qu'après l'exécution des scripts de la page dans un navigateur, donc une requête HTTP brute retourne le statut 200 avec les champs prix, chambres, salles de bains et superficie vides. Pour obtenir des données réelles, vous devez d'abord rendre la page, ce que gère le token JS de la Crawling API.
Ai-je besoin du token normal ou du token JS pour Homes.com ?
Le token JS. Le token normal récupère le HTML statique, qui sur Homes.com est la même enveloppe vide qu'une requête ordinaire. Le token JS rend la page dans un vrai navigateur avant de renvoyer le HTML, de sorte que les champs d'annonces sont présents lorsque BeautifulSoup les parse. Les options ajax_wait et page_wait indiquent au moteur de rendu combien de temps attendre ce contenu.
Quelles données puis-je scraper d'une annonce Homes.com ?
Les champs d'annonces publics : le titre, l'adresse postale, le prix demandé ou le loyer mensuel, le nombre de chambres et salles de bains, la superficie lorsqu'elle est indiquée, et le lien vers la page du bien. Restez sur des données visibles par n'importe quel visiteur sans compte, et évitez les coordonnées personnelles des agents ou propriétaires, qui sortent du périmètre des annonces publiques couvert par ce guide.
Mes sélecteurs retournent "N/A". Qu'est-ce qui a changé ?
Presque certainement le balisage de Homes.com. La carte for-rent-content-container, les champs property-name et address, et les lignes detailed-info-container changent sans préavis, de sorte que des sélecteurs qui fonctionnaient le mois dernier peuvent ne plus fonctionner. Ré-inspectez une page en direct dans les outils de développement de votre navigateur et mettez à jour les sélecteurs. La maintenance périodique des sélecteurs est normale pour tout scraper en production.
Comment gérer la pagination des annonces d'une ville ?
Homes.com ajoute un segment p{page} au chemin de recherche, vous récupérez donc .../p1/, .../p2/, et ainsi de suite dans une boucle, parsez les cartes de chaque page et collectez-les dans une liste. Gardez une courte pause entre les requêtes et arrêtez à votre limite de pages choisie. La fonction scrape_search ci-dessus montre la boucle complète.
Comment éviter d'être bloqué en scrapant Homes.com ?
Maintenez un faible taux de requêtes par IP, espacez les requêtes avec un court délai, variez vos cibles plutôt que de boucler sur un seul chemin, et passez par des IPs résidentielles rotatives pour qu'aucune adresse unique ne déclenche une limite de débit. La Crawling API gère la rotation et un pool d'IPs de confiance pour vous ; si vous construisez votre propre stack, c'est là qu'il faut investir. Surveillez les codes de statut et ralentissez lorsque vous commencez à voir des défis.
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.
