Чтобы прямая коммуникация с потенциальными клиентами работала, нужна основа — база реальных, актуальных email-адресов. Здесь и пригодится скрапинг Email на Python — инструмент, который позволяет программно извлекать адреса электронной почты с веб-сайтов.
В этом гайде разберем, как с нуля построить скрапинг Email на Python, как обрабатывать динамические страницы, как фильтровать и валидировать собранные адреса, а также как использовать полученные данные в реальной маркетинговой или бизнес-логике.
Материал будет полезен, если вам нужно:
Далее рассмотрим, как при помощи Python превратить публично доступные страницы в инструмент прямой коммуникации с теми, кто может стать вашим клиентом.
Суть — автоматическое сканирование HTML- или динамических страниц, поиск в содержимом или атрибутах шаблонов, соответствующих формату адресов (например, [email protected]). После этого — фильтрация, валидация адресов и сохранение.
Скрапинг широко применяется в бизнесе, маркетинге, исследовательской деятельности и для автоматизации рутинных процессов. Особенно он полезен, когда нужно собрать и структурировать большой объем публичной информации из разных источников.
Примеры задач:
Если вас интересует сбор контактных данных для e-commerce проектов, посмотрите наше руководство по скрапингу данных электронной коммерции.
Чтобы процесс скрапинга был результативным, подготовьте рабочее окружение и подберите подходящие инструменты. Они помогают быстрее получать данные, обрабатывать сложные или динамические страницы и организовывать масштабные проекты.
Типовые инструменты Python для скрапинга:
Инструмент | Применение |
---|---|
requests / httpx | Запросы к статическим страницам |
BeautifulSoup | Парсинг HTML / поиск элементов |
re (регулярные выражения) | Выделение шаблонов email |
lxml | Ускоренный парсинг |
Selenium / Playwright | Обработка динамических страниц с JavaScript |
Scrapy | Масштабный фреймворк для краулинга больших объемов данных |
pip install requests beautifulsoup4 lxml
pip install selenium # если нужен динамический рендеринг
Чтобы узнать, как похожие методы применяются для других платформ, ознакомьтесь с нашим подробным руководством как скрапить Reddit с помощью Python.
# 1. Создаем HTTP-сессию с таймаутами и ретраями
session = make_session()
# 2. Загружаем страницу
html = session.get(url)
# 3. Ищем email-адреса:
# - через regex по всему тексту
# - через ссылки вида mailto: в HTML
emails = extract_emails_from_text(html)
emails.update(find_mailto_links(html))
# 4. Возвращаем уникальный список адресов
return emails
"""
Итерируем внутренние ссылки в пределах одного домена и собираем email-адреса.
Особенности:
Ограничение страниц (max_pages) для безопасной остановки
Проверка принадлежности ссылки к базовому домену
Исключение повторных обходов
Опциональное уважение 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 # Импортируем lxml, чтобы он был доступен для BeautifulSoup
from urllib import robotparser # стандартный парсер robots.txt
# Используем функции из предыдущего блока:
# - make_session()
# - scrape_emails_from_url()
import re
# Общий регулярное выражение для email-адресов
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]:
"""Собирает email-адреса с указанной страницы URL."""
emails: Set[str] = set()
try:
resp = session.get(url, timeout=getattr(session, "_default_timeout", 10.0))
resp.raise_for_status()
# Регулярное выражение для поиска email-адресов
# Примечание: этот регекс не идеален, но достаточен для типовых случаев
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:
"""Создает и возвращает объект сессии requests с базовыми настройками."""
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",
# Не задаем Accept-Encoding, чтобы избежать проблем с br без brotli
"Connection": "keep-alive",
})
return session
def same_host(url: str, base_netloc: str) -> bool:
"""True, если ссылка принадлежит тому же хосту (домену/субдомену)."""
return urlparse(url).netloc == base_netloc
def load_robots(start_url: str, user_agent: str = "EmailScraper") -> robotparser.RobotFileParser:
"""Загружает robots.txt и возвращает парсер для проверки разрешений."""
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]:
"""
Обходит страницы в пределах домена и возвращает уникальные email-адреса.
- max_pages: ограничение на количество посещенных страниц.
- delay_sec: пауза между запросами.
- respect_robots: если True — проверяет правила доступа.
- include_subdomains: если True — разрешает поддомены (www и т.д.).
"""
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
if respect_robots and rp is not None:
try:
if not rp.can_fetch("EmailScraper/1.0", url):
continue
except Exception:
pass
# Один запрос: и для email, и для ссылок
try:
resp = session.get(url, timeout=10)
resp.raise_for_status()
html_text = resp.text or ""
except requests.RequestException:
continue
visited.add(url)
# Пропускаем не-HTML страницы
ctype = resp.headers.get("Content-Type", "")
if ctype and "text/html" not in ctype:
continue
# Сбор email-адресов
for m in EMAIL_RE.findall(html_text):
all_emails.add(m.lower())
# Парсинг ссылок
soup = BeautifulSoup(html_text, "lxml")
# Email из 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="Скрапер email-адресов, который обходит страницы в пределах сайта и выводит найденные адреса."
)
parser.add_argument(
"start_url",
help="Начальный URL, например: https://example.com"
)
parser.add_argument(
"--max-pages",
type=int,
default=100,
dest="max_pages",
help="Максимальное количество страниц для обхода (по умолчанию: 100)"
)
parser.add_argument(
"--delay",
type=float,
default=0.5,
help="Задержка между запросами в секундах (по умолчанию: 0.5)"
)
parser.add_argument(
"--no-robots",
action="store_true",
help="Игнорировать robots.txt (используйте с осторожностью)"
)
scope = parser.add_mutually_exclusive_group()
scope.add_argument(
"--include-subdomains",
dest="include_subdomains",
action="store_true",
default=True,
help="Включать поддомены (по умолчанию)"
)
scope.add_argument(
"--exact-host",
dest="include_subdomains",
action="store_false",
help="Ограничить обход только точным хостом (без поддоменов)"
)
parser.add_argument(
"--output",
type=str,
default=None,
help="Необязательно: путь к файлу для сохранения найденных email-адресов (по одному в строке)"
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"Найдено {len(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"Не удалось записать выходной файл: {ex}")
main.py https://example.com
При запуске скрипта скрапинга Email на Python не всегда все просто: многие сайты специально скрывают адреса или выдают их только после рендера JavaScript. Ниже — что может мешать и как с этим работать.
1. Обфускация email
Сайты часто применяют различные методы, чтобы скрыть адрес от автоматических сборщиков:
2. Динамические страницы
Современные сайты часто подгружают контент с помощью JavaScript (например, через fetch или AJAX), поэтому обычный requests.get() может вернуть пустой HTML без нужных данных.
Практические подходы:
Следуя этому руководству, вы сможете организовать скрапинг Email на Python эффективно и безопасно. Качество полученных данных напрямую влияет на результаты маркетинговых кампаний, поэтому стоит сразу внедрять фильтрацию, валидацию и корректное сохранение результатов.
Мы получили вашу заявку!
Ответ будет отправлен на почту в ближайшее время.
С уважением proxy-seller.io!
Комментарии: 0