En 2025, presque toutes les équipes sales B2B utilisent du scraping d'une façon ou d'une autre. LinkedIn Sales Navigator, Google Maps, Pappers, Infogreffe, Apollo — les données sont là, publiques, accessibles. La vraie question n'est plus "est-ce qu'on scrape ?" mais "est-ce qu'on le fait bien ?"
Ce que j'observe chez la plupart des clients quand ils arrivent avec un projet scraping :
- Soit ils ont un outil no-code qui se fait bloquer tous les 3 jours
- Soit ils ont un dev qui a pondu un script fragile il y a 18 mois et qui ne touche plus
- Soit ils font ça à la main, ce qui n'est pas du scraping mais de l'auto-flagellation
Voici ce que ça devrait être à la place.
Ce que "éthique" veut vraiment dire
Le terme est galvaudé. Dans la pratique, scraping éthique = trois choses :
1. Respecter ce qui est public vs. ce qui est protégé
Scraper les résultats d'une recherche LinkedIn publique : légal. Scraper les données personnelles d'un profil privé ou d'une base clients : illégal (RGPD, article 6). La frontière est la visibilité publique et la finalité du traitement.
2. Ne pas surcharger le serveur cible
Un scraper qui envoie 200 requêtes/seconde sur un site de PME, c'est un mini-DDoS. Indépendamment de la légalité, c'est mauvais pour votre réputation et votre IP. La règle : un délai aléatoire entre 1,5 et 4 secondes entre chaque requête, user-agent rotatif, respect du robots.txt sur les endpoints sensibles.
3. Ne stocker que ce qui est nécessaire
RGPD, article 5 : minimisation des données. Si vous scrapez pour qualifier des leads B2B, vous avez besoin du nom de l'entreprise, du secteur, de la taille, de l'email pro. Vous n'avez pas besoin de l'adresse personnelle du dirigeant ni de son lycée. Stocker plus que nécessaire, c'est un risque légal et une surface d'attaque.
Architecture d'un pipeline scraping robuste
Voici la structure que j'utilise pour les projets en production :
Source (LinkedIn / Pappers / Google Maps)
↓
Playwright (anti-détection + proxies rotatifs)
↓
Normalisation + déduplication (Levenshtein sur noms d'entreprise)
↓
Enrichissement IA (GPT-4o : secteur, CA estimé, signaux d'achat)
↓
Scoring ICP (modèle maison sur critères client)
↓
Export CRM (HubSpot / Airtable / CSV)
Chaque étape est indépendante et peut être relancée sans tout refaire. Si le scraping s'arrête au milieu (timeout, ban temporaire), le reste du pipeline reprend où il s'est arrêté.
Le vrai problème : la déduplication
C'est ce qui fait la différence entre un pipeline amateur et un pipeline en production.
Même société, trois orthographes différentes selon la source :
- "Société Générale" (LinkedIn)
- "SOCIETE GENERALE" (Pappers)
- "Soc. Gen." (annuaire sectoriel)
Sans déduplication, vous avez trois leads pour la même entreprise. Votre commercial appelle trois fois le même prospect. Mauvais pour la relation, mauvais pour votre réputation.
La solution : normalisation des noms (suppression accents, minuscules, suppression des formes juridiques) + distance de Levenshtein sur les chaînes normalisées + SIREN comme clé d'unicité quand disponible.
import unicodedata
from Levenshtein import ratio
def normalize(name: str) -> str:
name = name.lower().strip()
# Remove legal forms
for suffix in [' sas', ' sarl', ' sa', ' sasu', ' eurl', ' sci']:
name = name.removesuffix(suffix)
# Remove accents
return ''.join(
c for c in unicodedata.normalize('NFD', name)
if unicodedata.category(c) != 'Mn'
)
def is_duplicate(a: str, b: str, threshold: float = 0.85) -> bool:
return ratio(normalize(a), normalize(b)) >= threshold
Anti-détection : ce qui marche vraiment
Playwright est le standard. Voici les réglages qui font la différence :
from playwright.async_api import async_playwright
import random, asyncio
async def scrape_with_stealth(url: str):
async with async_playwright() as p:
browser = await p.chromium.launch(
headless=True,
args=['--disable-blink-features=AutomationControlled']
)
context = await browser.new_context(
user_agent=random.choice(USER_AGENTS),
viewport={'width': 1366, 'height': 768},
locale='fr-FR',
)
# Remove navigator.webdriver
await context.add_init_script("""
Object.defineProperty(navigator, 'webdriver', { get: () => undefined })
""")
page = await context.new_page()
await page.goto(url)
# Human-like delay
await asyncio.sleep(random.uniform(1.5, 4.0))
# ...
Ce qui ne sert à rien : les extensions "stealth" génériques, changer l'IP sans changer le fingerprint, les délais fixes (1s pile, ça se détecte).
Ce que vos concurrents font déjà
D'après les conversations que j'ai avec des équipes sales en 2025 : environ 60% des entreprises B2B entre 10 et 200 salariés utilisent un outil d'enrichissement de données (Apollo, Kaspr, Dropcontact) et un tiers d'entre elles ont un pipeline custom en plus.
La différence entre ceux qui ont un avantage et ceux qui suivent la masse : la qualité du scoring et de la personnalisation. N'importe qui peut extraire un email pro. Peu de gens peuvent automatiquement enrichir avec le CA estimé, les technologies utilisées (via BuiltWith ou Wappalyzer API), et les signaux de recrutement récents.
C'est là qu'un pipeline custom justifie son coût.
Ce qu'il ne faut pas faire
- Scraper en masse des profils LinkedIn personnels sans avoir un usage B2B clair et documenté
- Revendre les données collectées à des tiers — même anonymisées, c'est souvent problématique
- Ignorer les cookies et les CGU sur les sources premium (Pappers Premium, Societe.com) — vous avez souvent accepté des termes qui interdisent le scraping automatisé
- Ne pas purger les données inutilisées après un certain délai (RGPD, durée de conservation)
Quand le blocage arrive quand même
Même avec toutes ces précautions, un scraper finit tôt ou tard par se faire bloquer temporairement — CAPTCHA, 429, ou simplement une page vide qui indique une détection. La réaction la plus fréquente (et la plus mauvaise) : insister, retenter immédiatement, changer de proxy et recommencer en boucle. C'est exactement le pattern qui confirme au système cible qu'il a affaire à un bot, et qui peut transformer un blocage temporaire en bannissement permanent de l'IP ou du compte.
La bonne réaction est contre-intuitive : reculer, pas insister.
async def scrape_with_backoff(url: str, attempt: int = 0, max_attempts: int = 3):
if attempt >= max_attempts:
# Marquer pour retry manuel plus tard, ne pas s'acharner
await mark_for_manual_review(url)
return None
result = await scrape_with_stealth(url)
if is_blocked(result):
# Backoff long, pas juste quelques secondes — on laisse le
# système cible "oublier" le comportement suspect
wait_time = (attempt + 1) * 300 # 5min, 10min, 15min
await asyncio.sleep(wait_time)
return await scrape_with_backoff(url, attempt + 1, max_attempts)
return result
Sur les pipelines que j'opère, une URL qui échoue 3 fois avec ce backoff est mise de côté pour une revue manuelle plutôt que de continuer à taper dessus automatiquement. Le taux de bannissement définitif a chuté de manière notable depuis qu'on a remplacé le retry agressif par cette logique — l'objectif d'un pipeline de scraping en production n'est pas de gagner un bras de fer contre la détection, c'est de rester invisible.
Pour aller plus loin
Si vous avez un cas d'usage scraping/enrichissement en tête, je construis ce type de pipelines. Décrivez votre besoin — la première analyse est gratuite.
Le tl;dr : le scraping éthique, c'est surtout du scraping intelligent. Cibler moins mais mieux, enrichir proprement, et ne stocker que ce dont vous avez besoin. Vos concurrents le font déjà — autant le faire mieux qu'eux.