Trulia est l'une des places de marché immobilières les plus fréquentées des États-Unis, et ses résultats de recherche portent exactement les données structurées qui alimentent le suivi des prix, la recherche de marché et l'analyse d'investissement : le prix demandé, les chambres, les salles de bain, la superficie, l'adresse et un lien vers la page de détail de chaque propriété. Pour quiconque surveille un marché local, ces pages de listings constituent la matière première. Le problème est que Trulia rend ses résultats côté client et se défend vigoureusement contre le trafic automatisé, donc une requête HTTP simple vous donne une coquille quasi vide au lieu des listings recherchés.
Ce guide vous montre comment scraper Trulia avec Python de façon fiable. Vous construisez un petit scraper fonctionnel qui récupère une page de résultats de recherche rendue via la Crawling API, analyse chaque listing avec BeautifulSoup, gère la pagination et exporte les données en JSON et CSV. L'ensemble du tutoriel se limite aux listings de propriétés publics, et la section sur la légalité vers la fin n'est pas du remplissage, lisez-la avant de pointer ceci vers un volume réel.
Ce que vous allez construire
Un script Python qui prend une URL de recherche publique Trulia (par exemple, des propriétés à vendre à Los Angeles, CA), récupère le HTML rendu via la Crawling API et extrait un enregistrement structuré pour chaque listing sur la page. Nous extrayons ces champs de chaque carte de propriété :
- Prix le prix demandé affiché sur le listing.
- Adresse l'adresse de la propriété.
- Chambres le nombre de chambres.
- Salles de bain le nombre de salles de bain.
- Superficie la surface habitable en pieds carrés.
- Lien l'URL de la page de détail de la propriété.
Pourquoi une requête simple échoue sur Trulia
Si vous demandez une URL de recherche Trulia avec un client HTTP brut, vous obtenez une réponse avec le statut 200 et presque aucune des données de listing dans le corps. Deux facteurs jouent contre vous. Premièrement, Trulia rend une grande partie de son contenu de résultats dans le navigateur avec JavaScript, donc le HTML initial est une coquille mince qui ne se remplit qu'après l'exécution des scripts de la page. Deuxièmement, le site signale rapidement le trafic automatisé : les IPs de datacenter et les patterns de requêtes qui ne ressemblent pas à un vrai navigateur sont mis au défi, limités en débit ou reçoivent un captcha avant d'atteindre les listings rendus.
Un scraper Trulia 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 headless plus un pool de proxies résidentiels rotatifs, mais assembler ces éléments et les maintenir opérationnels représente la majeure partie du travail. La Crawling API regroupe les deux dans un seul appel : vous lui envoyez l'URL avec un token JavaScript, elle rend la page derrière une IP de confiance et vous retourne le HTML finalisé à analyser.
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. Trulia remplit ses cartes de listing côté client, donc vous avez besoin du token JS ici. Utiliser le token normal retourne la même coquille vide qu'une simple requête, et il n'y a rien d'utile à en extraire.
Prérequis
Quelques éléments doivent être en place avant d'écrire le moindre code. Aucun ne prend longtemps.
Notions de base en Python. Vous devez être à l'aise pour écrire et exécuter un script Python et installer des packages avec pip. Si vous débutez avec le langage, le guide de web scraping Python couvre le niveau supposé par ce tutoriel.
Python 3.8 ou supérieur. Confirmez votre version avec python --version, et vérifiez que pip est présent avec pip --version. Si vous n'avez pas Python, installez-le depuis python.org selon votre système d'exploitation.
Un compte Crawlbase et un token JS. Inscrivez-vous pour obtenir jusqu'à 20 000 requêtes gratuites, ouvrez votre tableau de bord et copiez votre token JavaScript (JS) depuis la page de docs du compte. Traitez le token comme un mot de passe : il authentifie vos requêtes, donc 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 trois bibliothèques dont le scraper a besoin.
python --version python -m venv trulia_env source trulia_env/bin/activate pip install crawlbase beautifulsoup4 pandas
Sur Windows, activez l'environnement avec trulia_env\Scripts\activate à la place de la ligne source. Trois dépendances font le travail : crawlbase est le client officiel pour la Crawling API, beautifulsoup4 analyse le HTML retourné pour que vous puissiez extraire les champs par sélecteur CSS, et pandas gère l'export CSV à la fin. Si vous n'avez pas utilisé le parser auparavant, 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 finalisée. Importez la classe CrawlingAPI, initialisez-la avec votre token JS et demandez l'URL de recherche. Comme Trulia charge ses cartes de façon asynchrone, passez ajax_wait et page_wait pour que l'API attende que les listings soient présents. Vérifier le statut avant d'analyser garde les échecs visibles plutôt qu'en silence.
from crawlbase import CrawlingAPI api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) options = {"ajax_wait": "true", "page_wait": 8000} def crawl(page_url): response = api.get(page_url, options) if response["headers"]["cb_status"] == "200": return response["body"].decode("utf-8") print(f"Request failed. cb_status: {response['headers']['cb_status']}") return None if __name__ == "__main__": search_url = "https://www.trulia.com/CA/Los_Angeles/" html = crawl(search_url) print(html[:500] if html else "No HTML returned")
Les deux options d'attente comptent pour une cible rendue côté client comme celle-ci. ajax_wait demande à l'API d'attendre que le contenu asynchrone finisse de se charger, et page_wait maintient un délai fixe en millisecondes après le chargement pour que les cartes se rendant tardivement apparaissent avant la capture de la page. Huit secondes est un bon point de départ pour Trulia ; augmentez si les listings reviennent vides. La Crawling API retourne un en-tête cb_status (legacy pc_status) qui reflète le résultat du crawl, vérifiez donc celui-ci plutôt que le code HTTP brut. Exécutez le script avec python trulia_scraper.py et vous devriez voir du vrai balisage de listing, pas la coquille vide qu'une requête normale retourne. Cela confirme que le rendu fonctionne avant d'écrire un seul sélecteur.
Trulia nécessite une page rendue derrière une IP de confiance, en un seul appel, et les options ajax_wait plus page_wait que vous venez de définir sont la façon d'attendre son chargement côté client. La Crawling API prend un token JS, exécute la page dans un vrai navigateur, fait tourner les IPs résidentielles côté serveur, et vous restitue le HTML finalisé, pour que vous évitiez de gérer une flotte headless et un pool de proxies vous-même. Pointez-la sur une page de recherche publique avec le niveau gratuit d'abord.
Étape 2 : collecter les cartes de listing
Avant d'extraire des champs individuels, vous avez besoin de l'ensemble des cartes de propriété sur la page. Sur Trulia, chaque listing se trouve dans un élément li, et tous ces éléments li vivent dans un ul avec l'attribut data-testid="search-result-list-container". Sélectionner les enfants directs de ce conteneur vous donne un noeud par propriété.
from bs4 import BeautifulSoup def get_listings(html): soup = BeautifulSoup(html, "html.parser") return soup.select('ul[data-testid="search-result-list-container"] > li')
Cela retourne une liste d'éléments de cartes. Chacun est une portée autonome que vous pouvez interroger pour le prix, l'adresse et le reste de cette propriété, ce qui garde les sélecteurs par champ simples et évite de mélanger les données entre listings.
Étape 3 : analyser les champs de chaque carte
Avec une carte en main, extrayez chaque champ par son attribut data-testid. Trulia est cohérent sur ces attributs entre les listings, ce qui les rend plus stables à cibler que les noms de classes visuelles. Encadrez chaque lookup pour qu'un élément manquant retourne None plutôt que de planter, car tous les listings ne portent pas tous les champs (un listing terrain seulement, par exemple, peut ne pas avoir de comptage de chambres ou de salles de bain).
def text_at(listing, selector): el = listing.select_one(selector) return el.get_text(strip=True) if el else None def parse_listing(listing): link_el = listing.select_one('a[data-testid="property-card-link"]') link = "https://www.trulia.com" + link_el["href"] if link_el else None return { "price": text_at(listing, 'div[data-testid="property-price"]'), "address": text_at(listing, 'div[data-testid="property-address"]'), "beds": text_at(listing, 'div[data-testid="property-beds"]'), "baths": text_at(listing, 'div[data-testid="property-baths"]'), "size": text_at(listing, 'div[data-testid="property-floorSpace"]'), "link": link, }
Le helper text_at fait la partie répétitive : il interroge un élément et retourne son texte nettoyé, ou None quand l'élément est absent, pour qu'un champ manquant ne plante jamais l'exécution. Le prix se trouve dans property-price, l'adresse dans property-address, les chambres et salles de bain dans property-beds et property-baths, et la superficie dans property-floorSpace. Le lien vers la page de détail se trouve sur un a avec data-testid="property-card-link", et comme ce href est relatif, vous préfixez l'origine Trulia pour obtenir une URL absolue.
Les valeurs data-testid de Trulia sont stables aujourd'hui mais pas garanties. Quand un champ revient None sur chaque carte, ré-inspectez un listing live 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 de production, pas le signe que quelque chose est cassé.
Étape 4 : assembler le script complet
Câblez maintenant la récupération, la collecte des cartes et l'analyse des champs dans un seul script exécutable. Récupérez le HTML rendu, itérez les cartes, analysez chacune en enregistrement et affichez les résultats en JSON.
import json from crawlbase import CrawlingAPI from bs4 import BeautifulSoup api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) options = {"ajax_wait": "true", "page_wait": 8000} def crawl(page_url): response = api.get(page_url, options) if response["headers"]["cb_status"] == "200": return response["body"].decode("utf-8") print(f"Request failed. cb_status: {response['headers']['cb_status']}") return None def get_listings(html): soup = BeautifulSoup(html, "html.parser") return soup.select('ul[data-testid="search-result-list-container"] > li') def text_at(listing, selector): el = listing.select_one(selector) return el.get_text(strip=True) if el else None def parse_listing(listing): link_el = listing.select_one('a[data-testid="property-card-link"]') link = "https://www.trulia.com" + link_el["href"] if link_el else None return { "price": text_at(listing, 'div[data-testid="property-price"]'), "address": text_at(listing, 'div[data-testid="property-address"]'), "beds": text_at(listing, 'div[data-testid="property-beds"]'), "baths": text_at(listing, 'div[data-testid="property-baths"]'), "size": text_at(listing, 'div[data-testid="property-floorSpace"]'), "link": link, } def main(): search_url = "https://www.trulia.com/CA/Los_Angeles/" html = crawl(search_url) if not html: return listings = get_listings(html) results = [parse_listing(li) for li in listings] print(json.dumps(results, indent=2)) if __name__ == "__main__": main()
À quoi ressemble la sortie
Exécutez le script complet avec python trulia_scraper.py et vous obtenez une liste propre d'enregistrements structurés, un par listing sur la page, prêts à écrire en JSON, CSV ou dans une base de données.
[ { "price": "$4,750,000", "address": "9240 W National Blvd, Los Angeles, CA 90034", "beds": "9bd", "baths": "9ba", "size": "6,045 sqft", "link": "https://www.trulia.com/p/ca/los-angeles/..." }, { "price": "$1,499,999", "address": "245 Windward Ave, Venice, CA 90291", "beds": "4bd", "baths": "3ba", "size": "1,332 sqft", "link": "https://www.trulia.com/p/ca/venice/..." } ]
Les listings avec des données manquantes reviennent avec null dans ces champs plutôt que d'échouer, c'est pourquoi un listing terrain ou en pré-construction peut ne montrer ni chambres, ni salles de bain, ni superficie. C'est attendu, et le code en aval doit traiter tout champ comme optionnel.
Gérer la pagination et exporter les données
Une page est une démo ; un vrai travail s'étend sur toute une ville. Trulia pagine ses résultats de recherche avec un schéma basé sur le chemin : il ajoute un segment de page séquentiel à l'URL de recherche, donc la première page est /1_p/, la deuxième est /2_p/, et ainsi de suite. Itérer ce nombre parcourt l'ensemble des résultats, et vous réutilisez les mêmes fonctions crawl et d'analyse sur chaque page.
import json import time import pandas as pd def scrape_pages(base_url, num_pages): results = [] for page in range(1, num_pages + 1): page_url = f"{base_url}/{page}_p/" html = crawl(page_url) if not html: print(f"Skipping page {page}: no HTML.") continue listings = get_listings(html) if not listings: break results.extend(parse_listing(li) for li in listings) time.sleep(2) return results def export(results): with open("trulia_listings.json", "w") as f: json.dump(results, f, indent=2) pd.DataFrame(results).to_csv("trulia_listings.csv", index=False) print(f"Saved {len(results)} listings to JSON and CSV.") if __name__ == "__main__": base = "https://www.trulia.com/CA/Los_Angeles" data = scrape_pages(base, num_pages=3) export(data)
Le time.sleep(2) entre les pages est délibéré : il cadence l'exécution pour que vous ne marteliez pas le site, ce qui est l'habitude la plus efficace pour rester non bloqué. La boucle s'arrête aussi tôt si une page ne retourne pas de cartes, pour que vous ne demandiez jamais au-delà de la dernière page de résultats. La fonction export écrit à la fois trulia_listings.json et trulia_listings.csv ; pandas transforme la liste de dicts en tableau plat où chaque champ devient une colonne. Ajustez le nombre de pages et le segment de ville dans base selon votre marché cible.
Rester non bloqué
Même avec le rendu géré, Trulia surveille le trafic ayant la forme d'un scraper. Quelques habitudes maintiennent une exécution saine, et elles s'appliquent à toute cible commerciale difficile.
-
Cadencez vos requêtes. Marteler les pages en boucle serrée est le moyen le plus rapide d'être limité en débit ou de recevoir un captcha. Étalez les requêtes, comme le
sleepci-dessus le fait, et évitez 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 pour qu'aucune seule ne déclenche une limite de débit. La Crawling API gère cela pour vous ; si vous construisez votre propre stack, c'est la partie à maîtriser.
-
Lisez les codes de statut. Une exécution qui commence à retourner des challenges ou des valeurs
cb_statusnon-200 vous indique que le débit ou le niveau d'IP actuels ne sont plus suffisants. Traitez cela comme un signal de reculer, pas du bruit à ignorer.
Pour le guide complet, consultez comment scraper des sites web sans se faire bloquer. Si vos sites cibles s'appuient fortement sur JavaScript, le guide sur le crawling de sites JavaScript couvre le côté rendu plus en profondeur.
Est-il légal de scraper Trulia ?
La légalité du scraping de Trulia dépend des conditions d'utilisation de Trulia, de votre juridiction et de ce que vous faites des données. Les conditions de Trulia restreignent l'accès automatisé, donc le scraping peut aller à l'encontre de ces conditions quel que soit le soin apporté à vos outils. Aucun code ici ne change cela ; il rend simplement la partie technique fonctionnelle. Lisez les Conditions générales de Trulia et son fichier robots.txt, respectez ses attentes de débit, et traitez les deux comme la frontière de ce que vous collectez.
Quelques règles à respecter. Collectez uniquement les données de listing de propriétés publiques : le prix, l'adresse, les chambres, les salles de bain, la superficie et le lien de listing que quiconque peut voir sans compte. Évitez tout ce qui est lié à des individus identifiables, y compris les coordonnées des agents, courtiers ou propriétaires affichées sur une carte, qui tombent hors du périmètre de listing public couvert par ce guide. Un détail spécifique à l'immobilier mérite d'être signalé : une grande partie des données de propriétés sous-jacentes sur des sites comme Trulia provient de flux MLS (Multiple Listing Service), qui sont généralement licenciés et portent leurs propres restrictions d'utilisation. Republier ces données en masse peut enfreindre ces licences même quand la page elle-même est publique.
Ce guide est délibérément limité aux pages de recherche et de listing publiques parce que c'est la ligne qui rend le travail défendable. Il ne couvre pas ce qui est derrière une connexion, les données de recherche sauvegardée ou de compte, les coordonnées personnelles d'individus, ni aucune tentative de contourner l'authentification. Si votre projet a besoin de plus que des champs de listing publics, la bonne voie est un flux de données immobilières licencié ou un accord officiel, pas un scraper plus sophistiqué. Lorsqu'un site propose une API officielle ou un partenariat de données, préférez-le ; il vous donne des données plus propres et une licence claire en même temps.
Points clés
- Trulia est rendu côté client. Une requête simple retourne une coquille vide, donc vous devez rendre la page avant de l'analyser.
-
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 le temps d'attente pour que les cartes se chargent. -
Ciblez les attributs stables. Les valeurs
data-testidde Trulia (property-price,property-address,property-beds,property-baths,property-floorSpace) pilotent l'extraction par champ, avec chaque carte délimitée dans unli. -
Paginez par chemin et exportez les deux formats. Trulia utilise des segments de page
/N_p/; bouclez-les, analysez chaque carte et écrivez le résultat en JSON et CSV avec pandas. - Restez sur les données publiques. Respectez les CGU et le robots.txt de Trulia, ne collectez que les champs de listing publics, faites attention que les données MLS sont souvent licenciées, et ne touchez jamais aux comptes, connexions ou coordonnées personnelles d'individus.
Foire aux questions
Pourquoi une simple requête ne retourne-t-elle pas de données depuis Trulia ?
Parce que Trulia rend ses résultats de recherche côté client avec JavaScript. Le HTML initial est une coquille 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 bain et adresse vides. Pour obtenir de vraies données vous devez d'abord rendre la page, ce que le token JS de la Crawling API gère pour vous.
Ai-je besoin du token normal ou du token JS pour Trulia ?
Le token JS. Le token normal récupère le HTML statique, qui sur Trulia est la même coquille vide qu'une requête simple retourne. Le token JS rend la page dans un vrai navigateur avant de restituer le HTML, pour que les cartes de listing soient présentes quand BeautifulSoup les analyse.
Quelles données puis-je scraper depuis un listing Trulia ?
Les champs de listing publics : le prix demandé, l'adresse, le nombre de chambres et de salles de bain, la superficie en pieds carrés et le lien vers la page de détail. Restez sur les données visibles par tout visiteur sans compte, et évitez les coordonnées personnelles des agents, courtiers ou propriétaires, qui tombent hors du périmètre de listing public couvert par ce guide.
Comment fonctionne la pagination sur Trulia ?
Trulia utilise un schéma basé sur le chemin, ajoutant un segment de page séquentiel à l'URL de recherche : /1_p/ pour la première page, /2_p/ pour la deuxième, et ainsi de suite. La fonction scrape_pages ci-dessus boucle ce nombre, récupère chaque page via la Crawling API, analyse les cartes et s'arrête quand une page ne retourne pas de listings.
Mes sélecteurs retournent None sur chaque carte. Qu'est-ce qui a changé ?
Très certainement le balisage de Trulia. Les valeurs data-testid que ce scraper cible peuvent changer sans préavis, donc les sélecteurs qui fonctionnaient le mois dernier peuvent se casser. Ré-inspectez un listing live 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 de production.
Comment scraper d'autres sites immobiliers de la même façon ?
Le même pattern s'applique : rendez la page, collectez les cartes de listing et mappez chaque champ public à un sélecteur. Les détails varient par site, consultez donc les guides complémentaires sur comment scraper Zillow et comment scraper Realtor.com, ou le tutoriel Apartments.com axé sur la location, qui réutilisent exactement cette structure de récupération et d'analyse.
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.
