Понять регулярные выражения в Python
Поиск информации в неструктурированном тексте – одна из популярнейших задач. Существуют различные подходы к ее решению. Например, для извлечения именованных сущностей можно использовать библиотеку Natasha, можно искать вхождения определённых подстрок и почти всегда используются регулярные выражения. А регулярные выражения не очень понятны на первый взгляд.
В данной статье я постараюсь описать некоторый минимум, необходимый для понимания возможностей регулярных выражений, а также приведу примеры задач, для решения которых я использовал регулярные выражения. Использоваться будет библиотека Python 3.7 «re» и regex101.com, также, информация из статьи. Пример работы:
import re text = ''' ИНН ., ИНН 1234567890 Инн2 - 9876543210 просто числа 1111111111 1377 ''' smf = re.search('\d', text, re.M) smf[0] if smf else 'Не найдено'
Рассмотрим подробнее. После импорта библиотеки и создания переменной text была вызвана функция re.search(). Это одна из функций для реализации регулярных выражений в Python, принимает регулярку, ищет где нам нужно первое совпадение, флаг re.M обозначает многострочный поиск и возвращает объект re.Match с найденными координатами или None. Регулярное выражение в данном случае позволяет найти 10 чисел, идущих подряд. «\d» — это обозначение для числа, обозначает количество символов.
Отметим, что переменная text содержит в себе следующее:
\nИНН\n.,\nИНН 1234567890\nИнн2 - 9876543210\nпросто числа 1111111111\n1377\n
В Python re существуют следующие функции:
- re.match(pattern, string, flags=0) ищет pattern в string с флагами flags, возвращает объекты match или None. Результаты можно получить как match.groups() и match.group(num=0).
- re.search(pattern, string, flags=0) ищет pattern в string с флагами flags, возвращает первое совпадение как объект match или None.
- re.findall(pattern, string, flags=0) ищет pattern в string с флагами flags, возвращает объекты список совпадений или пустой список.
- re.sub(pattern, repl, string, max=0) ищет pattern в string и заменяет на repl, количество раз max, если оно указано.
- re.I — делает поиск нечувствительным к регистру.
- re.L — ищет слова в соответствии с текущим языком. Эта интерпретация затрагивает алфавитную группу (\w и \W), а также поведение границы слова (\b и \B).
- re.M — символ $ выполняет поиск в конце любой строки текста (не только конце текста) и символ ^ выполняет поиск в начале любой строки текста (не только в начале текста).
- re.S — изменяет значение точки (.) на совпадение с любым символом, включая новую строку.
- re.U— интерпретирует буквы в соответствии с набором символов Unicode. Этот флаг влияет на поведение \w, \W, \b, \B. В python 3+ этот флаг установлен по умолчанию.
- re.X— позволяет многострочный синтаксис регулярного выражения. Он игнорирует пробелы внутри паттерна (за исключением пробелов внутри набора [] или при экранировании обратным слешем) и обрабатывает не экранированный “#” как комментарий.
Далее, приведена таблица с используемыми обозначениями и простыми примерами использования:
Обозначение | Описание | Пример в нашей строке |
. | любой одиночный символ, кроме «\n» | «И» |
\ | символ для экранирования | для «\.» результат – «.» |
\w | любая буква, цифра или «_» | «И» |
\d | любая цифра | «1» |
\s | любой символ пробела, табуляции, перевода строки | «\n» |
\b | должен определять границу для \w символов, но в Python 3.7, видимо, не работает | в regex101 можно было бы использовать для поиска отдельно стоящих слов/цифр, например, «\b\d\b» позволяет искать три идущих подряд цифры |
* | ноль или больше совпадений | для «ИНН.*» результат: «ИНН» |
+ | одно или больше совпадений | для «ИНН.+» результат: «ИНН 1234567890» |
^ | начало строки | для «^\d+» результат: «1377» |
$ | конец строки | для «\d+$» результат: «1234567890» |
| | одно или другое | «а|5» — буква «а» или цифра «5» |
обозначает количество искомых символов | «\d» — 5 цифр, «\d» — 3-6 цифр, «\d» — 4 и более цифр | |
[ ] | обозначает список символов для поиска, при этом, «^» внутри таких скобок будет означать отрицание | «[дес]» — только буквы «д», «е», «с», «[ёа-я]» — все прописные буквы кириллицы (ё не входит в основной список), «[^0-9]» — не цифры |
() | обозначает группы | «(ИНН (\d))» вернёт две группы — ‘ИНН 9876543210’ и ‘9876543210’ |
В регулярных выражениях достаточно удобно реализованы логические отрицания, например, «\D» — это любой нецифровой символ. Аналогично и для других обозначений: «\W», «\S», «\B». Пару слов о «жадности» и «ленивости» регулярных выражений. По умолчанию, возвращается максимально длинное совпадение, например:
text = '"Рога", "Копыта" и "Ко"' re.findall('".+"', text, re.M)
Результат: [‘»Рога», «Копыта» и «Ко»‘]
Мы хотели найти «всё внутри двойных кавычек». И нашли всё, что заключено в кавычки, как единое совпадение. Но если нужно каждое совпадение в отдельности, следует использовать «ленивый поиск»:
text = '"Рога", "Копыта" и "Ко"' re.findall('".+?"', text, re.M)
Добавив «?» к конструкции «».+»», то есть, составив выражение «».+?»» мы получили каждое минимальное совпадение с конструкцией «всё внутри двойных кавычек».
Далее – опережающие и ретроспективные проверки или же, lookahead/lookbehind. Эти конструкции позволяют реализовать поиск чего-то после и/или до определённых выражений. То есть, мы можем, не используя группы (круглые скобки), вытащить, например, что-то между кавычек:
text = '"Рога", "Копыта" и "Ко"' re.findall('(?<=").+?(? wp-block-code">text = 'какие-то 146%, нужные проценты значение 54%, шелуха значение 987, ещё значение - 159%' re.findall('(?<=значение).+?(\d+)(?=%)', text, re.M)
Разберём пример подробнее. Допустим, нам нужно найти в тексте число, которое следует после определённого слова (у нас – «значение»), при этом, между словом и числом может быть пробел, тире, что-то ещё, и после числа точно должен быть «%». Для понимания конструкции выше понадобится описание опережающих и ретроспективных проверок:
Если вы подумали об указании множественных условий для таких проверок, что-то вроде, (?<=значение1|значение2) то спешу разочаровать, так как re говорит нам:
«error: look-behind requires fixed-width pattern»
И это печально. Следует учитывать, что некоторые особенности регулярок могут по-разному работать в разных реализациях, поэтому стоит «обкатывать» регулярки не только на regex101, но и в самом Python.
Таким образом, используя полученные знания можно начать писать собственные регулярные выражения и с их помощью быстро получать нужную информацию из неструктурированного текста.
«Пробенчмаркать уже это всё наконец» – тестирование инструментов для обработки данных на Python. Часть 1.