Scraping Email dengan Python: Panduan Lengkap dengan Contoh

Komentar: 0

Agar penjangkauan langsung dapat bekerja, Anda membutuhkan dasar yang kuat - basis data alamat email yang nyata dan terkini. Di situlah pengikisan email dengan Python berperan: sebuah cara untuk mengumpulkan alamat dari situs web secara terprogram.

Dalam panduan ini, kita akan melihat cara membuat pengikisan email dengan Python dari awal, cara menangani halaman dinamis, cara memfilter dan memvalidasi alamat yang Anda kumpulkan, dan cara menggunakan data yang dihasilkan dalam alur kerja pemasaran atau bisnis yang sebenarnya.

Materi ini berguna jika Anda memerlukannya:

  • mengetahui cara mengikis alamat email dari situs web dengan Python secara mandiri, tanpa layanan yang sudah jadi;
  • mengotomatiskan pembuatan milis untuk nawala, CRM, atau penelitian;
  • menghubungkan kode ke kasus penggunaan nyata - mulai dari ekstraksi hingga integrasi.

Selanjutnya, kita akan melihat bagaimana cara mengubah halaman yang tersedia untuk umum menjadi saluran komunikasi langsung dengan orang-orang yang mungkin menjadi pelanggan Anda - menggunakan Python.

Apa Itu Email Scraping dan Bagaimana Manfaatnya

Pada intinya, scraping adalah memindai HTML atau halaman dinamis secara otomatis dan mencari pola-pola yang sesuai dengan format alamat (misalnya, username@domain.tld). Kemudian Anda menyaring, memvalidasi, dan menyimpan hasilnya.

Tugas-tugas di mana Scraper Email Python Digunakan

Ini banyak digunakan dalam bisnis, pemasaran, penelitian, dan mengotomatiskan proses rutin. Ini sangat berguna ketika Anda perlu mengumpulkan dan menyusun informasi publik dalam jumlah besar dari berbagai sumber.

Contoh tugas spesifik yang menggunakan penggalian email dengan Python:

  • Membangun basis data kontak untuk kampanye email;
  • Pemasaran dan perolehan prospek;
  • Penelitian dan analisis kontak yang tersedia untuk umum;
  • Mengisi dan memperbarui sistem CRM;
  • Memantau aktivitas pesaing;
  • Mengaudit dan memverifikasi data kontak Anda sendiri.

Jika Anda tertarik untuk mengumpulkan data kontak untuk proyek e-commerce, jelajahi panduan kami di Pengikisan data e-niaga.

Dasar-dasarnya: Alat dan Persiapan

Agar scraping efektif, Anda perlu mempersiapkan lingkungan dan memilih alat yang tepat. Alat-alat ini membantu Anda mengambil data dengan lebih cepat, menangani halaman yang kompleks atau dinamis, dan mengatur proyek yang lebih besar.

Memilih Perpustakaan untuk Mengikis Alamat Email

Alat-alat Python yang umum untuk melakukan scraping:

Alat Gunakan
requests / httpx Mengambil halaman statis
BeautifulSoup Penguraian HTML / pencarian elemen
re (ekspresi reguler) Mengekstrak pola
lxml Penguraian lebih cepat
Selenium / Playwright Menangani halaman yang digerakkan oleh JavaScript
Scrapy Kerangka kerja skala penuh untuk perayapan besar

Mempersiapkan Lingkungan Kerja

  1. Membuat lingkungan virtual (venv atau virtualenv).
  2. Menginstal dependensi:
    pip install requests beautifulsoup4 lxml
    pip install selenium  # if you need dynamic rendering
  3. (Jika perlu) siapkan driver browser (ChromeDriver, GeckoDriver).
  4. Siapkan daftar URL atau domain awal.
  5. Tentukan strategi penjelajahan - rekursif atau terbatas.

Untuk melihat bagaimana metode serupa diterapkan untuk platform lain, lihat panduan terperinci kami di mengikis Reddit menggunakan Python.

