Регулярные выражения в 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) на собеседованиях спрашивают редко, но понимание жадности и групп — стандартный вопрос.