Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
"Там просто регулярку написать" - говорили они.
Хочу показать вам небольшой кейс/задачу, которую передо мной поставили.
Суть - у нас есть лог (покажу самую интересную часть*), в котором много много разной информации (~100k-700k строк). Из этого лога нам нужно ~3% символов (именно, даже не строк). Затем, сделать таблички по полученным данным и визуализировать это всё. Делал я всё это на python, поэтому и регулярки написаны под python (спойлер: здесь только про регулярки).
Фактически, вся работа с логом сводилась к решению 3-ёх случаев:
Достать строку, в которой есть определенные слова.
Нам нужно найти конкретный реквест и достать данные из его аргументов.
Нам нужно найти конкретный реквест, достать данные из его аргументов, поймать печальку, что в аргументах не вся информация, которая нам нужна и достать еще данные, но по другому условию.
Для проверок наших регулярок, я использовал regex101.com, соответственно, настраиваем его под python.
Итак, часть Лога
Задача №1:
Нужно достать данные, когда запустилось приложение.
Мы знаем:
1) Строка начинается с даты, то есть с цифры.
2) Из документации (и здравого смысла, после просмотра лога),
что нужная нам строчка, содержит фразу "start_of_app_here".
Я рассчитываю, что вы уже немножко понимаете в регулярках, поэтому не буду расписывать, какой символ за что отвечает.
В итоге получаем такую регулярку:r"^\d.{,1000}start_of_app_here"
Проверяем.
Всё работает, но стоит добавить:
Лучше не использовать квантификаторы * и +, по моему опыту, они работают гораздо дольше квантификаторов с установленными границами {,}
Не забыть, при использовании метода findall третьим аргументов указать re.M, в противном случае, ^ будет восприниматься как начало текста, а не начало строки.
Пример:re.findall(r"your_reg", data, re.M)
Задача №2:
Достать аргументы запроса Pick_something.
Здесь немного поинтереснее и самое простое было бы достать всё, что начинается с Pick_something. У нас получилось бы что-то вроде:
r"Pick_something\(.{,1000}\)"
Но после этого нужно было бы избавляться от самого названия запроса и скобок.
Я предлагаю, сразу доставать только то, что внутри скобок. Для этого нам надо узнать, что делают (?<=text) и (?<!text). (?<=) - ищет text и не включает находку в вывод. (?<!) - тоже самое, только наоборот.
Из этого можем построить шаблон:
(?<=начало_искомого)что_нужно_достать(?<!конец_искомого).
В нашем случае:r"(?<=Pick_something\().{,1000}(?<!\))"
Задача №3:
Получаем от коллег по "самой лучшей работе в мире"* новую вводную, что в аргументах не вся информация, что нам нужна.
Обновленная задача звучит как: достать аргументы запроса и циферку команды QuantityInputCommand, которая идёт до запроса.
Первое, что я попробовал, используя прошлые "наработки" достать инфу также, только обернуть нужную мне информацию в группировочные скобки - ()
То есть:r"(?<=QuantityInputCommand:\s)(\d{,10})(?:\s|\S)+Pick_something((.{,200})(?<!))"
Нам нужна цифра, которая идёт непосредственно перед нашим запросом (Pick_something), то есть такая регулярка не отработала от слова совсем.
Есть вариант доставать всё подряд, и на пост обработке, когда уже будет список всех значений, сделать проверку на последовательность, то есть "если за цифрой из QuantityInputCommand идёт что-то начинающееся с ", то оставляем, в противном случае, удаляем".
Выглядит примерно так:r"(?<=QuantityInputCommand:\s)\d{,10}|(?<=Pick_something().{,200}(?<!))"
В итоге, мы можем указать на проверку отсутствие повтора, который нам всё ломает. Просто используем (?<!).
В нашей задаче решение* выглядит так:r"(?<=QuantityInputCommand:\s)(\d{,10})(?:\s|\S(?<!QuantityInputCommand:))+(?<=Pick_something()(.{,200})(?<!))"
Смотрим, стоит ли слева QuantityInputCommand ((?<=QuantityInputCommand:\s)
), забираем цифру и группируем её ((\d{,10})
), проверяем, не стоит ли после какого-либо текста (\s|\S) еще одна команда QuantityInputCommand ((?<!QuantityInputCommand:))+
) и дальше, по старой схеме, ищем нужный нам запрос и группируем и забираем его аргументы ((?<=Pick_something()(.{,200})(?<!))
)
Получаем:
Есть немного лишнего текста, но в группах у нас только нужные данные.
Я остановился на этом варианте, данные нужные получены, по скорости меня удовлетворило решение, наверняка, можно придумать что-то поэлегантнее, но в регулярках главное вовремя остановиться, доделывать их можно вечно*.
Итог
Основное, что я хотел показать, это 3-я регулярка, возможно, кому-то поможет и станет неким шаблоном. Довольно долго искал, где бы в лоб сказали: "делай вот так и будут тебе 1) нужные данные 2) по твоему условию 3) без лишних повторов".
Мне показалось, такая потребность может встречаться часто при работе с текстовыми данными, но в статьях про "Основы regex" подобного не нашел, есть подозрения, что такая штука должна в них быть, но мне так и не попалась.
Надеюсь, был кому-то полезен, заранее благодарю за оценку и\или комментарий)
Возможно, напишу небольшое продолжение, по дальнейшей обработке и визуализации в Jupyter Notebook на манер книги "Storytelling with data", если кому-то интересно, дайте знать в комментариях, пожалуйста :-)
P.S. * - по мнению автора.