Google Places API : prospection B2B locale

Google Places API : prospection B2B locale

Pourquoi Google Places API ?

Google Maps référence des millions d'entreprises avec leurs coordonnées. L'API Google Places permet d'extraire ces données de manière structurée pour constituer une base de prospects B2B qualifiés.

Cas d'usage typiques :

  • Trouver tous les restaurants d'une ville pour leur vendre un logiciel de caisse
  • Lister les agences immobilières d'une région pour proposer un service photo
  • Identifier les salons de coiffure pour leur proposer un outil de réservation en ligne

Configuration de l'API

1. Créer un projet Google Cloud

  1. Rendez-vous sur Google Cloud Console
  2. Créez un nouveau projet
  3. Activez l'API Places API (New)
  4. Créez une clé API dans APIs & Services > Credentials

2. Sécuriser votre clé

# Ne jamais committer votre clé dans le code !
export GOOGLE_PLACES_API_KEY="votre-clé-ici"

Restrictions recommandées :

  • Restreindre par API (Places API uniquement)
  • Restreindre par IP ou referrer en production
  • Définir un quota journalier pour éviter les dépassements de budget

3. Tarification

Requête Coût (par appel)
Text Search 0,032 $
Nearby Search 0,032 $
Place Details 0,017 $
Autocomplete 0,00283 $

Google offre 200 $ de crédits mensuels gratuits, ce qui permet environ 6 000 recherches Text Search par mois.

Recherche d'entreprises par type et localisation

Text Search (recommandé)

import requests
import json

API_KEY = "VOTRE_CLE_API"
BASE_URL = "https://places.googleapis.com/v1/places:searchText"

def rechercher_entreprises(requete: str, latitude: float, longitude: float, rayon_m: int = 5000) -> list[dict]:
    """
    Recherche des entreprises via Google Places Text Search.
    
    Args:
        requete: ex. "restaurants italiens"
        latitude: latitude du centre de recherche
        longitude: longitude du centre de recherche
        rayon_m: rayon de recherche en mètres (max 50000)
    """
    headers = {
        "Content-Type": "application/json",
        "X-Goog-Api-Key": API_KEY,
        "X-Goog-FieldMask": "places.displayName,places.formattedAddress,places.nationalPhoneNumber,places.websiteUri,places.googleMapsUri,places.rating,places.userRatingCount,places.businessStatus"
    }
    
    payload = {
        "textQuery": requete,
        "locationBias": {
            "circle": {
                "center": {"latitude": latitude, "longitude": longitude},
                "radius": rayon_m
            }
        },
        "languageCode": "fr"
    }
    
    response = requests.post(BASE_URL, headers=headers, json=payload)
    data = response.json()
    
    return data.get("places", [])

# Exemple : restaurants à Lyon
resultats = rechercher_entreprises("restaurants", 45.7640, 4.8357, 10000)

for place in resultats:
    nom = place.get("displayName", {}).get("text", "N/A")
    adresse = place.get("formattedAddress", "N/A")
    tel = place.get("nationalPhoneNumber", "N/A")
    site = place.get("websiteUri", "N/A")
    note = place.get("rating", "N/A")
    
    print(f"{nom} | {adresse} | {tel} | {site} | Note: {note}")

Pagination avec nextPageToken

L'API retourne 20 résultats par page. Pour en obtenir plus :

def rechercher_toutes_entreprises(requete: str, lat: float, lng: float, rayon_m: int = 5000, max_pages: int = 3) -> list[dict]:
    """Recherche paginée pour obtenir jusqu'à 60 résultats."""
    tous_resultats = []
    page_token = None
    
    for page in range(max_pages):
        headers = {
            "Content-Type": "application/json",
            "X-Goog-Api-Key": API_KEY,
            "X-Goog-FieldMask": "places.displayName,places.formattedAddress,places.nationalPhoneNumber,places.websiteUri,places.rating,nextPageToken"
        }
        
        payload = {
            "textQuery": requete,
            "locationBias": {
                "circle": {
                    "center": {"latitude": lat, "longitude": lng},
                    "radius": rayon_m
                }
            },
            "languageCode": "fr",
            "pageSize": 20
        }
        
        if page_token:
            payload["pageToken"] = page_token
        
        response = requests.post(BASE_URL, headers=headers, json=payload)
        data = response.json()
        
        resultats = data.get("places", [])
        tous_resultats.extend(resultats)
        
        page_token = data.get("nextPageToken")
        if not page_token:
            break
    
    return tous_resultats

