Email Scraping avec Python: Guide complet avec exemples

Commentaires: 0

Pour que la prospection directe fonctionne, vous avez besoin d'une base solide - une base de données d'adresses électroniques réelles et à jour. C'est là qu'intervient le scraping d'adresses électroniques avec Python: un moyen de collecter de manière programmatique des adresses à partir de sites web.

Dans ce guide, nous verrons comment créer un système de scraping d'emails avec Python à partir de zéro, comment gérer les pages dynamiques, comment filtrer et valider les adresses collectées, et comment utiliser les données résultantes dans des flux de travail marketing ou commerciaux réels.

Ce matériel est utile si vous avez besoin de:

  • comprendre comment récupérer les adresses électroniques d'un site web avec Python par soi-même, sans avoir recours à des services prêts à l'emploi;
  • automatiser la création de listes de diffusion pour les bulletins d'information, les CRM ou la recherche;
  • connecter le code à des cas d'utilisation réels - de l'extraction à l'intégration.

Ensuite, nous verrons comment transformer des pages accessibles au public en un canal de communication directe avec des personnes susceptibles de devenir vos clients - à l'aide de Python.

Qu'est-ce que l'Email Scraping et quelle est son utilité?

À la base, ce type de scraping consiste à analyser automatiquement des pages HTML ou dynamiques et à rechercher dans le contenu ou les attributs des modèles correspondant à des formats d'adresse (par exemple, username@domain.tld). Les résultats sont ensuite filtrés, validés et enregistrés.

Tâches pour lesquelles Python Email Scraper est utilisé

Il est largement utilisé dans les affaires, le marketing, la recherche et l'automatisation des processus de routine. Il est particulièrement utile lorsqu'il s'agit de rassembler et de structurer un grand volume d'informations publiques provenant de sources multiples.

Exemples de tâches spécifiques où le scraping d'emails avec Python est appliqué:

  • Création d'une base de données de contacts pour les campagnes de courrier électronique;
  • Marketing et génération de leads;
  • Recherche et analyse de contacts accessibles au public;
  • alimenter et mettre à jour les systèmes de gestion de la relation client (CRM);
  • Surveiller l'activité des concurrents;
  • Auditer et vérifier ses propres données de contact.

Si vous souhaitez recueillir des données de contact pour des projets de commerce électronique, consultez notre guide sur les récupération de données sur le commerce électronique.

Les bases: Outils et préparation

Pour que le scraping soit efficace, vous devez préparer l'environnement et choisir les bons outils. Ils vous aident à récupérer les données plus rapidement, à gérer des pages complexes ou dynamiques et à organiser des projets de plus grande envergure.

Choisir des bibliothèques pour récupérer des adresses électroniques

Outils Python courants pour le scraping:

Tool Utilisation
requests / httpx Récupérer des pages statiques
BeautifulSoup Analyse HTML / recherche d'éléments
re (expressions régulières) Extraire des modèles
lxml Une analyse plus rapide
Selenium / Playwright Gestion des pages pilotées par JavaScript
Scrapy Un cadre complet pour les grandes recherches

