SuperPages est l'un des plus grands annuaires d'entreprises en ligne aux États-Unis, avec des millions de sociétés indexées par secteur et par localisation. Chaque fiche porte le genre de détails publics et structurés que les équipes commerciales et marketing veulent pour une liste de prospects : un nom d'entreprise, la catégorie sous laquelle elle est classée, une adresse de rue, un numéro de téléphone public, et souvent un lien vers le site web de l'entreprise. Pour constituer un jeu de données régional de prestataires de services ou amorcer une campagne de prospection B2B, ces données publiques d'annuaire sont exactement la matière première dont vous avez besoin.

Ce guide vous montre comment scraper SuperPages pour récupérer des fiches d'entreprises avec Python de la façon fiable. Vous récupérez les pages de résultats de recherche rendues via la Crawling API, analysez chaque résultat avec BeautifulSoup pour extraire le nom, la catégorie, l'adresse, le téléphone, le site web et le lien de la page de détail, puis parcourez la pagination pour couvrir un ensemble de résultats complet et exportez les enregistrements en JSON ou CSV. Tout ici reste cantonné aux données publiques d'annuaire d'entreprises, et la section sur la légalité, vers la fin, couvre les obligations qui s'attachent aux données de leads B2B, alors lisez-la avant de pointer ceci vers 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 SuperPages rendue via la Crawling API, et extrait un enregistrement structuré pour chaque entreprise de la page. L'exemple courant est celui des entreprises de "Home Services" à "Los Angeles, CA", et pour chaque fiche nous extrayons ces champs :

  • Nom de l'entreprise l'identifiant principal sur lequel vous regroupez les leads.
  • Catégorie le secteur sous lequel la fiche est classée, utilisé pour segmenter les leads.
  • Adresse l'adresse de rue publique, y compris la ville, l'État et le code postal.
  • Téléphone le numéro de contact public affiché sur la carte de la fiche.
  • Site web le lien vers le site propre de l'entreprise, quand il est indiqué.
  • Lien de la page de détail l'URL de la page SuperPages dédiée à l'entreprise.

Pourquoi une simple requête échoue sur SuperPages

Vous pouvez interroger une URL de recherche SuperPages avec la bibliothèque requests et, un bon jour, récupérer du HTML. Le problème apparaît à grande échelle. SuperPages déploie des défenses anti-scraping : il limite le débit par IP, sert des CAPTCHA au trafic qui paraît automatisé, et bloque les adresses de centre de données qui demandent des pages selon un schéma serré, de type machine. Une seule requête depuis votre portable peut réussir ; quelques centaines depuis la même IP n'y arriveront pas.

Un scraper qui termine réellement le travail a donc besoin de requêtes qui se lisent comme un vrai visiteur venant d'une IP de confiance. Vous pouvez construire cela vous-même avec un pool de proxys résidentiels tournants et la tuyauterie pour les maintenir en bonne santé, mais entretenir cette stack est l'essentiel du travail. La Crawling API le réunit en un seul appel : vous lui envoyez l'URL, elle achemine la requête à travers des IP résidentielles côté serveur et gère la couche anti-bot, et elle renvoie le HTML pour que vous l'analysiez.

Quel token utiliser

Crawlbase propose deux types de tokens. Le token normal récupère du HTML statique ; le token JavaScript (JS) rend d'abord la page dans un vrai navigateur. SuperPages sert ses données de fiche dans le HTML initial, donc le token normal est le bon choix ici et garde chaque requête moins chère. N'optez pour le token JS que si une cible se met à rendre ses fiches côté client.

Prérequis

Quelques éléments à avoir en place d'abord. Aucun ne prend longtemps.

Python de base. Vous devriez être à l'aise pour exécuter un script et installer des paquets avec pip. Si les sélecteurs sont nouveaux pour vous, l'introduction sur comment utiliser BeautifulSoup en Python couvre le côté analyse en profondeur.

Python 3.8 ou ultérieur. Confirmez 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 normal depuis la page de documentation du compte. Vous obtenez jusqu'à 5 000 requêtes gratuites et aucune carte n'est requise. Traitez le token comme un mot de passe et gardez-le hors du contrôle de version.

Configurer le projet

Créez un environnement virtuel pour que les dépendances restent isolées, puis installez les deux bibliothèques dont le scraper a besoin.

bash
python --version

python -m venv superpages_env
source superpages_env/bin/activate

pip install crawlbase beautifulsoup4

Sous Windows, activez l'environnement avec superpages_env\Scripts\activate au lieu de la ligne source. Deux dépendances font le travail : crawlbase est le client officiel de la Crawling API, et beautifulsoup4 analyse le HTML renvoyé pour que vous puissiez extraire chaque champ par sélecteur CSS.

