Yelp est l'une des sources les plus denses de données publiques sur les entreprises locales sur le web. Chaque résultat de recherche porte un nom d'entreprise, une note en étoiles, un nombre d'avis, les catégories dans lesquelles elle est répertoriée, son quartier ou adresse, et un lien vers sa propre page Yelp. Pour la recherche sur les entreprises locales, la cartographie concurrentielle ou la constitution d'un jeu de données régional de prestataires de services, ces champs d'annonces publiques constituent exactement le signal structuré que vous cherchez, tous visibles sans se connecter.
Ce guide vous montre comment scraper Yelp avec Python de manière fiable. Vous récupérez les pages de résultats de recherche rendues via la Crawling API, analysez chaque fiche d'entreprise avec BeautifulSoup pour extraire le nom, la note, le nombre d'avis, la catégorie, l'adresse et le lien, puis parcourez la pagination pour couvrir un jeu de résultats complet et exportez en JSON ou CSV. Tout ce qui suit se limite aux données d'entreprises publiques, et la section sur la légalité en fin d'article n'est pas du remplissage, lisez-la avant de pointer cet outil sur un volume réel.
Ce que vous allez construire
Un petit scraper Python qui prend une requête de recherche et une localisation, récupère la page de résultats de recherche Yelp rendue via la Crawling API et extrait un enregistrement structuré pour chaque entreprise sur la page. L'exemple fil rouge concerne les "Italian Restaurants" à "San Francisco, CA", et pour chaque annonce nous extrayons ces champs :
- Nom de l'entreprise l'identifiant principal affiché sur la fiche.
- Note la note en étoiles agrégée de l'entreprise.
- Nombre d'avis le nombre d'avis soutenant cette note.
- Catégorie les catégories professionnelles dans lesquelles l'annonce est répertoriée.
- Adresse le quartier ou la rue publique, utilisé pour toute analyse géographique.
- Lien l'URL vers la propre page Yelp de l'entreprise.
Pourquoi une requête ordinaire échoue sur Yelp
Vous pouvez pointer la bibliothèque requests sur une URL de recherche Yelp et, les bons jours, obtenir du HTML en retour. Deux problèmes apparaissent rapidement. D'abord, Yelp rend une grande partie de son contenu de résultats de recherche avec JavaScript, donc le HTML brut qu'une simple requête reçoit est souvent une coquille qui ne contient pas encore les fiches d'entreprises que vous cherchez. Ensuite, Yelp surveille le trafic ayant la forme d'un scraper : il limite le débit par IP, sert des CAPTCHA aux requêtes qui semblent automatisées et bloque les adresses de datacenter qui récupèrent des pages dans un schéma machine serré. Une seule requête depuis votre ordinateur peut réussir ; quelques centaines depuis la même IP ne le peuvent pas.
Un scraper qui achève réellement le travail a donc besoin de deux choses : la page rendue comme un vrai navigateur la rendrait, et des requêtes qui semblent être celles d'un vrai visiteur venant d'une IP de confiance. Vous pouvez construire cela vous-même avec une flotte de navigateurs sans tête et un pool de proxies résidentiels rotatifs, mais maintenir cette pile représente la majeure partie du travail. La Crawling API regroupe tout en un seul appel : vous lui envoyez l'URL, elle rend le JavaScript et achemine la requête via des IP résidentielles côté serveur, gère la couche anti-bot et retourne le HTML final à 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. Comme Yelp construit ses résultats de recherche côté client, utilisez le token JS ici pour que les fiches d'entreprises soient présentes dans le HTML que vous récupérez. Le token normal est le bon choix uniquement pour les cibles qui livrent leurs données dans la réponse initiale.
Prérequis
Quelques éléments à mettre en place au préalable. Aucun ne prend beaucoup de temps.
Python de base. Vous devez être à l'aise pour exécuter un script et installer des packages avec pip. Si les sélecteurs sont nouveaux pour vous, le guide sur l'utilisation de BeautifulSoup en Python couvre en détail la partie analyse.
Python 3.8 ou version ultérieure. Vérifiez 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. Inscrivez-vous, ouvrez votre tableau de bord et copiez votre token JavaScript depuis la page de documentation du compte. Vous obtenez jusqu'à 20 000 requêtes gratuites et aucune carte n'est requise. Traitez le token comme un mot de passe et gardez-le en dehors du contrôle de version.
Configurer le projet
Créez un environnement virtuel pour que les dépendances restent isolées, puis installez les bibliothèques dont le scraper a besoin.
python --version python -m venv yelp_env source yelp_env/bin/activate pip install crawlbase beautifulsoup4 pandas
Sous Windows, activez l'environnement avec yelp_env\Scripts\activate à la place de la ligne source. Trois dépendances font le travail : crawlbase est le client officiel de la Crawling API, beautifulsoup4 analyse le HTML retourné pour que vous puissiez extraire chaque champ par sélecteur CSS, et pandas écrit les enregistrements en CSV à la fin.
Étape 1 : Récupérer une page de recherche rendue
Commencez par obtenir une page de résultats en retour. Construisez l'URL de recherche depuis votre requête et localisation, importez la classe CrawlingAPI, initialisez-la avec votre token et demandez l'URL avec le rendu JavaScript activé. Une recherche Yelp est pilotée par deux paramètres URL : find_desc pour la catégorie d'entreprise et find_loc pour la localisation. Vérifier le statut avant d'analyser garde les échecs visibles plutôt que silencieux.
from urllib.parse import urlencode from crawlbase import CrawlingAPI api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) def build_url(query, location, start=0): base = "https://www.yelp.com/search?" params = {"find_desc": query, "find_loc": location, "start": start} return base + urlencode(params) def crawl(page_url): response = api.get(page_url, {"ajax_wait": "true", "page_wait": "3000"}) if response["headers"]["cb_status"] == "200": return response["body"].decode("latin1") print(f"Request failed: {response['headers']['cb_status']}") return None if __name__ == "__main__": url = build_url("Italian Restaurants", "San Francisco, CA") html = crawl(url) print(html[:500] if html else "No HTML returned")
Les options ajax_wait et page_wait indiquent à la Crawling API de rendre JavaScript et de marquer une courte pause pour que les fiches d'entreprises finissent de charger avant que le HTML ne revienne. La vérification de statut lit cb_status (legacy pc_status) depuis les en-têtes de réponse, qui est le statut Crawlbase pour la requête, distinct du code HTTP amont. Exécutez le script avec python yelp_scraper.py et vous devriez voir un vrai balisage de résultats plutôt qu'une page de défi ou une coquille vide. Cela confirme que le chemin de récupération fonctionne avant d'écrire le moindre sélecteur.
Yelp rend ses résultats côté client et défie le trafic ayant la forme d'un scraper. La Crawling API rend le JavaScript dans un vrai navigateur et achemine chaque requête via des IP résidentielles rotatives côté serveur, gère les CAPTCHA et les blocages, et retourne du HTML prêt à analyser, ce qui vous évite de gérer vous-même une flotte de navigateurs sans tête et un pool de proxies. Pointez-la sur une page de recherche publique avec le niveau gratuit d'abord.
Étape 2 : Analyser les annonces avec BeautifulSoup
Une fois la page de résultats rendue en main, chargez-la dans BeautifulSoup et parcourez les fiches de résultats. Chaque entreprise se trouve dans une fiche sous un conteneur prévisible, et à l'intérieur de celui-ci le nom, la note, le nombre d'avis, la catégorie, l'adresse et le lien correspondent à leurs propres sélecteurs. Lire chaque champ de manière défensive, en retournant None quand un élément est manquant, empêche une valeur absente de faire planter l'exécution.
from bs4 import BeautifulSoup BASE = "https://www.yelp.com" def text_of(node): return node.get_text(strip=True) if node else None def extract_business(card): name = card.select_one('div[class*="businessName"] h3 > span > a') rating = card.select_one('div.css-volmcs + div.css-1jq1ouh > span:first-child') reviews = card.select_one('div.css-volmcs + div.css-1jq1ouh > span:last-child') category = card.select('div[class*="priceCategory"] div > p > span:first-child a') address = card.select_one('div[class*="priceCategory"] div > p > span:last-child') return { "name": text_of(name), "rating": text_of(rating), "review_count": text_of(reviews), "category": ", ".join(c.get_text(strip=True) for c in category) if category else None, "address": text_of(address), "link": BASE + name["href"] if name and name.get("href") else None, } def extract_businesses(html): soup = BeautifulSoup(html, "html.parser") cards = soup.select('div[data-testid="serp-ia-card"]:not(.ABP)') return [extract_business(card) for card in cards]
Les fiches sont sélectionnées avec div[data-testid="serp-ia-card"]:not(.ABP), ce qui choisit les fiches de résultats organiques tout en ignorant les variantes d'emplacements publicitaires. L'ancre businessName porte à la fois le nom affiché et le href relatif vers la page Yelp de cette entreprise, donc le nom et le lien proviennent du même élément. La note et le nombre d'avis sont les deux spans qui suivent le bloc de note, et la catégorie et l'adresse se trouvent dans la ligne prix-et-catégorie. L'assistant text_of retourne None quand un élément est absent au lieu de lever une exception sur un appel .get_text() sur rien, ce qui rend l'extraction résiliente lorsqu'une fiche manque un champ.
Les noms de classes de Yelp (les tokens css-* hachés en particulier) sont générés et changent sans préavis, donc traitez ces sélecteurs comme un modèle de départ, pas comme un contrat. Quand un champ revient à None pour chaque annonce, réinspectez une page de résultats en direct dans les outils de développement de votre navigateur et mettez le sélecteur à jour. Les hooks d'attribut data-testid et businessName tendent à être plus stables que les noms de classes hachés, donc préférez-les là où vous le pouvez. Une maintenance périodique des sélecteurs est normale pour tout scraper en production.
Étape 3 : Tout assembler
Reliez maintenant la récupération et l'analyse en un seul script exécutable pour une seule page. Construisez l'URL, récupérez le HTML rendu, passez-le à l'analyseur et affichez les enregistrements structurés en JSON.
import json from urllib.parse import urlencode from crawlbase import CrawlingAPI from bs4 import BeautifulSoup api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) BASE = "https://www.yelp.com" def build_url(query, location, start=0): params = {"find_desc": query, "find_loc": location, "start": start} return BASE + "/search?" + urlencode(params) def crawl(page_url): response = api.get(page_url, {"ajax_wait": "true", "page_wait": "3000"}) if response["headers"]["cb_status"] == "200": return response["body"].decode("latin1") print(f"Request failed: {response['headers']['cb_status']}") return None def text_of(node): return node.get_text(strip=True) if node else None def extract_business(card): name = card.select_one('div[class*="businessName"] h3 > span > a') rating = card.select_one('div.css-volmcs + div.css-1jq1ouh > span:first-child') reviews = card.select_one('div.css-volmcs + div.css-1jq1ouh > span:last-child') category = card.select('div[class*="priceCategory"] div > p > span:first-child a') address = card.select_one('div[class*="priceCategory"] div > p > span:last-child') return { "name": text_of(name), "rating": text_of(rating), "review_count": text_of(reviews), "category": ", ".join(c.get_text(strip=True) for c in category) if category else None, "address": text_of(address), "link": BASE + name["href"] if name and name.get("href") else None, } def extract_businesses(html): soup = BeautifulSoup(html, "html.parser") cards = soup.select('div[data-testid="serp-ia-card"]:not(.ABP)') return [extract_business(card) for card in cards] def main(): url = build_url("Italian Restaurants", "San Francisco, CA") html = crawl(url) if not html: return data = extract_businesses(html) print(json.dumps(data, indent=2)) if __name__ == "__main__": main()
À quoi ressemble la sortie
Exécutez le script complet avec python yelp_scraper.py et vous obtenez une liste propre d'enregistrements structurés, prête à écrire en JSON, CSV ou dans une base de données.
[ { "name": "Bella Trattoria", "rating": "4.3", "review_count": "(1.9k reviews)", "category": "Italian, Bars, Pasta Shops", "address": "Inner Richmond", "link": "https://www.yelp.com/biz/bella-trattoria-san-francisco" }, { "name": "Sotto Mare", "rating": "4.3", "review_count": "(5.2k reviews)", "category": "Seafood, Italian, Bars", "address": "North Beach/Telegraph Hill", "link": "https://www.yelp.com/biz/sotto-mare-san-francisco" } ]
Tout champ qu'une fiche ne porte pas revient comme null, ce qui est attendu et c'est exactement pourquoi l'analyseur lit chaque champ de manière défensive plutôt qu'en supposant que chaque clé est présente. Le nombre d'avis arrive comme une chaîne d'affichage du type "(1.9k reviews)" ; si vous avez besoin d'un entier propre pour l'analyse, supprimez les parenthèses et développez le suffixe k dans une passe de nettoyage ultérieure.
Étape 4 : Gérer la pagination sur plusieurs pages de résultats
Une page est une démonstration ; un vrai travail couvre le jeu de résultats complet. Yelp pagine ses résultats de recherche via le paramètre URL start, qui définit le décalage du premier résultat sur la page et avance par pas de dix. Parcourir les pages est donc une boucle sur une plage de décalages : 0, 10, 20, et ainsi de suite. Les mêmes fonctions build_url et extract_businesses restent inchangées, donc la pagination est simplement une boucle externe qui se cadence entre les requêtes et écrit le résultat combiné en JSON et CSV.
import json import time import pandas as pd def scrape_all_pages(query, location, max_pages=5): all_rows = [] for page in range(max_pages): start = page * 10 url = build_url(query, location, start) html = crawl(url) if not html: print(f"Stopping at offset {start}: no HTML") break rows = extract_businesses(html) if not rows: print(f"No results at offset {start}; reached the end") break all_rows.extend(rows) print(f"Offset {start}: {len(rows)} businesses") time.sleep(2) return all_rows if __name__ == "__main__": rows = scrape_all_pages("Italian Restaurants", "San Francisco, CA", max_pages=5) with open("yelp_businesses.json", "w") as f: json.dump(rows, f, indent=2) pd.DataFrame(rows).to_csv("yelp_businesses.csv", index=False) print(f"Saved {len(rows)} businesses to JSON and CSV")
Deux détails rendent cette boucle utilisable en production. Elle s'arrête tôt lorsqu'une page ne retourne aucune entreprise, ce qui évite de gaspiller des requêtes au-delà de la dernière vraie page, et elle attend deux secondes entre les requêtes pour que l'exécution n'arrive pas comme une seule rafale serrée. L'étape d'export écrit les deux formats depuis la même liste de dictionnaires : json.dump pour un fichier structuré et pandas pour un CSV qui s'ouvre directement dans un tableur. Ajustez max_pages et le délai en fonction de votre volume ; plus vous allez lentement, moins vous attirez l'attention.
Rester non bloqué
Même avec la Crawling API gérant le rendu, la rotation des IP et la couche anti-bot, quelques habitudes maintiennent une exécution en bonne santé, et elles s'appliquent à toute cible de répertoire local.
- Cadencez vos requêtes. Le délai ci-dessus n'est pas cosmétique. Une boucle serrée est le moyen le plus rapide de se faire ralentir ; espacer les requêtes ressemble bien plus à du trafic normal.
- Misez 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 seule ne déclenche une limite de débit. La Crawling API fait cela pour vous ; si vous construisez votre propre pile, c'est la partie à bien maîtriser.
- Lisez les codes de statut. Une exécution qui commence à retourner des défis ou des erreurs vous signale que le débit actuel est trop agressif. Traitez cela comme un signal pour ralentir, pas comme du bruit à ignorer.
Pour le guide complet, consultez comment scraper des sites web sans être bloqué. Si votre objectif est la catégorie plus large des répertoires d'entreprises locales plutôt que Yelp spécifiquement, le guide sur le scraping des annonces d'entreprises locales et le tutoriel Pages Jaunes couvrent des sources similaires avec la même approche. Et si vous voulez le texte des avis derrière chaque entreprise plutôt que le résumé des résultats de recherche, consultez comment crawler les avis Yelp, en gardant à l'esprit les notes de confidentialité ci-dessous.
Est-il légal de scraper Yelp ?
La légitimité du scraping de Yelp dépend des conditions d'utilisation du site, de votre juridiction et de ce que vous faites avec les données. Aucun code ici ne change cela ; il fait seulement fonctionner la partie technique. Lisez les Conditions d'utilisation de Yelp et son robots.txt, et traitez les deux comme la limite de ce que vous collectez et à quelle vitesse. Les conditions de Yelp restreignent l'accès automatisé, donc pour tout ce qui dépasse la recherche à petite échelle, la bonne voie est le propre canal officiel de Yelp : l'API Yelp Fusion offre des données d'entreprises et de recherche dans des conditions que Yelp soutient, ce qui est plus sûr et plus durable que le scraping de l'interface.
Si vous collectez depuis des pages publiques, limitez-vous aux données d'entreprises publiques uniquement : le nom de l'entreprise, la note agrégée, le nombre d'avis, la catégorie, le quartier ou l'adresse publique, et le lien vers l'annonce. Les avis eux-mêmes méritent une ligne plus nette. Les notes agrégées et les nombres d'avis sont des faits au niveau de l'entreprise, mais le texte d'un avis individuel et le nom de la personne qui l'a rédigé constituent des données personnelles. Traitez les identités des évaluateurs comme des données personnelles : ne construisez pas de profils d'individus, ne republiez pas l'avis d'une personne lié à son nom, et appliquez les obligations RGPD ou CCPA partout où des données personnelles sont concernées.
Ce que cette approche ne couvre pas est tout aussi important. Elle ne touche rien derrière une connexion, et elle ne contourne pas l'authentification ou tout contrôle d'accès pour atteindre du contenu restreint ; c'est hors de portée ici et va à l'encontre des conditions du site. Respectez les attentes de débit déclarées de Yelp, gardez votre volume de requêtes raisonnable pour ne pas solliciter ses serveurs, et si vous prévoyez de stocker, d'enrichir ou de réutiliser commercialement les données Yelp, préférez l'API Fusion et vérifiez les règles qui s'appliquent à vous plutôt que de supposer que public signifie sans restriction.
Points clés
-
Yelp est un répertoire structuré. Chaque résultat de recherche est une fiche avec un nom, une note, un nombre d'avis, une catégorie, une adresse publique et un lien, piloté par les paramètres URL
find_descetfind_loc. - Une simple requête échoue doublement. Yelp rend les résultats côté client et bloque le trafic ayant la forme d'un scraper ; la Crawling API rend le JavaScript, route via des IP résidentielles et retourne du HTML prêt à analyser en un seul appel.
-
BeautifulSoup fait l'extraction. Associez nom, note, nombre d'avis, catégorie, adresse et lien aux sélecteurs actuels, lisez chaque champ de manière défensive et attendez-vous à ce que les noms de classes
css-*hachés évoluent. -
La pagination est une boucle sur le décalage
start. Avancez par dix, réutilisez le même analyseur, arrêtez-vous tôt sur une page vide, attendez entre les requêtes et exportez en JSON et CSV. - Restez sur les données d'entreprises publiques. Respectez les CGU et le robots.txt, traitez les identités des évaluateurs comme des données personnelles, ne touchez jamais au contenu derrière une connexion et préférez l'API Yelp Fusion officielle pour tout ce qui dépasse la petite recherche.
Foire aux questions
Ai-je besoin du token normal ou du token JS pour Yelp ?
Le token JavaScript. Yelp construit ses fiches de résultats de recherche côté client, donc une récupération avec le token normal retourne souvent une coquille HTML sans les entreprises dedans. Le token JS rend d'abord la page dans un vrai navigateur, ce qui met les fiches dans le HTML que vous analysez. Associez-le aux options ajax_wait et page_wait pour que le contenu ait le temps de se stabiliser avant que la réponse revienne.
Comment gérer la pagination sur Yelp ?
Yelp expose le décalage des résultats via un paramètre URL start qui avance par pas de dix, donc vous bouclez sur une plage de décalages (0, 10, 20, et ainsi de suite), construisez une URL par page et exécutez le même analyseur sur chacune. Arrêtez-vous quand une page retourne zéro entreprise, ce qui marque la fin du jeu de résultats, et attendez quelques secondes entre les requêtes pour que l'exécution n'arrive pas comme une seule rafale.
Mes sélecteurs retournent None. Qu'est-ce qui a changé ?
Presque certainement le balisage de Yelp. Les noms de classes hachés comme css-volmcs et css-1jq1ouh sont générés et changent sans préavis, de sorte que les sélecteurs qui fonctionnaient le mois dernier peuvent ne plus marcher. Réinspectez une page de résultats en direct dans les outils de développement de votre navigateur et mettez-les à jour, et préférez les hooks data-testid et businessName plus stables là où vous le pouvez. Une maintenance périodique des sélecteurs est normale pour tout scraper en production.
Est-il légal de scraper les avis Yelp ?
Les notes agrégées et les nombres d'avis sont des faits au niveau de l'entreprise que vous pouvez utiliser pour l'analyse, mais le texte d'un avis individuel et le nom de l'évaluateur constituent des données personnelles. Ne construisez pas de profils d'individus et ne republiez pas l'avis d'une personne lié à son identité, et appliquez le RGPD ou le CCPA là où des données personnelles sont impliquées. Pour les données d'avis à toute échelle, l'API Yelp Fusion est la voie que Yelp soutient, et c'est le choix plus sûr que le scraping des pages publiques.
Comment éviter d'être bloqué en scrapant Yelp ?
Gardez un faible taux de requêtes par IP, cadencez les requêtes avec un délai et routez via des IP résidentielles rotatives pour qu'aucune seule adresse ne déclenche une limite de débit. La Crawling API gère le rendu, la rotation et la couche anti-bot pour vous ; si vous construisez votre propre pile, c'est la partie dans laquelle investir. Surveillez les codes de statut et ralentissez dès que vous commencez à voir des défis.
Puis-je exporter les données scrapées vers Excel ?
Oui. Le scraper produit une liste de dictionnaires, que pandas transforme en tableur en deux lignes : pd.DataFrame(rows).to_excel("yelp_businesses.xlsx", index=False). Comme chaque enregistrement partage les mêmes clés, les colonnes s'alignent proprement, et la même structure s'exporte tout aussi facilement vers le CSV que le script écrit déjà ou vers une table de base de données.
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.