Enrichissement des données

Extraire les détails d'une entreprise

def obtenir_details(place_id: str) -> dict:
    """Obtient les détails complets d'un lieu."""
    url = f"https://places.googleapis.com/v1/places/{place_id}"
    
    headers = {
        "X-Goog-Api-Key": API_KEY,
        "X-Goog-FieldMask": "displayName,formattedAddress,nationalPhoneNumber,internationalPhoneNumber,websiteUri,googleMapsUri,rating,userRatingCount,businessStatus,types,regularOpeningHours"
    }
    
    response = requests.get(url, headers=headers)
    return response.json()

Trouver l'email à partir du site web

Google Places ne fournit pas les emails. Il faut les extraire du site web :

import re
from bs4 import BeautifulSoup

def extraire_email_site(url: str) -> str | None:
    """Tente d'extraire un email depuis un site web."""
    try:
        response = requests.get(url, timeout=10, headers={
            "User-Agent": "Mozilla/5.0"
        })
        
        # Chercher dans le HTML
        emails = re.findall(
            r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}',
            response.text
        )
        
        # Filtrer les faux positifs courants
        emails_valides = [
            e for e in emails
            if not e.endswith(('.png', '.jpg', '.gif', '.svg'))
            and 'example' not in e
            and 'wixpress' not in e
        ]
        
        return emails_valides[0] if emails_valides else None
        
    except Exception:
        return None

Export vers CSV

import csv

def exporter_csv(entreprises: list[dict], fichier: str = "prospects.csv"):
    """Exporte les résultats en CSV pour import dans un CRM ou Lemlist."""
    champs = ["nom", "adresse", "telephone", "site_web", "email", "note", "nb_avis"]
    
    with open(fichier, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=champs)
        writer.writeheader()
        
        for e in entreprises:
            site = e.get("websiteUri", "")
            email = extraire_email_site(site) if site else ""
            
            writer.writerow({
                "nom": e.get("displayName", {}).get("text", ""),
                "adresse": e.get("formattedAddress", ""),
                "telephone": e.get("nationalPhoneNumber", ""),
                "site_web": site,
                "email": email,
                "note": e.get("rating", ""),
                "nb_avis": e.get("userRatingCount", "")
            })
    
    print(f"✓ {len(entreprises)} prospects exportés dans {fichier}")

Stratégie de scraping multi-villes

Pour couvrir une zone géographique large, divisez en sous-zones :

VILLES_FRANCE = [
    {"nom": "Paris", "lat": 48.8566, "lng": 2.3522},
    {"nom": "Lyon", "lat": 45.7640, "lng": 4.8357},
    {"nom": "Marseille", "lat": 43.2965, "lng": 5.3698},
    {"nom": "Toulouse", "lat": 43.6047, "lng": 1.4442},
    {"nom": "Bordeaux", "lat": 44.8378, "lng": -0.5792},
    {"nom": "Lille", "lat": 50.6292, "lng": 3.0573},
    {"nom": "Nantes", "lat": 47.2184, "lng": -1.5536},
    {"nom": "Strasbourg", "lat": 48.5734, "lng": 7.7521},
]

import time

def scraper_multi_villes(requete: str, villes: list[dict]) -> list[dict]:
    """Scrape des entreprises sur plusieurs villes."""
    tous_prospects = []
    
    for ville in villes:
        print(f"Recherche à {ville['nom']}...")
        resultats = rechercher_toutes_entreprises(
            requete, ville["lat"], ville["lng"], rayon_m=15000
        )
        tous_prospects.extend(resultats)
        time.sleep(1)  # Respecter les rate limits
    
    # Dédupliquer par nom + adresse
    vus = set()
    uniques = []
    for p in tous_prospects:
        cle = (
            p.get("displayName", {}).get("text", ""),
            p.get("formattedAddress", "")
        )
        if cle not in vus:
            vus.add(cle)
            uniques.append(p)
    
    print(f"Total : {len(uniques)} prospects uniques sur {len(villes)} villes")
    return uniques

Bonnes pratiques

  1. Respectez les quotas : surveillez votre consommation dans Google Cloud Console
  2. Cachez les résultats : stockez les résultats en base pour éviter les appels redondants
  3. Respectez les CGU : Google interdit le stockage massif des données Places sans affichage d'une carte
  4. Qualifiez vos prospects : filtrez par note, nombre d'avis et présence d'un site web
  5. Rate limiting : ajoutez des délais entre les requêtes (1-2 secondes)