Извлечение данных: email, телефоны, даты

Собираем практичные паттерны для типовых данных и честно обсуждаем их пределы.

Регулярка для «реального» формата — это всегда компромисс между простотой и полнотой; идеального паттерна для email или телефона не существует.

Извлечение email

Для практических задач достаточно простого паттерна: «символы слова и точки, собака, домен, точка, зона»:

import re

text = "Пишите на ivan@mail.ru или petr@example.com сегодня"
print(re.findall(r"[\w.+-]+@[\w-]+\.[\w.-]+", text))

Вывод:

['ivan@mail.ru', 'petr@example.com']

Класс [\w.+-]+ — имя до собаки (буквы, цифры, точки, плюсы), затем @, домен и зона. Этот паттерн ловит подавляющее большинство реальных адресов. Полная же спецификация email (RFC 5322) чудовищно сложна — гнаться за ней регуляркой не нужно.

Извлечение телефонов

Телефоны пишут по-разному: +7 999 123-45-67, 8(800)555-35-35. Вместо одного «идеального» паттерна часто проще выдрать все цифры и затем нормализовать:

import re

raw = "Звоните: +7 999 123-45-67 или 8(800)555-35-35"
for phone in re.findall(r"[\d()+\- ]{10,}", raw):
    digits = re.sub(r"\D", "", phone)
    print(digits)

Вывод:

79991234567
88005553535

Сначала [\d()+\- ]{10,} выхватил «телефоноподобные» куски (цифры, скобки, плюс, дефис, пробел длиной от 10), потом re.sub(r"\D", "", ...) убрал всё нецифровое. Чистка после грубого захвата надёжнее одной громоздкой регулярки.

Извлечение дат

Если формат даты фиксирован, паттерн прост и надёжен — например, ISO-дата ГГГГ-ММ-ДД:

import re

log = "Старт 2024-06-14, дедлайн 2024-12-31, релиз 2025-01-15"
print(re.findall(r"\d{4}-\d{2}-\d{2}", log))

Вывод:

['2024-06-14', '2024-12-31', '2025-01-15']

Где проходит предел регулярок

Регулярка проверяет форму, но не смысл. Паттерн \d{4}-\d{2}-\d{2} с радостью примет 2024-13-45 — формально это «4 цифры, дефис, 2 цифры...», хотя 13-го месяца не бывает. Проверку «настоящая ли это дата» делают уже кодом: попыткой datetime.strptime. Правило такое: регуляркой ловите кандидатов по форме, а корректность значения проверяйте обычной логикой.

import re
from datetime import datetime

for s in ["2024-06-14", "2024-13-45"]:
    if re.fullmatch(r"\d{4}-\d{2}-\d{2}", s):
        try:
            datetime.strptime(s, "%Y-%m-%d")
            print(s, "— настоящая дата")
        except ValueError:
            print(s, "— форма верна, но такой даты нет")

Вывод:

2024-06-14 — настоящая дата
2024-13-45 — форма верна, но такой даты нет

Итог

  • Для email и телефонов берите простой паттерн «на 99% случаев», не гонитесь за идеалом.
  • Телефоны удобно грубо захватить, а потом вычистить через re.sub(r"\D", "", ...).
  • Регулярка проверяет форму, но не смысл: корректность значения (реальность даты) проверяйте кодом.
Проверьте себя
1. Стоит ли пытаться написать «идеальную» регулярку под полную спецификацию email?
AДа, иначе валидация не работает
BEmail регуляркой проверить вообще нельзя
CНет: полная спецификация RFC слишком сложна; для практики берут простой паттерн «на 99% случаев»
DДа, это всего пара символов
2. Удобный приём для разнобойных телефонов — это...
AОдин огромный паттерн на все форматы
BНе использовать регулярки вовсе
CИскать только по +7
DГрубо захватить телефоноподобный кусок, а затем убрать всё нецифровое через re.sub(r"\D", "", ...)
3. Почему \d{4}-\d{2}-\d{2} примет строку 2024-13-45?
AРегулярка проверяет только форму (цифры и дефисы), но не смысл значения — корректность даты проверяют кодом
BЭто ошибка в паттерне
CПотому что 13-й месяц существует
DИз-за флага IGNORECASE