Реальные кейсы: парсинг логов и очистка текста

Собираем изученное в законченные практические задачи.

Сильная сторона регулярок — превращать неструктурированный текст (логи, выгрузки, разметку) в данные за пару строк.

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

Строка веб-сервера содержит IP, метод, путь и код ответа. Разберём её на части именованными группами:

import re

line = '127.0.0.1 - - [14/Jun/2024] "GET /index.html" 200'
pattern = r'(?P<ip>\d+\.\d+\.\d+\.\d+).*"(?P<method>\w+) (?P<path>\S+)" (?P<code>\d+)'
m = re.search(pattern, line)
print(m.group("ip"))
print(m.group("method"))
print(m.group("path"))
print(m.group("code"))

Вывод:

127.0.0.1
GET
/index.html
200

Разберём паттерн: \d+\.\d+\.\d+\.\d+ — четыре группы цифр через экранированные точки (IP), .* пропускает середину, затем в кавычках метод (\w+) и путь (\S+), и в конце код ответа. Имена групп делают результат самодокументируемым.

Удаление HTML-тегов

Чтобы выдрать чистый текст из простой разметки, убирают всё в угловых скобках. Ключ — класс [^>]+ вместо жадной .*, чтобы не съесть лишнее:

import re

html = "<p>Привет, <b>мир</b>!</p>"
print(re.sub(r"<[^>]+>", "", html))

Вывод:

Привет, мир!

Паттерн <[^>]+> читается как «открывающая скобка, любые символы кроме закрывающей, закрывающая скобка» — то есть один тег целиком. Класс [^>] не даёт совпадению перескочить через границу тега. (Для настоящего HTML это всё же грубо — об этом в следующем уроке.)

Нормализация текста

Часто текст приходит «грязным»: лишние пробелы, табы, переносы. Приведём его к одной строке с одиночными пробелами:

import re

dirty = "  Слишком   много \t пробелов\n и переносов  "
clean = re.sub(r"\s+", " ", dirty).strip()
print(repr(clean))

Вывод:

'Слишком много пробелов и переносов'

Связка re.sub(r"\s+", " ", text).strip() — рабочая лошадка очистки текста, которую вы будете использовать постоянно.

Извлечение всех чисел с дробями

Ещё типовая задача — собрать все числа, включая дробные, из текста:

import re

text = "Куплено 3 шт по 199.90, доставка 0 руб, итого 599.70"
print(re.findall(r"\d+\.\d+|\d+", text))

Вывод:

['3', '199.90', '0', '599.70']

Альтернация \d+\.\d+|\d+ сначала пробует дробное число, и только если не вышло — целое. Порядок важен: дробный вариант стоит первым, чтобы 199.90 не распалось на 199 и 90.

Итог

  • Именованные группы превращают строку лога в понятные поля.
  • Для тегов и кавычек используйте «отрицающий» класс ([^>], [^"]) вместо жадной .*.
  • re.sub(r"\s+", " ", text).strip() — стандартная очистка текста.
  • В альтернации с числами более длинный вариант (дробное) ставьте первым.
Проверьте себя
1. Почему для удаления HTML-тегов используют <[^>]+> вместо <.+>?
AТак короче
BКласс [^>] не даёт совпадению перескочить через закрывающую скобку, поэтому захватывается ровно один тег, а не всё между первой < и последней >
C<.+> вообще не работает
DЭто одно и то же
2. Почему в \d+\.\d+|\d+ дробный вариант стоит первым?
AАлфавитный порядок
BАльтернация пробует варианты слева направо; если поставить \d+ первым, число 199.90 распалось бы на 199 и 90
CПорядок не важен
DЧтобы ускорить поиск
3. Что делает re.sub(r"\s+", " ", text).strip()?
AУдаляет все пробелы
BСхлопывает любые группы пробельных символов в один пробел и убирает пробелы по краям
CРазбивает текст на слова
DСчитает количество слов
Поддержать проект