Étape 1 : récupérer une page de recherche rendue

Commencez par récupérer une page de résultats. Construisez l'URL de recherche à partir de votre requête et de votre localisation, importez la classe CrawlingAPI, initialisez-la avec votre token et demandez l'URL. Vérifier le statut avant d'analyser rend les échecs bruyants plutôt que silencieux.

python
from urllib.parse import urlencode
from crawlbase import CrawlingAPI

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})

def build_url(query, location, page=1):
    base = "https://www.superpages.com/search?"
    params = {"search_terms": query, "geo_location_terms": location, "page": page}
    return base + urlencode(params)

def crawl(page_url):
    response = api.get(page_url)
    if response["headers"]["cb_status"] == "200":
        return response["body"].decode("utf-8")
    print(f"Request failed: {response['headers']['cb_status']}")
    return None

if __name__ == "__main__":
    url = build_url("Home Services", "Los Angeles, CA")
    html = crawl(url)
    print(html[:500] if html else "No HTML returned")

Notez que la vérification de statut lit cb_status (legacy pc_status) dans les en-têtes de réponse, qui est le statut Crawlbase de la requête, distinct du code HTTP en amont. Exécutez le script avec python scraper.py et vous devriez voir un vrai balisage de résultats plutôt qu'une page de défi. Cela confirme que le chemin de récupération fonctionne avant que vous n'écriviez un seul sélecteur.

Crawlbase Crawling API

SuperPages limite le débit par IP et défie le trafic en forme de scraper, exactement la friction que vous venez de voir motiver l'étape de récupération. La Crawling API achemine chaque requête à travers des IP résidentielles tournantes côté serveur, gère les CAPTCHA et les blocages, et vous remet du HTML prêt à analyser, vous évitant ainsi de gérer une flotte de navigateurs 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 : analyser les fiches avec BeautifulSoup

Une fois une page de résultats en main, chargez-la dans BeautifulSoup et parcourez les cartes de résultats. Chaque entreprise est une carte autonome sous un conteneur prévisible, et à l'intérieur le nom, l'adresse, le téléphone, le site web et le lien de la page de détail correspondent à leurs propres sélecteurs. Lire chaque champ de manière défensive, en renvoyant une chaîne vide quand un élément manque, empêche une seule valeur absente de planter l'exécution.

python
from bs4 import BeautifulSoup

BASE = "https://www.superpages.com"

def extract_listings(html):
    soup = BeautifulSoup(html, "html.parser")
    listings = []

    for business in soup.select("div.search-results > div.result"):
        name_el = business.select_one("a.business-name span")
        category_el = business.select_one("div.categories")
        address_el = business.select_one("span.street-address")
        phone_el = business.select_one("a.phone.primary")
        website_el = business.select_one("a.weblink-button")
        link_el = business.select_one("a.business-name")

        listings.append({
            "name": name_el.text.strip() if name_el else "",
            "category": category_el.text.strip() if category_el else "",
            "address": address_el.text.strip() if address_el else "",
            "phone": phone_el.text.strip() if phone_el else "",
            "website": website_el["href"] if website_el else "",
            "detail_page_link": BASE + link_el["href"] if link_el else "",
        })

    return listings

Les sélecteurs viennent directement du balisage des cartes SuperPages : le nom de l'entreprise se trouve dans un span à l'intérieur d'une ancre a.business-name, l'adresse dans span.street-address, le téléphone dans a.phone.primary, et le site web sortant dans a.weblink-button. Le lien de la page de détail réutilise la même ancre a.business-name et est un chemin relatif, il est donc préfixé par l'hôte BASE pour former une URL complète. Chaque champ est protégé par un if ... else "" pour qu'un élément manquant laisse une chaîne vide dans l'enregistrement au lieu de lever une erreur.

Les sélecteurs dérivent

Les noms de classes ci-dessus (result, business-name, street-address, phone primary, weblink-button) reflètent le balisage SuperPages actuel, et ce balisage change sans préavis. Traitez les sélecteurs comme un modèle de départ, pas comme un contrat. Quand un champ revient vide sur chaque fiche, ré-inspectez une page de résultats 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 de production.

Étape 3 : gérer la pagination sur les pages de résultats

Une page est une démo ; une vraie liste de leads couvre l'ensemble complet des résultats. SuperPages expose la page de résultats via le paramètre d'URL page, donc parcourir les pages est une boucle sur une plage d'entiers. Les mêmes fonctions build_url et extract_listings se reportent sans changement, donc la pagination n'est qu'une boucle externe qui se cadence entre les requêtes.