Contoh: Scraping Email dengan Python - Logika Inti (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

Mengapa dengan cara ini?

  • Sesi + percobaan ulang - untuk menghindari kegagalan acak dan melakukan permintaan ulang pada kesalahan.
  • Regex + mailto: - dua jalur yang sederhana dan efektif sekaligus.
  • lxml di BeautifulSoup - pengurai HTML yang lebih cepat dan tepat.
  • Menormalkan mailto: - hapus semua yang tidak diperlukan (?subject=...), simpan alamatnya saja.

Varian yang Diperluas: Perayap Multi-Tingkat

"""
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}")

Cara Menjalankan dan Mengonfigurasi Skrip yang Diperluas

main.py https://example.com
Parameter Skrip
  • start_url - URL awal tempat penelusuran dimulai (misalnya, https://example.com).
  • --max-pages - jumlah maksimum halaman yang akan dilalui. Default: 100.
  • --delay - penundaan antara permintaan dalam hitungan detik untuk mengurangi beban server. Default: 0,5.
  • --tanpa-robot - abaikan aturan dari robots.txt. Gunakan dengan hati-hati, karena situs mungkin melarang penjelajahan otomatis.
  • --include-subdomain - menyertakan subdomain selama penelusuran. Diaktifkan secara default.
  • --exact-host - batasi penjelajahan pada host yang tepat (tanpa subdomain).
  • --output - jalur ke file untuk menyimpan alamat yang ditemukan (satu per baris). Jika tidak disediakan, alamat akan dicetak ke konsol.

Menangani Kebingungan dan Konten Dinamis

Ketika Anda menjalankan skrip, banyak hal yang tidak selalu mudah: banyak situs yang dengan sengaja menyembunyikan alamat email atau hanya menampilkannya setelah JavaScript di-render. Berikut ini adalah hal-hal yang bisa menghalangi - dan cara menanganinya.

Potensi Masalah

1. Pengaburan

Situs-situs sering kali menggunakan teknik untuk menyembunyikan alamat dari bot:

  • JavaScript yang mengumpulkan alamat dari beberapa bagian (misalnya, pengguna + "@" + domain.com);
  • String yang dienkripsi atau dikodekan (misalnya, Base64, entitas HTML);
  • Komentar atau sisipan HTML di mana sebagian alamat tetap tersembunyi;
  • Email sebagai gambar (gambar teks), dalam hal ini skrip tidak melihat apa-apa;
  • Penggantian karakter: user [at] example [dot] com dan bentuk lain yang "dapat dibaca manusia" (address munging).

2. Halaman dinamis

Situs modern sering memuat konten melalui JavaScript (misalnya, mengambil, AJAX). Sebuah request.get() biasa dapat mengembalikan sebuah shell HTML "kosong" tanpa konten email.

Cara untuk Mengatasi Hambatan Ini

Pendekatan praktis ketika Anda menemukan halaman seperti itu:

  1. Selenium atau Playwright:

    Luncurkan browser, biarkan halaman "dimuat", tunggu elemen-elemen yang diperlukan, lalu tangkap HTML lengkapnya. Ini berfungsi ketika email disuntikkan oleh JS setelah dirender.

  2. Panggilan API:

    Sering kali halaman benar-benar menarik data dari API. Periksa permintaan jaringan (DevTools → Network) untuk melihat apakah ada permintaan yang mengembalikan email atau info kontak dalam JSON. Jika ya, lebih baik menggunakan API secara langsung.

  3. Mengurai JS / skrip sebaris:

    Terkadang alamat "tertanam" dalam JavaScript (misalnya, string Base64 atau dipecah menjadi beberapa bagian). Anda dapat menginterpretasikan JS tersebut, mengekstrak string, dan memecahkan kode alamat.

  4. Jika email berupa gambar:

    Unduh gambar dan terapkan OCR (Pengenalan Karakter Optik), misalnya dengan Tesseract. Ini lebih banyak menggunakan sumber daya, tetapi terkadang diperlukan.

  5. Penundaan dan pengaturan waktu:

    Beberapa elemen muncul setelah beberapa detik atau setelah peristiwa tertentu (gulir, klik). Hal ini masuk akal:

    • gunakan sleep() atau tunggu sampai ada pemilih;
    • mencoba beberapa kali percobaan;
    • menerapkan strategi "coba lagi jika tidak ditemukan".

Kesimpulan

Dengan menerapkan teknik yang dibahas dalam artikel ini untuk scraping email dengan Python, Anda bisa membuat skrip Anda bekerja dengan andal dalam kondisi dunia nyata. Perlu diingat bahwa kualitas data secara langsung memengaruhi efektivitas kampanye selanjutnya, jadi sebaiknya menerapkan penyaringan, validasi, dan menyimpan ke format yang nyaman sejak awal.

Komentar:

0 komentar