E-Mail-Scraping mit Python: Vollständige Anleitung mit Beispielen

Bemerkungen: 0

Damit die direkte Ansprache funktioniert, brauchen Sie eine solide Grundlage - eine Datenbank mit echten, aktuellen E-Mail-Adressen. Hier kommt das E-Mail-Scraping mit Python ins Spiel: eine Möglichkeit, programmatisch Adressen von Websites zu sammeln.

In diesem Leitfaden erfahren Sie, wie Sie E-Mail-Scraping mit Python von Grund auf aufbauen, wie Sie mit dynamischen Seiten umgehen, wie Sie die gesammelten Adressen filtern und validieren und wie Sie die daraus resultierenden Daten in echten Marketing- oder Geschäftsabläufen verwenden.

Dieses Material ist nützlich, wenn Sie es brauchen:

  • herausfinden, wie man E-Mail-Adressen von einer Website mit Python selbst auslesen kann, ohne vorgefertigte Dienste;
  • die Erstellung von Mailinglisten für Newsletter, CRM oder Forschung automatisieren;
  • Code mit realen Anwendungsfällen verbinden - von der Extraktion bis zur Integration.

Als Nächstes sehen wir uns an, wie man öffentlich zugängliche Seiten in einen direkten Kommunikationskanal mit Menschen verwandelt, die Ihre Kunden werden könnten - mit Python.

Was E-Mail-Scraping ist und wie es hilft

Im Kern geht es beim Scraping darum, HTML- oder dynamische Seiten automatisch zu scannen und im Inhalt oder in den Attributen nach Mustern zu suchen, die Adressformaten entsprechen (z. B. username@domain.tld). Anschließend werden die Ergebnisse gefiltert, validiert und gespeichert.

Aufgaben, bei denen Python Email Scraper verwendet wird

Sie wird häufig in der Wirtschaft, im Marketing, in der Forschung und bei der Automatisierung von Routineprozessen eingesetzt. Es ist besonders nützlich, wenn Sie eine große Menge an öffentlichen Informationen aus verschiedenen Quellen sammeln und strukturieren müssen.

Beispiele für spezifische Aufgaben, bei denen E-Mail-Scraping mit Python zum Einsatz kommt:

  • Aufbau einer Kontaktdatenbank für E-Mail-Kampagnen;
  • Marketing und Lead-Generierung;
  • Recherche und Analyse von öffentlich zugänglichen Kontakten;
  • Auffüllen und Aktualisieren von CRM-Systemen;
  • Überwachung der Aktivitäten von Wettbewerbern;
  • Überprüfung und Verifizierung der eigenen Kontaktdaten.

Wenn Sie an der Erfassung von Kontaktdaten für E-Commerce-Projekte interessiert sind, lesen Sie unseren Leitfaden über Scraping von E-Commerce-Daten.

Die Grundlagen: Werkzeuge und Vorbereitung

Um Scraping effektiv zu gestalten, müssen Sie die Umgebung vorbereiten und die richtigen Tools auswählen. Sie helfen Ihnen, Daten schneller abzurufen, komplexe oder dynamische Seiten zu bearbeiten und größere Projekte zu organisieren.

Bibliotheken zum Scrapen von E-Mail-Adressen auswählen

Gängige Python-Tools für Scraping:

Tool Verwenden Sie
Anfragen / httpx Abrufen statischer Seiten
BeautifulSoup HTML-Parsing / Element-Suche
re (reguläre Ausdrücke) Muster extrahieren
lxml Schnelleres Parsing
Selenium / Dramatiker Umgang mit JavaScript-gesteuerten Seiten
Scrapy Ein umfassender Rahmen für große Crawls

Vorbereiten der Arbeitsumgebung

  1. Erstellen Sie eine virtuelle Umgebung (venv oder virtualenv).
  2. Abhängigkeiten installieren:
    pip install requests beautifulsoup4 lxml
    pip install selenium  # if you need dynamic rendering
  3. (Falls erforderlich) richten Sie einen Browser-Treiber ein (ChromeDriver, GeckoDriver).
  4. Erstellen Sie eine Liste von Start-URLs oder -Domains.
  5. Entscheiden Sie sich für die Traversalstrategie - rekursiv oder eingeschränkt.

Um zu sehen, wie ähnliche Methoden für andere Plattformen angewandt werden, lesen Sie unseren ausführlichen Leitfaden über Reddit mit Python scrapen.

Beispiel: E-Mail-Scraping mit Python - Kernlogik (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

Warum auf diese Weise?

  • Session + retries - um zufällige Ausfälle zu vermeiden und bei Fehlern wiederholte Anfragen durchzuführen.
  • Regex + mailto: - zwei einfache, effektive Wege auf Anhieb.
  • lxml in BeautifulSoup - ein schnellerer und präziserer HTML-Parser.
  • Normalisierung von mailto: - Entfernen Sie alles Extra (?subject=...), behalten Sie nur die Adresse.

Eine erweiterte Variante: 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}")

