Регулярные выражения в Python — гайд для аналитика

Коротко

Регулярные выражения (regex) — язык шаблонов для поиска, извлечения и замены текста. В Python за них отвечает модуль re. Аналитики используют регулярки для парсинга логов, чистки телефонов, извлечения чисел из текста, валидации форматов. На собеседованиях спрашивают основные метасимволы, разницу search и match, жадные и ленивые квантификаторы. Ниже — всё, что нужно знать.

Raw-строки

Регулярные выражения полны обратных слэшей: \d, \w, \s. Если писать обычную строку, Python обработает \ как escape-символ. Поэтому паттерны всегда пишут в raw-строках с префиксом r:

import re

# Без r — проблема: \d воспринимается как escape-последовательность
re.findall('\d+', 'user42')     # работает, но неявно

# С r — явно и правильно
re.findall(r'\d+', 'user42')    # ['42']

Привычка писать r"..." для паттернов убережёт от неочевидных багов. Подробнее про строки и raw-строки.

Основные метасимволы

Символ Что делает Пример
. Любой символ (кроме \n) r'a.b''aXb', 'a1b'
\d Цифра [0-9] r'\d+''42', '2026'
\w Буква, цифра или _ r'\w+''user_42'
\s Пробельный символ r'\s+' — пробел, таб, перенос
^ Начало строки r'^SELECT'
$ Конец строки r'\.csv$'
[] Набор символов r'[aeiou]' — гласные
| Или r'cat|dog'

Квантификаторы

# *  — 0 или более
# +  — 1 или более
# ?  — 0 или 1
# {n}   — ровно n
# {n,m} — от n до m

re.findall(r'\d{4}', 'Год 2026, код 42')
# ['2026']

re.findall(r'\d{1,3}', 'Год 2026, код 42')
# ['202', '6', '42']

Группы ()

Круглые скобки выделяют часть паттерна. findall возвращает содержимое групп, а не весь матч:

text = 'revenue: 1500000, users: 4200'

re.findall(r'(\w+): (\d+)', text)
# [('revenue', '1500000'), ('users', '4200')]

Именованные группы — через (?P<name>...):

m = re.search(r'(?P<metric>\w+): (?P<value>\d+)', text)
m.group('metric')  # 'revenue'
m.group('value')   # '1500000'

Основные функции модуля re

re.search — найти первое совпадение

line = '2026-04-09 ERROR: connection timeout'

m = re.search(r'(\d{4}-\d{2}-\d{2})\s+(\w+)', line)
if m:
    date = m.group(1)   # '2026-04-09'
    level = m.group(2)  # 'ERROR'

search ищет по всей строке и возвращает первый Match-объект или None.

re.match — только с начала строки

re.match(r'\d+', '42abc')    # Match — строка начинается с цифр
re.match(r'\d+', 'abc42')    # None — в начале буквы
re.search(r'\d+', 'abc42')   # Match — search ищет везде

match проверяет только начало строки. Если нужен поиск в произвольном месте — используйте search.

re.findall — все совпадения

text = 'DAU: 3200, WAU: 15000, MAU: 45000'

re.findall(r'\d+', text)
# ['3200', '15000', '45000']

Возвращает список строк (или кортежей, если есть группы).

re.sub — замена

# Убрать всё, кроме цифр
phone = '+7 (903) 123-45-67'
clean = re.sub(r'\D', '', phone)
# '79031234567'

# Замена с обратной ссылкой
re.sub(r'(\w+)@(\w+)', r'\1 [at] \2', 'user@company.ru')
# 'user [at] company.ru'

re.split — разбить по паттерну

line = 'one,  two;three   four'
re.split(r'[,;\s]+', line)
# ['one', 'two', 'three', 'four']

Мощнее обычного str.split() — разбивает по любому набору разделителей сразу.

re.compile — компиляция для производительности

Если паттерн используется многократно (в цикле, в функции), скомпилируйте его один раз:

pattern = re.compile(r'\d{4}-\d{2}-\d{2}')

logs = ['2026-04-09 INFO ...', 'no date here', '2026-04-10 ERROR ...']
dates = [pattern.search(line).group() for line in logs if pattern.search(line)]
# ['2026-04-09', '2026-04-10']

re.compile возвращает объект с теми же методами: .search(), .findall(), .sub(), .split(). Разница — паттерн компилируется один раз, а не при каждом вызове.

Практические примеры для аналитика

Извлечь числа из текста:

text = 'Выручка составила 1,500,000 руб. при 4200 пользователях'
numbers = re.findall(r'[\d,]+', text)
clean = [int(n.replace(',', '')) for n in numbers]
# [1500000, 4200]