python
import time

def scrape_all_pages(query, location, max_pages):
    all_listings = []
    for page in range(1, max_pages + 1):
        print(f"Scraping page {page}...")
        url = build_url(query, location, page)
        html = crawl(url)
        if not html:
            print(f"Stopping at page {page}: no HTML")
            break
        listings = extract_listings(html)
        if not listings:
            print(f"No results on page {page}; reached the end")
            break
        all_listings.extend(listings)
        time.sleep(2)
    return all_listings

Deux détails rendent cette boucle adaptée à la production. Elle s'arrête tôt quand une page ne renvoie aucune fiche, pour que vous ne gaspilliez pas de requêtes au-delà de la dernière page réelle, et elle patiente deux secondes entre les requêtes pour que l'exécution n'arrive pas comme une seule rafale serrée. Ajustez max_pages et la pause à votre volume ; plus vous allez lentement, moins vous attirez l'attention.

Étape 4 : tout assembler et exporter

Maintenant, câblez la récupération, l'analyse et la pagination en un seul script exécutable, puis écrivez les enregistrements à la fois dans un fichier JSON et un CSV pour que la liste de leads tombe directement dans un tableur ou un import CRM.

python
import csv
import json
import time
from urllib.parse import urlencode
from crawlbase import CrawlingAPI
from bs4 import BeautifulSoup

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})
BASE = "https://www.superpages.com"
FIELDS = ["name", "category", "address", "phone", "website", "detail_page_link"]

def build_url(query, location, page=1):
    base = "https://www.superpages.com/search?"
    params = {"search_terms": query, "geo_location_terms": location, "page": page}
    return base + urlencode(params)

def crawl(page_url):
    response = api.get(page_url)
    if response["headers"]["cb_status"] == "200":
        return response["body"].decode("utf-8")
    print(f"Request failed: {response['headers']['cb_status']}")
    return None

def extract_listings(html):
    soup = BeautifulSoup(html, "html.parser")
    listings = []
    for business in soup.select("div.search-results > div.result"):
        name_el = business.select_one("a.business-name span")
        category_el = business.select_one("div.categories")
        address_el = business.select_one("span.street-address")
        phone_el = business.select_one("a.phone.primary")
        website_el = business.select_one("a.weblink-button")
        link_el = business.select_one("a.business-name")
        listings.append({
            "name": name_el.text.strip() if name_el else "",
            "category": category_el.text.strip() if category_el else "",
            "address": address_el.text.strip() if address_el else "",
            "phone": phone_el.text.strip() if phone_el else "",
            "website": website_el["href"] if website_el else "",
            "detail_page_link": BASE + link_el["href"] if link_el else "",
        })
    return listings

def scrape_all_pages(query, location, max_pages):
    all_listings = []
    for page in range(1, max_pages + 1):
        print(f"Scraping page {page}...")
        html = crawl(build_url(query, location, page))
        if not html:
            break
        listings = extract_listings(html)
        if not listings:
            break
        all_listings.extend(listings)
        time.sleep(2)
    return all_listings

def save_json(data, filename="superpages_listings.json"):
    with open(filename, "w") as f:
        json.dump(data, f, indent=4)

def save_csv(data, filename="superpages_listings.csv"):
    with open(filename, "w", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=FIELDS)
        writer.writeheader()
        writer.writerows(data)

def main():
    rows = scrape_all_pages("Home Services", "Los Angeles, CA", max_pages=5)
    save_json(rows)
    save_csv(rows)
    print(f"Saved {len(rows)} listings")

if __name__ == "__main__":
    main()

Comme chaque enregistrement partage les six mêmes clés, les colonnes CSV s'alignent proprement et csv.DictWriter les écrit sans aucun mappage supplémentaire. Échangez la requête et la localisation en bas pour cibler un autre secteur ou une autre ville, et augmentez max_pages quand vous voulez un balayage plus profond d'une recherche.

À quoi ressemble la sortie

Exécutez le script complet avec python scraper.py et vous obtenez une liste propre d'enregistrements structurés, prêts à écrire en JSON, CSV ou dans une base de données. Le fichier JSON ressemble à ceci :

json
[
  {
    "name": "Evergreen Cleaning Systems",
    "category": "House Cleaning",
    "address": "3325 Wilshire Blvd Ste 622, Los Angeles, CA 90010",
    "phone": "213-375-1597",
    "website": "https://www.evergreencleaningsystems.com",
    "detail_page_link": "https://www.superpages.com/los-angeles-ca/bpp/evergreen-cleaning-systems-540709574"
  },
  {
    "name": "Any Day Anytime Cleaning Service",
    "category": "House Cleaning",
    "address": "27612 Cherry Creek Dr, Santa Clarita, CA 91354",
    "phone": "661-297-2702",
    "website": "",
    "detail_page_link": "https://www.superpages.com/santa-clarita-ca/bpp/any-day-anytime-cleaning-service-513720439"
  }
]

