Регулярные выражения – выражения, последовательности символов, которые позволяют искать совпадения в тексте. Выражаясь более формально, они помогают найти подстроки определенного вида в строке. Еще о регулярных выражениях можно думать как о шаблонах, в которые мы можем подставлять текст, и этот текст либо соответствует шаблону, либо нет.
В самом простом случае в качестве регулярного выражения может использоваться обычная строка. Например, чтобы найти в предложении «Кошка сидит под столом.» слово «Кошка», ничего специального применять не нужно, достаточно воспользоваться оператором in
. Если нас интересует слово «кошка» в любом регистре, то это уже более интересная задача. Правда, ее все еще можно решить без регулярных выражений, приведя все слова в строке к нижнему регистру. А что, если у нас будет текст подлиннее, и в нем необходимо обнаружить «кошку» в разных падежах? И еще производные слова вроде «кошечка»? Тут уже удобнее написать некоторый шаблон, чтобы не создавать длинный список слов с разными формами слова кошка. Давайте немного потренируемся (но не на кошках).
Импортируем модуль re
для работы с регулярными выражениями:
import re
В качестве игрушечного примера возьмем обычную строку со странным текстом (текст невнятный, но отражает эволюцию смеха на пути к сессии):
data0 = "ha haha ha-ha hah heh. hse."
Найдем в этой строке все подстроки, которые соответствуют шаблону h.h
– вместо точки может быть любой символ (буква, цифра, пробел и прочие знаки). Воспользуемся функцией findall()
, она возвращает список совпадений:
# . – любой символ
re.findall("h.h", data0)
['hah', 'hah', 'heh']
Если нужны именно точки, символ .
нужно экранировать с помощью \
, в такой записи слэш показывает, что мы ищем именно точку, а не используем ее как специальный символ, принятый в синтаксисе регулярных выражений. Итак, найдем все «слова», начинающиеся с h
, состоящие из четырех символов, последний из которых – точка:
# \. – экранируем точку для поиска точек
re.findall("h..\.", data0)
['heh.', 'hse.']
Точка – далеко не единственный специальный символ в регулярных выражениях. Так, символ +
показывает, что нас интересуют случаи, когда элемент, стоящий слева от +
, встречается не менее одного раза. Найдем подстроки, где точно есть буква h
, а за ней стоит хотя бы одна буква a
:
# подстроки, где буква a встречается 1 и более раз (+)
re.findall("ha+", data0)
['ha', 'ha', 'ha', 'ha', 'ha', 'ha']
Если мы допускаем, что буквы a
может не быть совсем, нам понадобится другой символ – символ *
(ноль и более вхождений элемента, стоящего слева от *
):
# подстроки, где буква a встречается 0 или 1 раз (*)
re.findall("ha*", data0)
['ha', 'ha', 'ha', 'ha', 'ha', 'ha', 'h', 'h', 'h', 'h']
А если нас интересуют случаи, когда какой-то символ встречается ноль раз или один раз, то пригодится символ ?
:
# подстроки, где дефис встречается 0 или 1 раз (?)
re.findall("ha-?ha", data0)
['haha', 'ha-ha']
Особую роль в регулярных выражениях играют скобки разного вида. Круглые скобки могут использоваться для объединения символов в группы, а квадратные – для перечисления всех вариантов, которые могут встретиться в некотором месте строки:
# hah или heh с точкой или пробелом на конце
# \s – обозначение пробела (от space)
re.findall("h[ae]h[\.\s]", data0)
['hah ', 'heh.']
В квадратные скобки также можно вписывать последовательности – готовые перечни известных символов:
[a-z]
: строчные буквы английского алфавита;[A-Z]
: заглавные буквы английского алфавита;[а-я]
: строчные буквы русского алфавита;[А-Я]
: заглавные буквы русского алфавита;[0-9]
: цифры от 0 до 9.Проверим, есть ли в нашей строке цифры:
# нет, мы и не ждали
re.findall("[0-9]", data0)
[]
А теперь проверим, есть ли в нашей строке последовательности ровно из трех строчных английских букв. Для этого пригодится еще один вид скобок – фигурные. В фигурных скобках указывают количество символов, которое необходимо найти:
# последовательности из 3 букв
re.findall("[a-z]{3}", data0)
['hah', 'hah', 'heh', 'hse']
Если мы не знаем точное количество символов, но знаем интервал, его границы тоже можно указать в фигурных скобках через запятую:
# последовательности из 3-4 английских букв
re.findall("[a-z]{3,4}", data0)
['haha', 'hah', 'heh', 'hse']
Границы интервала можно опускать:
# последовательности не менее, чем из 3 английских букв
re.findall("[a-z]{3,}", data0)
['haha', 'hah', 'heh', 'hse']
# последовательности не более, чем из 3 английских букв (пустые тоже есть)
re.findall("[a-z]{,3}", data0)
['ha', '', 'hah', 'a', '', 'ha', '', 'ha', '', 'hah', '', 'heh', '', '', 'hse', '', '']
Давайте повнимательнее посмотрим на поиск цифр и чисел, может пригодиться, например, для обработки номеров телефонов или адресов. Создадим другую, более вразумительную строку:
data1 = "+7(906)000-11-23 Alla T"
Пока просто найдем все цифры. Для поиска цифр вместо последовательности часто используют ее сокращенную версию – специальный символ \d
(от digits, экранируется с помощью слэша, чтобы не путать с обычной буквой d):
# тоже цифры, от digit
re.findall("\d", data1)
['7', '9', '0', '6', '0', '0', '0', '1', '1', '2', '3']
Цифры нашли, но ведь цифры в строке – далеко не всегда номер телефона, теоретически они могут быть и в адресе (как обычном, так и электронном), и в названии сайта. Напишем паттерн для поиска именно номера телефона в предположении, что:
+7
;+7
обязательно стоят скобки вокруг первых трех цифр;# \+7: экранируем +, чтобы не путать со специальным символом +
# (\d{3}\): набор из 3 цифр в скобках
# \d{3}: набор из 3 цифр
# -?: дефис встречается 0 или 1 раз
# \d{2}: набор из 2 цифр
re.findall("\+7\(\d{3}\)\d{3}-?\d{2}-?\d{2}", data1)
['+7(906)000-11-23']
Если допустить, что телефон может начинаться с 8
, а не только с +7
, выражение будет выглядеть так:
# \+?: + встречается 0 или 1 раз
# после 7 или 8
re.findall("\+?[78]\(\d{3}\)\d{3}-?\d{2}-?\d{2}", data1)
['+7(906)000-11-23']
Проверим на другой строке:
data2 = "+7(906)000-11-23 Alla T 8(906)111-00-23"
re.findall("\+?[78]\(\d{3}\)\d{3}-?\d{2}-?\d{2}", data2)
['+7(906)000-11-23', '8(906)111-00-23']
Ну, а если допустить, что «приставки» +7
или 8
может вообще не быть, то понадобится еще один ?
:
data3 = "+7(906)000-11-23 Alla T 8(906)111-00-23 Alla Borisovna (999)233-00-21"
re.findall("\+?[78]?\(\d{3}\)\d{3}-?\d{2}-?\d{2}", data3)
['+7(906)000-11-23', '8(906)111-00-23', '(999)233-00-21']
Итак, на этом краткое введение в регулярные выражения мы закончим, чуть позже увидим, зачем они могут понадобиться при парсинге, даже если мы выгружаем информацию с помощью BeautifulSoup.