Préparation de l'environnement de travail

  1. Créer un environnement virtuel (venv ou virtualenv).
  2. Installer les dépendances:
    pip install requests beautifulsoup4 lxml
    pip install selenium  # if you need dynamic rendering
  3. (Si nécessaire, configurer un pilote de navigateur (ChromeDriver, GeckoDriver).
  4. Préparez une liste d'URL ou de domaines de départ.
  5. Décidez de la stratégie de traversée - récursive ou limitée.

Pour voir comment des méthodes similaires sont appliquées à d'autres plateformes, consultez notre guide détaillé sur les scraper Reddit à l'aide de Python.

Exemple: Scraping d'emails avec Python - Logique de base (Pseudocode)

# 1. Create an HTTP session with timeouts and retries
session = make_session()
# 2. Load the page
html = session.get(url)
# 3. Look for email addresses:
#    - via regex across the entire text
#    - via mailto: links in HTML
emails = extract_emails_from_text(html)
emails.update(find_mailto_links(html))
# 4. Return a unique list of addresses
return emails

Pourquoi de cette manière?

  • Session + tentatives - pour éviter les échecs aléatoires et répéter les demandes en cas d'erreur.
  • Regex + mailto: - deux chemins simples et efficaces immédiatement.
  • lxml dans BeautifulSoup - un analyseur HTML plus rapide et plus précis.
  • Normalisation de mailto: - supprimer tout ce qui est en trop (?subject=...), ne garder que l'adresse.

Une variante étendue: L'engin à plusieurs niveaux (Multi-Level Crawler)

"""
Iterate over internal links within one domain and collect email addresses.
Highlights:
- Page limit (max_pages) to stop safely
- Verifying that a link belongs to the base domain
- Avoiding re-visits
- Optional respect for robots.txt
"""

from __future__ import annotations
from collections import deque
from typing import Set
from urllib.parse import urljoin, urlparse, urlsplit, urlunsplit
import time
import requests
from bs4 import BeautifulSoup
import lxml # Import lxml to ensure it's available for BeautifulSoup
from urllib import robotparser  # standard robots.txt parser
# We use functions from the previous block:
# - make_session()
# - scrape_emails_from_url()
import re

# General regular expression for email addresses
EMAIL_RE = re.compile(r"[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9.-]+")

def scrape_emails_from_url(url: str, session: requests.Session) -> Set[str]:
   """Collect email addresses from the given URL page."""
   emails: Set[str] = set()
   try:
       resp = session.get(url, timeout=getattr(session, "_default_timeout", 10.0))
       resp.raise_for_status()
       # Regular expression for email addresses
       # Note: this regex isn't perfect, but it's sufficient for typical cases
       email_pattern = re.compile(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}")
       emails.update(email_pattern.findall(resp.text))
   except requests.RequestException:
       pass
   return emails

def make_session() -> requests.Session:
   """Create and return a requests session with basic settings."""
   session = requests.Session()
   session.headers.update({
       "User-Agent": "EmailScraper/1.0",
       "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
       "Accept-Language": "en-US,en;q=0.9",
       # Don't force Accept-Encoding to avoid br issues without brotli
       "Connection": "keep-alive",
   })
   return session
def same_host(url: str, base_netloc: str) -> bool:
   """True if the link belongs to the same host (domain/subdomain)."""
   return urlparse(url).netloc == base_netloc
def load_robots(start_url: str, user_agent: str = "EmailScraper") -> robotparser.RobotFileParser:
   """Read robots.txt and return a parser for permission checks."""
   base = urlparse(start_url)
   robots_url = f"{base.scheme}://{base.netloc}/robots.txt"
   rp = robotparser.RobotFileParser()
   rp.set_url(robots_url)
   try:
       rp.read()
   except Exception:
       pass
   rp.useragent = user_agent
   return rp

def normalize_url(url: str, base: str | None = None) -> str | None:
   try:
       abs_url = urljoin(base, url) if base else url
       parts = urlsplit(abs_url)
       if parts.scheme not in ("http", "https"):
           return None
       host = parts.hostname
       if not host:
           return None
       host = host.lower()
       netloc = host
       if parts.port:
           netloc = f"{host}:{parts.port}"
       parts = parts._replace(fragment="")
       return urlunsplit((parts.scheme.lower(), netloc, parts.path or "/", parts.query, ""))
   except Exception:
       return None

def in_scope(url: str, base_host: str, include_subdomains: bool) -> bool:
   try:
       host = urlsplit(url).hostname
       if not host:
           return False
       host = host.lower()
       base_host = (base_host or "").lower()
       if include_subdomains:
           return host == base_host or host.endswith("." + base_host)
       else:
           return host == base_host
   except Exception:
       return False
def collect_emails_from_site(
   start_url: str,
   max_pages: int = 100,
   delay_sec: float = 0.5,
   respect_robots: bool = True,
   include_subdomains: bool = True,
) -> Set[str]:
   """
   Traverse pages within a domain and return unique email addresses.
   - max_pages: hard limit on visited pages.
   - delay_sec: polite pause between requests.
   - respect_robots: if True — checks access rules.
   - include_subdomains: if True — allows subdomains (www, etc.).
   """
   session = make_session()
   base_host = (urlparse(start_url).netloc or "").lower()
   visited: Set[str] = set()
   queue: deque[str] = deque()
   enqueued: Set[str] = set()
   all_emails: Set[str] = set()

   start_norm = normalize_url(start_url)
   if start_norm:
       queue.append(start_norm)
       enqueued.add(start_norm)

   rp = load_robots(start_url, user_agent="EmailScraper/1.0") if respect_robots else None

   while queue and len(visited) < max_pages:
       url = queue.popleft()
       if url in visited:
           continue

       # robots.txt check
       if respect_robots and rp is not None:
           try:
               if not rp.can_fetch("EmailScraper/1.0", url):
                   continue
           except Exception:
               pass

       # One request: used both for emails and links
       try:
           resp = session.get(url, timeout=10)
           resp.raise_for_status()
           html_text = resp.text or ""
       except requests.RequestException:
           continue

       visited.add(url)

       # Skip non-HTML pages
       ctype = resp.headers.get("Content-Type", "")
       if ctype and "text/html" not in ctype:
           continue

       # Collect emails
       for m in EMAIL_RE.findall(html_text):
           all_emails.add(m.lower())

       # Parse links
       soup = BeautifulSoup(html_text, "lxml")

       # Emails from mailto:
       for a in soup.find_all("a", href=True):
           href = a["href"].strip()
           if href.lower().startswith("mailto:"):
               addr_part = href[7:].split("?", 1)[0]
               for piece in addr_part.split(","):
                   email = piece.strip()
                   if EMAIL_RE.fullmatch(email):
                       all_emails.add(email.lower())

       for a in soup.find_all("a", href=True):
           href = a["href"].strip()
           if not href or href.startswith(("javascript:", "mailto:", "tel:", "data:")):
               continue
           next_url = normalize_url(href, base=url)
           if not next_url:
               continue
           if not in_scope(next_url, base_host, include_subdomains):
               continue
           if next_url not in visited and next_url not in enqueued:
               queue.append(next_url)
               enqueued.add(next_url)

       if delay_sec > 0:
           time.sleep(delay_sec)

   try:
       session.close()
   except Exception:
       pass
   return all_emails
if __name__ == "__main__":
   import argparse

parser = argparse.ArgumentParser(
   description="An email scraper that traverses pages within a site and prints discovered addresses."
)

parser.add_argument(
   "start_url",
   help="Starting URL, for example: https://example.com"
)

parser.add_argument(
   "--max-pages",
   type=int,
   default=100,
   dest="max_pages",
   help="Maximum number of pages to traverse (default: 100)"
)

parser.add_argument(
   "--delay",
   type=float,
   default=0.5,
   help="Delay between requests in seconds (default: 0.5)"
)

parser.add_argument(
   "--no-robots",
   action="store_true",
   help="Ignore robots.txt (use carefully)"
)

scope = parser.add_mutually_exclusive_group()

scope.add_argument(
   "--include-subdomains",
   dest="include_subdomains",
   action="store_true",
   default=True,
   help="Include subdomains (default)"
)

scope.add_argument(
   "--exact-host",
   dest="include_subdomains",
   action="store_false",
   help="Restrict traversal to the exact host (no subdomains)"
)

parser.add_argument(
   "--output",
   type=str,
   default=None,
   help="Optional: path to a file to save found email addresses (one per line)"

   args = parser.parse_args()

   emails = collect_emails_from_site(
       args.start_url,
       max_pages=args.max_pages,
       delay_sec=args.delay,
       respect_robots=not args.no_robots,
       include_subdomains=args.include_subdomains,
   )

   for e in sorted(emails):
       print(e)

   print(f"Found {len(emails)} unique emails.")

   if args.output:
       try:
           with open(args.output, "w", encoding="utf-8") as f:
               for e in sorted(emails):
                   f.write(e + "\n")
       except Exception as ex:
           print(f"Could not write the output file: {ex}")

Comment exécuter et configurer le script étendu?

main.py https://example.com
Paramètres du script
  • start_url - l'URL de départ où la traversée commence (par exemple, https://example.com).
  • --max-pages - nombre maximal de pages à parcourir. Valeur par défaut: 100.
  • --delay - délai entre les demandes en secondes pour réduire la charge du serveur. Valeur par défaut: 0,5.
  • --no-robots - ignore les règles du fichier robots.txt. À utiliser avec précaution, car un site peut interdire la navigation automatisée.
  • --include-subdomains - inclut les sous-domaines pendant la traversée. Activé par défaut.
  • --exact-host - restreint la recherche à l'hôte exact (pas de sous-domaines).
  • --output - chemin vers un fichier pour enregistrer les adresses trouvées (une par ligne). Si ce n'est pas le cas, les adresses sont affichées sur la console.

Gestion de l'obscurcissement et du contenu dynamique

Lorsque vous exécutez un script, les choses ne sont pas toujours simples: de nombreux sites cachent délibérément les adresses électroniques ou ne les exposent qu'après le rendu du JavaScript. Voici ce qui peut vous gêner, et comment y remédier.

Problèmes potentiels

1. Obscurcissement

Les sites utilisent souvent des techniques pour cacher les adresses aux robots:

  • JavaScript qui assemble l'adresse à partir de différentes parties (par exemple, utilisateur + "@" + domaine.com);
  • Chaînes cryptées ou encodées (par exemple, Base64, entités HTML);
  • Commentaires ou insertions HTML où une partie de l'adresse reste cachée;
  • Email en tant qu'image (une image du texte), dans ce cas le script ne voit rien;
  • Remplacements de caractères: user [at] example [dot] com et autres formes "lisibles par l'homme" (modification de l'adresse).

2. Pages dynamiques

Les sites modernes chargent souvent le contenu via JavaScript (par exemple, fetch, AJAX). Un simple requests.get() peut renvoyer un shell HTML "vide" sans le contenu de l'e-mail.

Comment surmonter ces obstacles

Approches pratiques lorsque vous rencontrez de telles pages:

  1. Selenium ou Playwright:

    Lancez un navigateur, laissez la page se "charger", attendez les éléments requis, puis capturez le code HTML complet. Cela fonctionne lorsque l'e-mail est injecté par JS après le rendu.

  2. Appels API:

    Souvent, la page tire réellement des données d'une API. Vérifiez les requêtes réseau (DevTools → Network) pour voir s'il existe une requête qui renvoie l'adresse électronique ou les informations de contact en JSON. Si c'est le cas, il est préférable d'utiliser l'API directement.

  3. Analyse des JS / scripts en ligne:

    Parfois, l'adresse est "intégrée" dans JavaScript (par exemple, une chaîne Base64 ou divisée en plusieurs parties). Vous pouvez interpréter ce JavaScript, extraire la chaîne et décoder l'adresse.

  4. Si l'e-mail est une image:

    Télécharger l'image et appliquer la ROC (Reconnaissance Optique de Caractères), par exemple avec Tesseract. Cette opération demande plus de ressources, mais elle est parfois nécessaire.

  5. Retards et calendrier:

    Certains éléments apparaissent après quelques secondes ou après des événements spécifiques (défilement, clic). Il est judicieux de:

    • utiliser sleep() ou attendre un sélecteur;
    • faire plusieurs tentatives;
    • appliquer des stratégies de "réessai si introuvable".

Conclusion

En appliquant les techniques discutées dans cet article pour le scraping d'emails avec Python, vous pouvez faire fonctionner vos scripts de manière fiable dans des conditions réelles. Gardez à l'esprit que la qualité des données affecte directement l'efficacité des campagnes ultérieures, il est donc utile de mettre en œuvre le filtrage, la validation et l'enregistrement dans un format pratique dès le départ.

Commentaires:

0 Commentaires