Чистка телефонных номеров:

phones = ['+7 (903) 123-45-67', '89031234567', '8-903-123-45-67']
clean = [re.sub(r'\D', '', p) for p in phones]
# ['79031234567', '89031234567', '89031234567']

Парсинг строки лога:

log = '2026-04-09 14:23:01 [ERROR] users.py:42 Connection refused'
m = re.match(
    r'(?P<ts>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})'
    r' \[(?P<level>\w+)\]'
    r' (?P<file>\S+):(?P<line>\d+)'
    r' (?P<msg>.+)',
    log
)
m.groupdict()
# {'ts': '2026-04-09 14:23:01', 'level': 'ERROR',
#  'file': 'users.py', 'line': '42', 'msg': 'Connection refused'}

Валидация email (упрощённая):

def is_valid_email(email: str) -> bool:
    return bool(re.match(r'^[\w.+-]+@[\w-]+\.[\w.]+$', email))

is_valid_email('analyst@yandex.ru')    # True
is_valid_email('not-an-email')         # False

Частые ошибки

Жадные vs ленивые квантификаторы. По умолчанию * и + жадные — захватывают максимум. Добавьте ? для ленивого поведения:

html = '<b>bold</b> and <i>italic</i>'

re.findall(r'<.*>', html)     # ['<b>bold</b> and <i>italic</i>'] — жадный
re.findall(r'<.*?>', html)    # ['<b>', '</b>', '<i>', '</i>'] — ленивый

Забывают raw-строку. '\bword\b' — Python интерпретирует \b как backspace. Правильно: r'\bword\b'. Если регулярка ведёт себя странно — первым делом проверьте r"".

Путают match и search. match проверяет только начало строки. Если нужно найти паттерн в середине — нужен search. Подробнее о работе со строками в Python.

Вопросы с собеседований

Чем отличается re.match от re.search?match ищет совпадение только в начале строки. search — в любом месте. re.match(r'\d+', 'abc123') вернёт None, а re.search(r'\d+', 'abc123') найдёт '123'.

Что такое жадный и ленивый квантификатор? — Жадный (*, +) захватывает максимально длинную подстроку. Ленивый (*?, +?) — минимальную. Для парсинга HTML-тегов, кавычек и других парных конструкций обычно нужен ленивый.

Зачем нужен re.compile? — Компилирует паттерн в объект один раз. Если паттерн используется в цикле или вызывается многократно — compile экономит время, потому что не перекомпилирует при каждом вызове.

Как извлечь все числа из строки?re.findall(r'\d+', text) — вернёт список строк. Для float: re.findall(r'\d+\.?\d*', text). Если числа содержат разделители тысяч — re.findall(r'[\d,]+', text) с последующей чисткой.

Что вернёт findall, если в паттерне есть группы? — Содержимое групп, а не весь матч. re.findall(r'(\w+)=(\d+)', 'a=1 b=2') вернёт [('a', '1'), ('b', '2')]. Если группа одна — список строк. Если групп нет — список полных совпадений.

Больше вопросов по Python, SQL и аналитике — в тренажёре. Примеры вопросов по всем темам — на отдельной странице. Про форматирование строк — в гайде по f-строкам.

Открыть тренажёр в Telegram — вопросы по Python, pandas, SQL и аналитике. Бесплатно.

FAQ

Когда использовать регулярные выражения, а когда строковые методы?

Для простых задач (найти подстроку, разбить по разделителю, заменить фиксированный текст) — строковые методы (find, split, replace) быстрее и читаемее. Регулярки нужны, когда паттерн сложный: несколько вариантов разделителей, извлечение по шаблону, валидация формата.

Можно ли использовать regex в pandas?

Да. Серия строк (str-аксессор) поддерживает регулярные выражения: df['col'].str.extract(r'(\d+)'), df['col'].str.contains(r'\d+'), df['col'].str.replace(r'\s+', ' ', regex=True). Внутри используется тот же модуль re.

Как отладить сложную регулярку?

Разбивайте на части и тестируйте каждую отдельно. Используйте re.VERBOSE (флаг re.X) — он позволяет добавлять пробелы и комментарии внутри паттерна. Онлайн-инструменты вроде regex101.com визуализируют матчи в реальном времени.

Регулярные выражения — это must-have для аналитика?

Базовый уровень — да. Уметь написать паттерн для дат, чисел, email, разобрать строку лога — это ожидается. Сложные regex (рекурсия, lookahead/lookbehind) на собеседованиях спрашивают редко, но понимание жадности и групп — стандартный вопрос.