Les fiches sans site web revendiqué reviennent avec "website": "", ce qui est attendu et précisément pourquoi l'analyseur lit chaque champ de manière défensive plutôt que de supposer que chaque clé est présente. À partir d'ici, les données sont prêtes pour la déduplication, l'enrichissement ou l'import dans votre outillage de prospection. Pour le flux de travail plus large autour de la transformation de ces enregistrements en campagne, voir le guide sur le web crawling pour la génération de leads.

Passer à plus de requêtes et de localisations

Une seule recherche couvre un secteur dans une ville. Un vrai jeu de données de prospection en croise généralement plusieurs des deux, donc l'étape suivante naturelle est de piloter le scraper à partir d'une liste de paires requête-localisation plutôt que de chaînes codées en dur.

python
searches = [
    ("Home Services", "Los Angeles, CA"),
    ("Plumbers", "San Diego, CA"),
    ("Electricians", "Phoenix, AZ"),
]

all_rows = []
for query, location in searches:
    all_rows.extend(scrape_all_pages(query, location, max_pages=3))

save_json(all_rows)
save_csv(all_rows)

L'appel extend ajoute chaque recherche dans une seule liste plate, donc l'étape d'export reste inchangée. Quand la matrice de requêtes et de localisations devient grande, déplacez le travail hors d'une seule boucle synchrone vers une file d'attente. Le Crawler asynchrone prend des URL en masse et renvoie les résultats au fur et à mesure qu'ils se terminent, ce qui convient mieux que de bloquer sur chaque page une fois que vous scrapez des milliers de recherches.

Rester non bloqué

Même avec la Crawling API qui gère la rotation d'IP et la couche anti-bot, quelques habitudes maintiennent une exécution en bonne santé, et elles s'appliquent à toute cible d'annuaire.

  • Cadencez vos requêtes. La pause de deux secondes n'est pas cosmétique. Une boucle serrée est le moyen le plus rapide de se faire limiter ; étaler les requêtes se lit bien plus comme du trafic normal.
  • 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 le fait pour vous ; si vous construisez 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 actuel est trop agressif. Traitez cela comme un signal pour lever le pied, pas comme du bruit à ignorer.

Pour le manuel plus large, voir comment scraper des sites web sans se faire bloquer. Si vous voulez la même approche appliquée à un annuaire voisin, les présentations sur le scraping de Yellow Pages et le scraping de fiches d'entreprises locales suivent la même forme récupérer-analyser-paginer avec des sélecteurs différents.

Est-il légal de scraper SuperPages ?

Que le scraping de SuperPages soit autorisé dépend des conditions d'utilisation du site, de votre juridiction et de ce que vous faites des données. Aucun des codes présentés ici ne change cela ; il fait seulement fonctionner la partie technique. Lisez les Conditions d'utilisation de SuperPages et son robots.txt, et traitez les deux comme la frontière de ce que vous collectez et à quelle vitesse. Tout dans ce guide est cantonné aux données publiques d'annuaire d'entreprises B2B : un nom de société, sa catégorie, une adresse de rue publique, un numéro de téléphone public et un lien vers son propre site web. C'est de l'information que tout visiteur peut voir sans se connecter, et elle décrit des entreprises plutôt que des particuliers.

Le poids juridique se déplace quand vous agissez sur ces données. Les coordonnées professionnelles restent soumises au droit de la vie privée et au droit anti-spam dans de nombreuses régions. Sous le RGPD, un contact nommé dans une petite entreprise peut compter comme une donnée personnelle, vous avez donc besoin d'une base légale pour le stocker et le traiter, et les personnes conservent le droit de s'opposer et d'être supprimées. Aux États-Unis, le CAN-SPAM Act régit le courrier électronique commercial : vous devez vous identifier honnêtement, éviter les lignes d'objet trompeuses et honorer rapidement les demandes de désinscription. Les règles de démarchage téléphonique et les registres do-not-call s'appliquent à la prospection téléphonique dans le même esprit. Collecter les données est une chose ; les utiliser pour de la prospection est là où ces obligations mordent, alors intégrez dès le départ la gestion des désinscriptions et les listes de suppression plutôt que de les rajouter plus tard.

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 ni aucun contrôle d'accès pour atteindre du contenu protégé ; cela est hors champ ici et va à l'encontre des conditions du site. Si SuperPages propose une API officielle ou un flux de données sous licence pour le volume dont vous avez besoin, préférez-le : une source autorisée supprime entièrement l'ambiguïté. En cas de doute sur un usage commercial d'un jeu de données de contacts agrégé, vérifiez les règles qui s'appliquent à vous plutôt que de supposer que public veut dire sans restriction.

