{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Python для сбора и анализа данных\n", "\n", "*Алла Тамбовцева, НИУ ВШЭ*\n", "\n", "### Очень краткое введение в регулярные выражения" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Регулярные выражения – выражения, последовательности символов, которые позволяют искать совпадения в тексте. Выражаясь более формально, они помогают найти подстроки определенного вида в строке. Еще о регулярных выражениях можно думать как о шаблонах, в которые мы можем подставлять текст, и этот текст либо соответствует шаблону, либо нет.\n", "\n", "В самом простом случае в качестве регулярного выражения может использоваться обычная строка. Например, чтобы найти в предложении «Кошка сидит под столом.» слово «Кошка», ничего специального применять не нужно, достаточно воспользоваться оператором `in`. Если нас интересует слово «кошка» в любом регистре, то это уже более интересная задача. Правда, ее все еще можно решить без регулярных выражений, приведя все слова в строке к нижнему регистру. А что, если у нас будет текст подлиннее, и в нем необходимо обнаружить «кошку» в разных падежах? И еще производные слова вроде «кошечка»? Тут уже удобнее написать некоторый шаблон, чтобы не создавать длинный список слов с разными формами слова кошка. Давайте немного потренируемся (но не на кошках).\n", "\n", "Импортируем модуль `re` для работы с регулярными выражениями:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import re" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В качестве игрушечного примера возьмем обычную строку со странным текстом (текст невнятный, но отражает эволюцию смеха на пути к сессии):" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "data0 = \"ha haha ha-ha hah heh. hse.\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Найдем в этой строке все подстроки, которые соответствуют шаблону `h.h` – вместо точки может быть любой символ (буква, цифра, пробел и прочие знаки). Воспользуемся функцией `findall()`, она возвращает список совпадений:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['hah', 'hah', 'heh']" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# . – любой символ\n", "re.findall(\"h.h\", data0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если нужны именно точки, символ `.` нужно экранировать с помощью `\\`, в такой записи слэш показывает, что мы ищем именно точку, а не используем ее как специальный символ, принятый в синтаксисе регулярных выражений. Итак, найдем все «слова», начинающиеся с `h`, состоящие из четырех символов, последний из которых – точка:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['heh.', 'hse.']" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# \\. – экранируем точку для поиска точек\n", "re.findall(\"h..\\.\", data0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Точка – далеко не единственный специальный символ в регулярных выражениях. Так, символ `+` показывает, что нас интересуют случаи, когда элемент, стоящий слева от `+`, встречается не менее одного раза. Найдем подстроки, где точно есть буква `h`, а за ней стоит хотя бы одна буква `a`:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['ha', 'ha', 'ha', 'ha', 'ha', 'ha']" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# подстроки, где буква a встречается 1 и более раз (+)\n", "re.findall(\"ha+\", data0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если мы допускаем, что буквы `a` может не быть совсем, нам понадобится другой символ – символ `*` (ноль и более вхождений элемента, стоящего слева от `*`):" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['ha', 'ha', 'ha', 'ha', 'ha', 'ha', 'h', 'h', 'h', 'h']" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# подстроки, где буква a встречается 0 или 1 раз (*)\n", "re.findall(\"ha*\", data0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "А если нас интересуют случаи, когда какой-то символ встречается ноль раз или один раз, то пригодится символ `?`:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['haha', 'ha-ha']" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# подстроки, где дефис встречается 0 или 1 раз (?)\n", "re.findall(\"ha-?ha\", data0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Особую роль в регулярных выражениях играют скобки разного вида. Круглые скобки могут использоваться для объединения символов в группы, а квадратные – для перечисления всех вариантов, которые могут встретиться в некотором месте строки:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['hah ', 'heh.']" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# hah или heh с точкой или пробелом на конце\n", "# \\s – обозначение пробела (от space)\n", "\n", "re.findall(\"h[ae]h[\\.\\s]\", data0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В квадратные скобки также можно вписывать последовательности – готовые перечни известных символов:\n", "\n", "* `[a-z]`: строчные буквы английского алфавита;\n", "* `[A-Z]`: заглавные буквы английского алфавита;\n", "* `[а-я]`: строчные буквы русского алфавита;\n", "* `[А-Я]`: заглавные буквы русского алфавита;\n", "* `[0-9]`: цифры от 0 до 9.\n", "\n", "Проверим, есть ли в нашей строке цифры:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[]" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# нет, мы и не ждали\n", "re.findall(\"[0-9]\", data0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "А теперь проверим, есть ли в нашей строке последовательности ровно из трех строчных английских букв. Для этого пригодится еще один вид скобок – фигурные. В фигурных скобках указывают количество символов, которое необходимо найти:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['hah', 'hah', 'heh', 'hse']" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# последовательности из 3 букв\n", "re.findall(\"[a-z]{3}\", data0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если мы не знаем точное количество символов, но знаем интервал, его границы тоже можно указать в фигурных скобках через запятую:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['haha', 'hah', 'heh', 'hse']" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# последовательности из 3-4 английских букв\n", "re.findall(\"[a-z]{3,4}\", data0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Границы интервала можно опускать:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['haha', 'hah', 'heh', 'hse']" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# последовательности не менее, чем из 3 английских букв\n", "re.findall(\"[a-z]{3,}\", data0)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['ha',\n", " '',\n", " 'hah',\n", " 'a',\n", " '',\n", " 'ha',\n", " '',\n", " 'ha',\n", " '',\n", " 'hah',\n", " '',\n", " 'heh',\n", " '',\n", " '',\n", " 'hse',\n", " '',\n", " '']" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# последовательности не более, чем из 3 английских букв (пустые тоже есть)\n", "re.findall(\"[a-z]{,3}\", data0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Давайте повнимательнее посмотрим на поиск цифр и чисел, может пригодиться, например, для обработки номеров телефонов или адресов. Создадим другую, более вразумительную строку:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "data1 = \"+7(906)000-11-23 Alla T\" " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Пока просто найдем все цифры. Для поиска цифр вместо последовательности часто используют ее сокращенную версию – специальный символ `\\d` (от *digits*, экранируется с помощью слэша, чтобы не путать с обычной буквой *d*):" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['7', '9', '0', '6', '0', '0', '0', '1', '1', '2', '3']" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# тоже цифры, от digit\n", "re.findall(\"\\d\", data1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Цифры нашли, но ведь цифры в строке – далеко не всегда номер телефона, теоретически они могут быть и в адресе (как обычном, так и электронном), и в названии сайта. Напишем паттерн для поиска именно номера телефона в предположении, что:\n", "\n", "* телефон точно начинается с `+7`;\n", "* после `+7` обязательно стоят скобки вокруг первых трех цифр;\n", "* а вот дефисы между группами цифр могут отсутствовать:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['+7(906)000-11-23']" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# \\+7: экранируем +, чтобы не путать со специальным символом +\n", "# (\\d{3}\\): набор из 3 цифр в скобках\n", "# \\d{3}: набор из 3 цифр\n", "# -?: дефис встречается 0 или 1 раз\n", "# \\d{2}: набор из 2 цифр\n", "\n", "re.findall(\"\\+7\\(\\d{3}\\)\\d{3}-?\\d{2}-?\\d{2}\", data1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если допустить, что телефон может начинаться с `8`, а не только с `+7`, выражение будет выглядеть так:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['+7(906)000-11-23']" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# \\+?: + встречается 0 или 1 раз\n", "# после 7 или 8\n", "\n", "re.findall(\"\\+?[78]\\(\\d{3}\\)\\d{3}-?\\d{2}-?\\d{2}\", data1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Проверим на другой строке:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['+7(906)000-11-23', '8(906)111-00-23']" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "data2 = \"+7(906)000-11-23 Alla T 8(906)111-00-23\" \n", "re.findall(\"\\+?[78]\\(\\d{3}\\)\\d{3}-?\\d{2}-?\\d{2}\", data2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ну, а если допустить, что «приставки» `+7` или `8` может вообще не быть, то понадобится еще один `?`:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['+7(906)000-11-23', '8(906)111-00-23', '(999)233-00-21']" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "data3 = \"+7(906)000-11-23 Alla T 8(906)111-00-23 Alla Borisovna (999)233-00-21\" \n", "re.findall(\"\\+?[78]?\\(\\d{3}\\)\\d{3}-?\\d{2}-?\\d{2}\", data3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Итак, на этом краткое введение в регулярные выражения мы закончим, чуть позже увидим, зачем они могут понадобиться при парсинге, даже если мы выгружаем информацию с помощью BeautifulSoup." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 2 }