So führen Sie das erweiterte Skript aus und konfigurieren es

main.py https://example.com
Skript-Parameter
  • start_url - die Start-URL, an der die Durchquerung beginnt (z. B. https://example.com).
  • --max-pages - maximale Anzahl der zu durchlaufenden Seiten. Standardwert: 100.
  • --delay - verzögerung zwischen Anfragen in Sekunden, um die Serverlast zu reduzieren. Voreinstellung: 0,5.
  • --no-robots - ignoriert Regeln aus robots.txt. Verwenden Sie diese Option mit Bedacht, da eine Website automatisches Traversieren verbieten kann.
  • --include-subdomains - schließt Subdomains beim Traversal ein. Standardmäßig aktiviert.
  • --exact-host - beschränkt das Traversal auf den exakten Host (keine Subdomains).
  • --output - pfad zu einer Datei zum Speichern der gefundenen Adressen (eine pro Zeile). Wenn nicht angegeben, werden die Adressen auf der Konsole ausgegeben.

Umgang mit Verschleierung und dynamischen Inhalten

Wenn Sie ein Skript ausführen, sind die Dinge nicht immer einfach: Viele Websites verbergen absichtlich E-Mail-Adressen oder geben sie erst nach dem Rendern von JavaScript preis. Hier erfahren Sie, was in die Quere kommen kann - und wie Sie damit umgehen.

Mögliche Probleme

1. Verschleierung

Websites verwenden oft Techniken, um Adressen vor Bots zu verbergen:

  • JavaScript, das die Adresse aus Teilen zusammensetzt (z. B. user + "@" + domain.com);
  • Verschlüsselte oder kodierte Zeichenketten (z. B. Base64, HTML-Entities);
  • HTML-Kommentare oder Einfügungen, bei denen ein Teil der Adresse verborgen bleibt;
  • E-Mail als Bild (ein Bild mit Text), in diesem Fall sieht das Skript nichts;
  • Zeichenersetzungen: user [at] example [dot] com und andere "menschenlesbare" Formen (Adressvermischung).

2. Dynamische Seiten

Moderne Websites laden Inhalte häufig über JavaScript (z. B. Fetch, AJAX). Eine einfache requests.get() kann eine "leere" HTML-Shell ohne den E-Mail-Inhalt zurückgeben.

Wege zur Überwindung dieser Hindernisse

Praktische Ansätze, wenn Sie auf solche Seiten stoßen:

  1. Selenium oder Playwright:

    Starten Sie einen Browser, lassen Sie die Seite "laden", warten Sie auf die erforderlichen Elemente und erfassen Sie dann den vollständigen HTML-Code. Dies funktioniert, wenn die E-Mail nach dem Rendern durch JS injiziert wird.

  2. API-Aufrufe:

    Oftmals bezieht die Seite tatsächlich Daten von einer API. Prüfen Sie die Netzwerkanfragen (DevTools → Netzwerk), um zu sehen, ob es eine Anfrage gibt, die die E-Mail- oder Kontaktinformationen in JSON zurückgibt. Wenn ja, ist es besser, die API direkt zu verwenden.

  3. Parsing von Inline-JS/Skripten:

    Manchmal ist die Adresse in JavaScript "eingebettet" (z. B. eine Base64-Zeichenfolge oder in Teile aufgeteilt). Sie können dieses JS interpretieren, die Zeichenfolge extrahieren und die Adresse entschlüsseln.

  4. Wenn die E-Mail ein Bild enthält:

    Laden Sie das Bild herunter und wenden Sie OCR (Optical Character Recognition) an, zum Beispiel mit Tesseract. Dies ist ressourcenintensiver, aber manchmal notwendig.

  5. Verzögerungen und Zeitplan:

    Einige Elemente erscheinen erst nach einigen Sekunden oder nach bestimmten Ereignissen (Scrollen, Klicken). Das ist sinnvoll:

    • sleep() verwenden oder auf einen Selektor warten;
    • versuchen Sie mehrere Versuche;
    • die Strategien "Wiederholen, wenn nicht gefunden" anwenden.

Schlussfolgerung

Wenn Sie die in diesem Artikel besprochenen Techniken für das E-Mail-Scraping mit Python anwenden, können Sie dafür sorgen, dass Ihre Skripte unter realen Bedingungen zuverlässig funktionieren. Denken Sie daran, dass sich die Datenqualität direkt auf die Wirksamkeit nachfolgender Kampagnen auswirkt. Es lohnt sich also, von Anfang an Filterung, Validierung und Speicherung in einem geeigneten Format zu implementieren.

Bemerkungen:

0 Bemerkungen