Récapitulatif

Points clés

  • SuperPages est un annuaire B2B structuré. Chaque résultat de recherche est une carte avec un nom d'entreprise, une catégorie, une adresse publique, un téléphone public, un site web facultatif et un lien de page de détail, piloté par les paramètres d'URL search_terms et geo_location_terms.
  • Une simple récupération peine à grande échelle. Les limites de débit, les CAPTCHA et les blocages d'IP arrêtent une boucle naïve ; la Crawling API achemine à travers des IP résidentielles et renvoie du HTML prêt à analyser en un seul appel.
  • BeautifulSoup fait l'extraction. Mappez le nom, la catégorie, l'adresse, le téléphone, le site web et le lien aux sélecteurs actuels, lisez chaque champ de manière défensive, et attendez-vous à ce que ces sélecteurs dérivent.
  • La pagination est une boucle sur le paramètre page. Réutilisez le même analyseur sur les pages, arrêtez-vous tôt sur une page vide, faites une pause entre les requêtes, et exportez en JSON et CSV.
  • La prospection licite vous incombe. Les données sont publiques, mais le RGPD et le CAN-SPAM s'appliquent quand même à la façon dont vous contactez ces entreprises, vous avez donc besoin d'une base légale et devez honorer les désinscriptions.

Foire aux questions

Ai-je besoin du token normal ou du token JS pour SuperPages ?

Le token normal. SuperPages sert ses données de fiche dans le HTML initial, donc une récupération avec le token normal renvoie un balisage analysable et garde chaque requête moins chère. Le token JS rend d'abord la page dans un vrai navigateur, ce dont vous n'avez besoin que lorsqu'une cible charge ses fiches côté client après l'arrivée de la page. Commencez par le token normal et ne basculez que si les champs reviennent vides de bout en bout.

Comment gérer la pagination sur SuperPages ?

SuperPages expose la page de résultats via un paramètre d'URL page, donc vous bouclez sur une plage d'entiers, construisez une URL par page et exécutez le même analyseur sur chacune. Arrêtez-vous quand une page renvoie zéro fiche, ce qui marque la fin de l'ensemble de résultats, et patientez quelques secondes entre les requêtes pour que l'exécution n'arrive pas comme une seule rafale.

Mes sélecteurs renvoient des valeurs vides. Qu'est-ce qui a changé ?

Presque certainement le balisage SuperPages. Des noms de classes comme result, business-name, street-address et weblink-button changent sans préavis, donc des sélecteurs qui marchaient le mois dernier peuvent casser. Ré-inspectez une page de résultats 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 de production, pas le signe que quelque chose est cassé.

Comment exporter les leads vers CSV ou Excel ?

Le scraper écrit déjà un CSV avec csv.DictWriter, puisque chaque enregistrement partage les mêmes clés. Pour Excel, pandas transforme la même liste de dictionnaires en un tableur en deux lignes : pd.DataFrame(rows).to_excel("superpages_listings.xlsx", index=False). Les colonnes s'alignent proprement car l'ensemble de champs est fixe.

Puis-je aussi scraper les pages de détail individuelles des entreprises ?

Oui. Chaque enregistrement porte un detail_page_link, vous pouvez donc renvoyer ces URL à travers la même fonction crawl et analyser la page dédiée pour des champs supplémentaires comme les horaires d'ouverture ou un bloc de contact plus long. Cadencez ce second passage de la même façon, puisqu'il double votre nombre de requêtes, et gardez-le cantonné aux informations publiques de l'entreprise sur la page.

Comment garder ma prospection conforme ?

Traitez la liste scrapée comme un point de départ, pas comme un feu vert. Confirmez que vous avez une base légale pour contacter chaque entreprise selon les règles de votre région, identifiez-vous honnêtement dans chaque message, et câblez la gestion des désinscriptions et une liste de suppression dans votre pipeline d'envoi dès le premier jour. Les obligations du RGPD et du CAN-SPAM s'attachent à la prospection, pas à la collecte, donc le travail de conformité réside dans la façon dont vous utilisez les données.

Commencer à construire

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.

En libre-service · Sans appel commercial requis · Volumes de crawl entreprise disponibles