{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "-uy2mgLQFdGk" }, "source": [ "# Некоторые примеры парсинга с BeautifulSoup и Pandas\n", "\n", "*Алла Тамбовцева*" ] }, { "cell_type": "markdown", "metadata": { "id": "3oLSrfOiGDow" }, "source": [ "Импортируем библиотеки и функцию `BeautifulSoup` (понадобятся для разных примеров ниже):\n", "\n", "* `requests` для отправки запроса и получения кода HTML веб-страницы;\n", "* `bs4` для поиска тэгов в коде HTML;\n", "* `pandas` для обработки полученной информации и приведения ее к табличному виду." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "id": "k0qoHH-Ikb6h" }, "outputs": [], "source": [ "import requests\n", "import pandas as pd\n", "from bs4 import BeautifulSoup" ] }, { "cell_type": "markdown", "metadata": { "id": "2WxjHswNFEDj" }, "source": [ "### Сюжет 1. Парсинг таблиц: BeautifulSoup vs Pandas" ] }, { "cell_type": "markdown", "metadata": { "id": "UDMQF7bMGKkq" }, "source": [ "Для игрушечного примера создадим строку с кодом HTML для маленькой таблицы с двумя строками и тремя столбцами:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "id": "ObRr1F61kG1D" }, "outputs": [], "source": [ "# tr – table row (строка), \n", "# th – table header (ячейка с заголовком столбца), \n", "# td - table data (ячейка с данными)\n", "\n", "\n", "table = \"\"\"\n", "\n", " \n", " \n", " \n", "
idnamemark
1Anna7
3Ben6
\n", "\"\"\"" ] }, { "cell_type": "markdown", "metadata": { "id": "Wftv8kb7GR77" }, "source": [ "Посмотрим на то, как эта таблица выглядит. Для этого скопируем строку с кодом HTML без кавычек в ячейку Jupyter Notebook и изменим ее тип на *Markdown*, он позволит преобразовать код в таблицу и покажет ее на экране:" ] }, { "cell_type": "markdown", "metadata": { "id": "JYw3R8qYlt6L" }, "source": [ "\n", " \n", " \n", " \n", "
idnamemark
1Anna7
3Ben6
" ] }, { "cell_type": "markdown", "metadata": { "id": "O5CniC8KHKOa" }, "source": [ "Сконвертируем строку `table` (тип *string*) в объект `BeautifulSoup` для удобства поиска по тэгам:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "e-_8IU6zlvVz", "outputId": "e0495171-0389-4e8a-8c82-8588d804590d" }, "outputs": [ { "data": { "text/plain": [ "\n", "\n", "\n", "\n", "
idnamemark
1Anna7
3Ben6
\n", "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tab = BeautifulSoup(table)\n", "tab" ] }, { "cell_type": "markdown", "metadata": { "id": "J0gyLHfwHSoN" }, "source": [ "Найдем все ячейки с данными по тэгу `` и извлечем из кода HTML текст:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "5i2sI4cFmGWi", "outputId": "e8e42652-0e2f-41a8-9bc8-df0975f27707" }, "outputs": [ { "data": { "text/plain": [ "['1', 'Anna', '7', '3', 'Ben', '6']" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tds = tab.find_all(\"td\")\n", "values = [v.text for v in tds]\n", "values" ] }, { "cell_type": "markdown", "metadata": { "id": "IHuMrXw9Hsy3" }, "source": [ "Теперь, чтобы получить полноценную таблицу, нужно разбить список на части (две строки) и преобразовать в датафрейм. Эту задачу можно решать по-разному. Мы преобразуем список в массив и разобьем его на два массива одинаковой длины через функцию `split()` из библиотеки `numpy`:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "id": "VJBwBhbLmP-6" }, "outputs": [], "source": [ "import numpy as np" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 112 }, "id": "hDoM-Nxjm5gZ", "outputId": "b1d87c83-3386-497e-df4a-fc7035f5ab77" }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
012
01Anna7
13Ben6
\n", "
" ], "text/plain": [ " 0 1 2\n", "0 1 Anna 7\n", "1 3 Ben 6" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pd.DataFrame(np.split(np.array(values), 2)) " ] }, { "cell_type": "markdown", "metadata": { "id": "1766rlr6IX4I" }, "source": [ "Итак, мы посмотрели, как можно с помощью BeautifulSoup, Numpy и Pandas справиться с парсингом фрагмента HTML-кода, содержащим таблицу. На самом деле, можно было поступить гораздо проще – задействовать специальную функцию `read_html()` из библиотеки `pandas`. Вернемся к строке `table` с кодом HTML (преобразовывать ее в объект BeautifulSoup не нужно) и воспользуемся этой функцией:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 112 }, "id": "boJ5ycaGm-Lp", "outputId": "5f146743-ec3f-4511-973b-6d5dcc619713" }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
idnamemark
01Anna7
13Ben6
\n", "
" ], "text/plain": [ " id name mark\n", "0 1 Anna 7\n", "1 3 Ben 6" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# по умолчанию считывает все таблицы и возвращает все таблицы из HTML в виде списка\n", "# здесь одна – извлекаем элемент с индексом 0\n", "\n", "pd.read_html(table)[0] " ] }, { "cell_type": "markdown", "metadata": { "id": "Pm_QkNg-JLkw" }, "source": [ "Теперь перейдем к более продвинутой задаче. Зайдем на сайт Левада-Центра и найдем на [странице](https://www.levada.ru/indikatory/) с индикаторами одобрения органов власти первую таблицу с данными (под первым графиком, имеет индекс 0):" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "id": "dcwOWVsQngsZ" }, "outputs": [], "source": [ "page = requests.get(\"https://www.levada.ru/indikatory/\")\n", "soup = BeautifulSoup(page.text)\n", "tab0 = soup.find_all(\"table\", {\"class\" : \"datatable\"})[0]" ] }, { "cell_type": "markdown", "metadata": { "id": "LqtfEd4TJn2I" }, "source": [ "Преобразуем объект `tab0` в строку, так как функция `read_html()` умеет работать только с обычными строками или файлами, а не с объектами `BeautifulSoup`, и создаем датафрейм на основе кода HTML, указав, что первая строка таблицы содержит заголовки столбцов (`header`):" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 237 }, "id": "Aki6dqsKpSDT", "outputId": "4a0e9635-5937-49c7-c969-a17f0375e6f5" }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
08.199909.199910.199911.199912.199901.200002.200003.200004.200005.2000...02.202203.202204.202205.202206.202207.202208.202209.202210.202211.2022
031536580798475707772...71838283838383777979
133272012131017211517...27151715161515211918
237201588789811...1212122223
\n", "

3 rows × 278 columns

\n", "
" ], "text/plain": [ " 08.1999 09.1999 10.1999 11.1999 12.1999 01.2000 02.2000 03.2000 \\\n", "0 31 53 65 80 79 84 75 70 \n", "1 33 27 20 12 13 10 17 21 \n", "2 37 20 15 8 8 7 8 9 \n", "\n", " 04.2000 05.2000 ... 02.2022 03.2022 04.2022 05.2022 06.2022 \\\n", "0 77 72 ... 71 83 82 83 83 \n", "1 15 17 ... 27 15 17 15 16 \n", "2 8 11 ... 1 2 1 2 1 \n", "\n", " 07.2022 08.2022 09.2022 10.2022 11.2022 \n", "0 83 83 77 79 79 \n", "1 15 15 21 19 18 \n", "2 2 2 2 2 3 \n", "\n", "[3 rows x 278 columns]" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df = pd.read_html(str(tab0), header = 0)[0]\n", "df" ] }, { "cell_type": "markdown", "metadata": { "id": "gtS8g-ynJ8pZ" }, "source": [ "Ура! Одной строчкой кода мы получили красивую таблицу вместо «сырого» фрагмента HTML. Давайте транспонируем полученный датафрейм, чтобы получить более привычный вариант таблицы (и удобный для визуализации, если нас интересует динамика процента одобряющий и неодобряющих):" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "id": "sU4UPKl-peUR" }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
012
08.1999313337
09.1999532720
10.1999652015
11.199980128
12.199979138
............
07.202283152
08.202283152
09.202277212
10.202279192
11.202279183
\n", "

278 rows × 3 columns

\n", "
" ], "text/plain": [ " 0 1 2\n", "08.1999 31 33 37\n", "09.1999 53 27 20\n", "10.1999 65 20 15\n", "11.1999 80 12 8\n", "12.1999 79 13 8\n", "... .. .. ..\n", "07.2022 83 15 2\n", "08.2022 83 15 2\n", "09.2022 77 21 2\n", "10.2022 79 19 2\n", "11.2022 79 18 3\n", "\n", "[278 rows x 3 columns]" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df = df.transpose() \n", "df" ] }, { "cell_type": "markdown", "metadata": { "id": "7khV7uQLKdNx" }, "source": [ "Обработаем этот датафрейм:\n", "\n", "* добавим названия столбцов;\n", "* извлечем даты из названией строк (атрибут `index`) и сохраним их в отдельный столбец;\n", "* в качестве названий строк добавим набор целых чисел от 0 до 277;\n", "* переставим столбцы местами – выбирем их в нужном порядке и перезаписываем `df`;" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 424 }, "id": "EW0HrUhVpjg5", "outputId": "3a5defa6-40d8-4d5a-c1c0-b654ccebac33" }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
dateyesnono answer
008.1999313337
109.1999532720
210.1999652015
311.199980128
412.199979138
...............
27307.202283152
27408.202283152
27509.202277212
27610.202279192
27711.202279183
\n", "

278 rows × 4 columns

\n", "
" ], "text/plain": [ " date yes no no answer\n", "0 08.1999 31 33 37\n", "1 09.1999 53 27 20\n", "2 10.1999 65 20 15\n", "3 11.1999 80 12 8\n", "4 12.1999 79 13 8\n", ".. ... ... .. ...\n", "273 07.2022 83 15 2\n", "274 08.2022 83 15 2\n", "275 09.2022 77 21 2\n", "276 10.2022 79 19 2\n", "277 11.2022 79 18 3\n", "\n", "[278 rows x 4 columns]" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.columns = [\"yes\", \"no\", \"no answer\"]\n", "df[\"date\"] = df.index\n", "df.index = range(0, 278) \n", "df = df[[\"date\", \"yes\", \"no\", \"no answer\"]]\n", "df" ] }, { "cell_type": "markdown", "metadata": { "id": "fI4q8fExLaGJ" }, "source": [ "Проверим типы столбцов:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "5uRoBmkbr1-q", "outputId": "ec6bab90-ff41-4418-e852-3d957646e1d8" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "RangeIndex: 278 entries, 0 to 277\n", "Data columns (total 4 columns):\n", "date 278 non-null object\n", "yes 278 non-null int64\n", "no 278 non-null int64\n", "no answer 278 non-null int64\n", "dtypes: int64(3), object(1)\n", "memory usage: 8.8+ KB\n" ] } ], "source": [ "df.info()" ] }, { "cell_type": "markdown", "metadata": { "id": "ikR5ugSYLkku" }, "source": [ "Все отлично, числовые данные сохранены как целые числа, но дату стоит перевести в специальный формат `datetime`, иначе сортировать данные и визуализировать их будет сложно – текст с датами не будет упорядочиваться хронологически. Воспользуемся функцией `to_datetime()` и укажем, в каком формате у нас сохранены даты (про форматы и соответствующие аббревиатуры можно почитать [здесь](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior)):" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "id": "k812HffNr_Qx" }, "outputs": [], "source": [ "# format: в каком формате даты в строке\n", "# %m - шаблон для месяцев в числовом виде\n", "# %Y – шаблон для лет в числовом виде (Y - 4 цифры, y – 2 цифры)\n", "\n", "df[\"date\"] = pd.to_datetime(df[\"date\"], format = \"%m.%Y\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Тип столбца изменился:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "RangeIndex: 278 entries, 0 to 277\n", "Data columns (total 4 columns):\n", "date 278 non-null datetime64[ns]\n", "yes 278 non-null int64\n", "no 278 non-null int64\n", "no answer 278 non-null int64\n", "dtypes: datetime64[ns](1), int64(3)\n", "memory usage: 8.8 KB\n" ] } ], "source": [ "df.info()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "И для определенности к каждой дате (а у нас был только месяц и год) приписалось число – первый день месяца:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
dateyesnono answer
01999-08-01313337
11999-09-01532720
21999-10-01652015
31999-11-0180128
41999-12-0179138
\n", "
" ], "text/plain": [ " date yes no no answer\n", "0 1999-08-01 31 33 37\n", "1 1999-09-01 53 27 20\n", "2 1999-10-01 65 20 15\n", "3 1999-11-01 80 12 8\n", "4 1999-12-01 79 13 8" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.head()" ] }, { "cell_type": "markdown", "metadata": { "id": "1gtiUDLKMn2l" }, "source": [ "Проверим, что при визуализации динамики все будет в порядке, хронология не будет нарушена. Построим маленький базовый график с помощью функции `plot()`, без особых настроек, для примера. Импортируем модуль `pyplot`:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "id": "6bWX-roFsUdQ" }, "outputs": [], "source": [ "from matplotlib import pyplot as plt" ] }, { "cell_type": "markdown", "metadata": { "id": "wPJeLenUMwYG" }, "source": [ "Давайте в одной координатной плоскости построим сразу два графика – покажем динамику для процента одобряющих и неодобряющих деятельность Владимира Путина:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 283 }, "id": "Nc-pcM6ptHg5", "outputId": "4e6ae9dc-9295-4174-83d6-6ace24bf06db" }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/opt/anaconda3/lib/python3.7/site-packages/pandas/plotting/_matplotlib/converter.py:103: FutureWarning: Using an implicitly registered datetime converter for a matplotlib plotting method. The converter was registered by pandas on import. Future versions of pandas will require you to explicitly register matplotlib converters.\n", "\n", "To register the converters:\n", "\t>>> from pandas.plotting import register_matplotlib_converters\n", "\t>>> register_matplotlib_converters()\n", " warnings.warn(msg, FutureWarning)\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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XLiQefPmDUoG6PCfQiLE7ZOYlAeuUAyct++Fyl2De8ycWXBR31TUCRMm4Pf7qa6u5tlnnwVg165d7N+/nwsuuICDBw8CsH37drZt20Z8fDxTp07lzjvvJD8/n02bNgHwyCOPsHv37h4LVD311FOkpaXR3t7OwoULufLKK0lPTz9hudrW1lY+/fRTPvroI26++WZ2794NwJYtW1i3bh1Wq5XHHnss7JyvvfZaXnrpJR588EEqKio4fvw4p556Kvfffz/nnnsuTz31FI2NjSxatIjzzjvvhIW5eiNmLKLX78esNHCFYlShl/JYt24d119/PQCnnHIK48ePDxrw5cuXY7fbsVgsTJ8+neLi4j69xu9+9zvmzJnD4sWLKS0t5dChQ8CJy9Ved911ACxdupSmpiYaGxsBrcKh1Wo94Zy/+MUvBotlvfTSS1x99dUA/Pe//+WRRx5h7ty5LFu2DKfTSUlJSR//Y52JGQ9caeAKxSDRR085Whw9ehSj0UhWVhYnqskUHx8fvG80GvvURm3t2rW89957fPbZZyQkJAQNZzhCy9X2VOI21Fvuac5jx44lPT2dnTt38uKLL/LnP/85OP4f//gHU6dOjXj+vREzFlHVQlEoRg81NTXcdttt3HHHHQghWLp0Kc899xwABw8epKSkJGJDl5ycTHNzc9jnHA4HqampJCQksH//ftavXx987kTlavVGEOvWrcNut2O327sd+0Rzvvbaa/nFL36Bw+Fg1qxZAFx44YX8/ve/Dxr+bdu2RfT+TkTseOB+VQtFoYhl2tvbmTt3Lh6PB5PJxPXXX88999wDwO23385tt93GrFmzMJlMPP3005087xORnp7OGWecwcyZM7nooov45S9/GXxuxYoVPPHEE8yePZupU6eyePHi4HOh5WrtdnvQaAOkpqayZMkSmpqaeOqpp8K+7onmfNVVV3HXXXfxwAMPBMc/8MAD3H333cyePRspJQUFBT3WgYmUiMrJDhb9LSfr90sm3P9v7j5vMnefNyUKM1MoRjcjtZzscNJTudply5bx6KOPsmBBt+qtQ0K0yskOG55AsL/SwBUKhaKDmJBQvD7tKkHFgSsUisGip3K1a9euHdqJDICYcGl1A648cIVCoeggJixih4SiPHCFor8M5XqXon/09TOKCQMelFCUB65Q9AuLxUJdXZ0y4iMYKSV1dXVY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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "# label – чтобы назвать каждый график и отразить это в легенде\n", "\n", "plt.plot(df[\"date\"], df[\"yes\"], label = \"Approve\")\n", "plt.plot(df[\"date\"], df[\"no\"], label = \"Don't approve\")\n", "plt.legend() " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Отлично! График готов, похож на аналогичный график на сайте Левада-Центра. Конечно, размер и масштаб у этого графика не очень подходящие, но это всегда можно исправть (см. дополнительные материалы к занятию)." ] }, { "cell_type": "markdown", "metadata": { "id": "TN7_BVysNDm-" }, "source": [ "### Сюжет 2. Очень краткое введение в регулярные выражения" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Регулярные выражения – выражения, последовательности символов, которые позволяют искать совпадения в тексте. Выражаясь более формально, они помогают найти подстроки определенного вида в строке. Еще о регулярных выражениях можно думать как о шаблонах, в которые мы можем подставлять текст, и этот текст либо соответствует шаблону, либо нет. \n", "\n", "В самом простом случае в качестве регулярного выражения может использоваться обычная строка. Например, чтобы найти в предложении *Кошка сидит под столом.* слово *Кошка*, ничего специального применять не нужно, достаточно воспользоваться оператором `in`. Если нас интересует слово *кошка* в любом регистре, то это уже более интересная задача. Правда, ее все еще можно решить без регулярных выражений, приведя все слова в строке к нижнему регистру. А что, если у нас будет текст подлиннее, и в нем необходимо «обнаружить» *кошку* в разных падежах? И еще производные слова вроде *кошечка*? Тут уже удобнее написать некоторый шаблон, чтобы не создавать длинный список слов с разными формами слова *кошка*. Давайте немного потренируемся (но не на кошках)." ] }, { "cell_type": "markdown", "metadata": { "id": "vLHN_BrgNIXu" }, "source": [ "Импортируем модуль `re` для работы с регулярными выражениями:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "id": "_ytzc_5RthdZ" }, "outputs": [], "source": [ "import re" ] }, { "cell_type": "markdown", "metadata": { "id": "9-G5FC1tNJ-1" }, "source": [ "В качестве игрушечного примера возьмем обычную строку со странным текстом (текст невнятный, но отражает эволюцию смеха на пути к сессии):" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "id": "nV70p74VuSEz" }, "outputs": [], "source": [ "data0 = \"ha haha ha-ha hah heh. hse.\"" ] }, { "cell_type": "markdown", "metadata": { "id": "K3taewV-NMrF" }, "source": [ "Найдем в этой строке все подстроки, которые соответствуют шаблону `h.h` – вместо точки может быть любой символ (буква, цифра, пробел и прочие знаки). Воспользуемся функцией `findall()`, она возвращает список совпадений:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "6BOVThXLuhR4", "outputId": "6f97850c-a773-45b3-b65d-a01a911cbf86" }, "outputs": [ { "data": { "text/plain": [ "['hah', 'hah', 'heh']" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.findall(\"h.h\", data0)" ] }, { "cell_type": "markdown", "metadata": { "id": "aceJiknvNYvK" }, "source": [ "Если нужны именно точки, символ `.` нужно экранировать с помощью `\\`, в такой записи слэш показывает, что мы ищем именно точку, а не используем ее как специальный символ, принятый в синтаксисе регулярных выражений. Итак, найдем все «слова», начинающиеся с `h`, состоящие из четырех символов, последний из которых – точка:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "H4M6-2aPu1gA", "outputId": "e1b2faf1-47e3-43d6-bec1-2257300be63c" }, "outputs": [ { "data": { "text/plain": [ "['heh.', 'hse.']" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.findall(\"h..\\.\", data0)" ] }, { "cell_type": "markdown", "metadata": { "id": "Y-UXil5yOHBv" }, "source": [ "Точка – далеко не единственный специальный символ в регулярных выражениях. Так, символ `+` показывает, что нас интересуют случаи, когда элемент, стоящий слева от `+`, встречается не менее одного раза. Найдем подстроки, где точно есть буква `h`, а за ней стоит хотя бы одна буква `a`:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "4m7Kp9W2vNwQ", "outputId": "461de9c2-cda6-46c9-d3e4-c441d3d50a64" }, "outputs": [ { "data": { "text/plain": [ "['ha', 'ha', 'ha', 'ha', 'ha', 'ha']" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# подстроки с h с хотя бы с одной буквой a\n", "re.findall(\"ha+\", data0)" ] }, { "cell_type": "markdown", "metadata": { "id": "SvmjXnV7OQ-d" }, "source": [ "Если мы допускаем, что буквы `a` может не быть совсем, нам понадобится другой символ – символ `*` (ноль и более вхождений элемента, стоящего слева от `*`):" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Jx8-JzLHvrTI", "outputId": "5fa0964a-695b-4c5b-f77c-87f8aaa33d7f" }, "outputs": [ { "data": { "text/plain": [ "['ha', 'ha', 'ha', 'ha', 'ha', 'ha', 'h', 'h', 'h', 'h']" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# подстроки, где точно есть h, а буква a встречается или нет\n", "re.findall(\"ha*\", data0)" ] }, { "cell_type": "markdown", "metadata": { "id": "KZ1atElBQQkP" }, "source": [ "А если нас интересуют случаи, когда какой-то символ встречается ноль раз или один раз, то пригодится символ `?`:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "2bZYXp_Mv2e6", "outputId": "8f390bfe-48a9-4b79-92ed-b60866e21a6c" }, "outputs": [ { "data": { "text/plain": [ "['haha', 'ha-ha']" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# подстроки haha или ha-ha, с дефисом посередине и без него\n", "re.findall(\"ha-?ha\", data0)" ] }, { "cell_type": "markdown", "metadata": { "id": "lhqrrDl2SNoY" }, "source": [ "Особую роль в регулярных выражениях играют скобки разного вида. Круглые скобки могут использоваться для объединения символов в группы, а квадратные – для перечисления всех вариантов, которые могут встретиться в некотором месте строки:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['hah ', 'heh.']" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# hah или heh с точкой или пробелом на конце\n", "# \\s – обозначение пробела (от space)\n", "\n", "re.findall(\"h[ae]h[\\.\\s]\", data0)" ] }, { "cell_type": "markdown", "metadata": { "id": "lhqrrDl2SNoY" }, "source": [ "В квадратные скобки также можно вписывать последовательности – готовые перечни известных символов:\n", "\n", "* `[a-z]`: строчные буквы английского алфавита;\n", "* `[A-Z]`: заглавные буквы английского алфавита;\n", "* `[а-я]`: строчные буквы русского алфавита;\n", "* `[А-Я]`: заглавные буквы русского алфавита;\n", "* `[0-9]`: цифры от 0 до 9.\n", "\n", "Проверим, есть ли в нашей строке цифры:" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[]" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.findall(\"[0-9]\", data0) # нет, мы и не ждали" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "А теперь проверим, есть ли в нашей строке последовательности ровно из трех строчных английских букв. Для этого пригодится еще один вид скобок – фигурные. В фигурных скобках указывают количество символов, которое необходимо найти:" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "fc8krE_DwZq5", "outputId": "dbb589fe-3f26-44c0-d14c-3d706cfa0537" }, "outputs": [ { "data": { "text/plain": [ "['hah', 'hah', 'heh', 'hse']" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# последовательности ровно из трех английских букв\n", "re.findall(\"[a-z]{3}\", data0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если мы не знаем точное количество символов, но знаем интервал, его границы тоже можно указать в фигурных скобках через запятую:" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['haha', 'hah', 'heh', 'hse']" ] }, "execution_count": 28, "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": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['haha', 'hah', 'heh', 'hse']" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# последовательности не менее, чем из 3 английских букв\n", "re.findall(\"[a-z]{3,}\", data0)" ] }, { "cell_type": "code", "execution_count": 30, "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": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# последовательности не более, чем из 3 английских букв (пустые тоже есть)\n", "re.findall(\"[a-z]{,3}\", data0)" ] }, { "cell_type": "markdown", "metadata": { "id": "OevxBgKTTHa0" }, "source": [ "Давайте повнимательнее посмотрим на поиск цифр и чисел, может пригодиться, например, для обработки номеров телефонов или адресов. Создадим другую, более вразумительную строку:" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "id": "Sifp7LaUwv-Q" }, "outputs": [], "source": [ "data1 = \"+7(906)000-11-23 Alla Borisovna\" " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Пока просто найдем все цифры:" ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "GQF5D7Eow9AA", "outputId": "b18d9147-8094-4ced-ebd1-f8b9268d559c" }, "outputs": [ { "data": { "text/plain": [ "['7', '9', '0', '6', '0', '0', '0', '1', '1', '2', '3']" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.findall(\"[0-9]\", data1)" ] }, { "cell_type": "markdown", "metadata": { "id": "ECFXyCdOTNyN" }, "source": [ "Для поиска цифр вместо последовательности часто используют ее сокращенную версию – специальный символ `\\d` (от *digits*, экранируется с помощью слэша, чтобы не путать с обычной буквой *d*):" ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "gDVy6IVOTeSM", "outputId": "4a754bfd-02a8-41ec-e381-460dd838ad83" }, "outputs": [ { "data": { "text/plain": [ "['7', '9', '0', '6', '0', '0', '0', '1', '1', '2', '3']" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "re.findall(\"\\d\", data1)" ] }, { "cell_type": "markdown", "metadata": { "id": "8Bv02KhzThBU" }, "source": [ "Цифры нашли, но ведь цифры в строке – далеко не всегда номер телефона, теоретически они могут быть и в адресе (как обычном, так и электронном), и в названии сайта. Напишем паттерн для поиска именно номера телефона в предположении, что:\n", "\n", "* телефон точно начинается с `+7`;\n", "* после `+7` обязательно стоят скобки вокруг первых трех цифр;\n", "* а вот дефисы между группами цифр могут отсутствовать):" ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "l6qnvUmQxFwQ", "outputId": "b9166840-31d7-4e76-fae4-987ca05eff35" }, "outputs": [ { "data": { "text/plain": [ "['+7(906)000-11-23']" ] }, "execution_count": 34, "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": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['+7(906)000-11-23']" ] }, "execution_count": 35, "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": 36, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['+7(906)000-11-23', '8(906)111-00-23']" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "data2 = \"+7(906)000-11-23 Alla Borisovna 8(906)111-00-23 Alla Andreevna\" \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": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['+7(906)000-11-23', '8(906)111-00-23', '(999)233-00-21']" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "data3 = \"+7(906)000-11-23 Alla Borisovna 8(906)111-00-23 Alla Andreevna (999)233-00-21 Alla\" \n", "re.findall(\"\\+?[78]?\\(\\d{3}\\)\\d{3}-?\\d{2}-?\\d{2}\", data3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Итак, на этом краткое введение в регулярные выражения мы закончим, сейчас увидим, зачем они могут понадобиться при парсинге, даже если мы выгружаем информацию с помощью BeautifulSoup." ] }, { "cell_type": "markdown", "metadata": { "id": "RqvZjUlyTxkf" }, "source": [ "### Сюжет 3. Извлечение информации из кода JavaScript внутри HTML" ] }, { "cell_type": "markdown", "metadata": { "id": "q6wcCvIqT6fP" }, "source": [ "В конце курса по Python в магистратуре у нас было [домашнее задание](https://github.com/allatambov/PyMs2022/blob/main/pyall-hw05.ipynb) на парсинг страницы фильма «Не покидай...» с сайта www.kino-teatr.ru. Сайт некоммерческий, довольно дружелюбный, позволяет свободно выгружать информацию. Но у него есть одна особенность: число лайков и дизлайков, поставленных актерам пользователями, загружается на страницу динамически, то есть автоматически «подтягивается» с сервера при загрузке страницы в определенный момент времени. На практике это выливается в то, что найти нужную информацию по тэгам просто невозможно, ее нет в основном коде HTML. Как быть? Понять, как выглядит запрос данных, который отправляется на сервер, и выяснить, где хранятся нужные нам данные. Мы рассмотрим несложный случай, когда сайт забирает информацию из строки JSON, которая находится на странице, но внутри кода, написанного на JavaScript. Такое можно встретить на страницах с результатами каких-нибудь игр или на сайтах, посвященных динамике цен или курсу валют (другой вопрос, что не всегда JSON прямо так явно находится в том же файле, где и код HTML).\n", "\n", "Так как ранее мы обсуждали довольно базовый парсинг, в домашнем задании не требовалось собирать число лайков и дизлайков, эти значения были даны в виде готовых массивов. Обновим задачу – теперь нам нужно собрать имена актеров и их id, а затем «подтянуть» к этой информации число голосов за и против. \n", "\n", "Начало работы стандартное – загружаем код HTML страницы по ссылке и преобразуем его в объект BeautifulSoup:" ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "id": "TiFPGCxixdgf" }, "outputs": [], "source": [ "page = requests.get(\"https://www.kino-teatr.ru/kino/movie/sov/4319/titr/\")\n", "soup = BeautifulSoup(page.text)" ] }, { "cell_type": "markdown", "metadata": { "id": "ZBvVKfswYOzo" }, "source": [ "Ищем имена актеров – находим блоки с тэгами `
` с классом `film_name` и вытаскиваем из них «чистый» текст:" ] }, { "cell_type": "code", "execution_count": 39, "metadata": { "id": "opB9ZYbQzEmG" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['Лидия Федосеева-Шукшина', 'Вячеслав Невинный', 'Игорь Красавин', 'Варвара Владимирова', 'Светлана Селезнёва', 'Регина Разума', 'Альберт Филозов', 'Артём Тынкасов', 'Елена Антонова', 'Владимир Ставицкий']\n" ] } ], "source": [ "names_raw = soup.find_all(\"div\", {\"class\" : \"film_name\"}) \n", "names = [name.text for name in names_raw]\n", "\n", "print(names[0:10]) # первые 10 для примера" ] }, { "cell_type": "markdown", "metadata": { "id": "mlpJUJ2pYlrj" }, "source": [ "Теперь ищем id, они нам понадобятся для совмещения с информацией по числу голосов за и против:" ] }, { "cell_type": "code", "execution_count": 40, "metadata": { "id": "9oej_Yyk0GA_" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[
\n", "\n", "
Королева Флора — главная роль
\n", "
жена короля Теодора
\n", "
\n", "
,
\n", "\n", "
Король Теодор — главная роль
\n", "
\n", "
]\n" ] } ], "source": [ "divs = soup.find_all(\"div\", {\"class\" : \"actor_film_descript\"}) \n", "print(divs[0:2])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Как можно заметить, числовых id здесь нет, но это легко исправить – забрать значения атрибута `id` через метод `.get()` (вспоминаем о сходстве объектов BeautifulSoup и словарей), разбить их по символу `_` и забрать часть после `_` с индексом 1:" ] }, { "cell_type": "code", "execution_count": 41, "metadata": { "id": "9oej_Yyk0GA_" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['16801', '16800', '16803', '16802', '89473', '124124', '16804', '132138', '56008', '132139']\n" ] } ], "source": [ "ids = [i.get(\"id\").split(\"_\")[1] for i in divs]\n", "print(ids[0:10])" ] }, { "cell_type": "markdown", "metadata": { "id": "V1TUvqEDZtUd" }, "source": [ "Теперь воспользуемся тем, что функция `DataFrame()` из библиотеки pandas умеет превращать в датафрейм не только списки списков или словари, но и списки кортежей. Объединим элементы в список попарно через функцию `zip()` и сконвертируем перечень пар-кортежей в датафрейм:" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[('16801', 'Лидия Федосеева-Шукшина'),\n", " ('16800', 'Вячеслав Невинный'),\n", " ('16803', 'Игорь Красавин'),\n", " ('16802', 'Варвара Владимирова'),\n", " ('89473', 'Светлана Селезнёва'),\n", " ('124124', 'Регина Разума'),\n", " ('16804', 'Альберт Филозов'),\n", " ('132138', 'Артём Тынкасов'),\n", " ('56008', 'Елена Антонова'),\n", " ('132139', 'Владимир Ставицкий')]" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# напоминание: как выглядят элементы в zip()\n", "\n", "list(zip(ids, names))[0:10]" ] }, { "cell_type": "code", "execution_count": 43, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 1000 }, "id": "3r046aGb0i6M", "outputId": "12c956b7-cb32-4e25-f1ec-90113fb60623" }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
idname
016801Лидия Федосеева-Шукшина
116800Вячеслав Невинный
216803Игорь Красавин
316802Варвара Владимирова
489473Светлана Селезнёва
5124124Регина Разума
616804Альберт Филозов
7132138Артём Тынкасов
856008Елена Антонова
9132139Владимир Ставицкий
1072744Анатолий Рудаков
11132140Юрий Багинян
1262460Александр Денисов
13132141Валентин Букин
1483813Анатолий Голуб
15132142Саша Занько
16132143Вика Яблонская
172030314А. Баутенко
181973004С. Гецингер
191973001Дмитрий Диджиокас
201973002Владимир Зубенко
211973003Владимир Корпусь
221966145Ивар Кумник
231973005Анна Маланкина
241973000Валерий Мороз
25160325Леонид Нечаев
261973006И. Окал
271928641Анна Портная
281973007Ростислав Рахт
291973009В. Славуник
301973008Юрий Шульга
312088754Александр Макарцев
321973010Евгений Герчаков
331973011Сергей Дрейден
34222174Анатолий Тукиш
351973015Виктор Борцов
361973016Михаил Кокшенов
371973012Ольга Машная
381973017Леонид Нечаев
391973020Илья Рутберг
401973019Лариса Удовиченко
411973013Елена Цыплакова
421973014Борис Шувалов
431973018Игорь Ясулович
442496375Н. Острова
452496376Анатолий Тукиш
461914304Павел Бабаков
472496373Улдис-Янис Вейспалс
482022950Сергей Головкин
\n", "
" ], "text/plain": [ " id name\n", "0 16801 Лидия Федосеева-Шукшина\n", "1 16800 Вячеслав Невинный\n", "2 16803 Игорь Красавин\n", "3 16802 Варвара Владимирова\n", "4 89473 Светлана Селезнёва\n", "5 124124 Регина Разума\n", "6 16804 Альберт Филозов\n", "7 132138 Артём Тынкасов\n", "8 56008 Елена Антонова\n", "9 132139 Владимир Ставицкий\n", "10 72744 Анатолий Рудаков\n", "11 132140 Юрий Багинян\n", "12 62460 Александр Денисов\n", "13 132141 Валентин Букин\n", "14 83813 Анатолий Голуб\n", "15 132142 Саша Занько\n", "16 132143 Вика Яблонская\n", "17 2030314 А. Баутенко\n", "18 1973004 С. Гецингер\n", "19 1973001 Дмитрий Диджиокас\n", "20 1973002 Владимир Зубенко\n", "21 1973003 Владимир Корпусь\n", "22 1966145 Ивар Кумник\n", "23 1973005 Анна Маланкина\n", "24 1973000 Валерий Мороз\n", "25 160325 Леонид Нечаев\n", "26 1973006 И. Окал\n", "27 1928641 Анна Портная\n", "28 1973007 Ростислав Рахт\n", "29 1973009 В. Славуник\n", "30 1973008 Юрий Шульга\n", "31 2088754 Александр Макарцев\n", "32 1973010 Евгений Герчаков\n", "33 1973011 Сергей Дрейден\n", "34 222174 Анатолий Тукиш\n", "35 1973015 Виктор Борцов\n", "36 1973016 Михаил Кокшенов\n", "37 1973012 Ольга Машная\n", "38 1973017 Леонид Нечаев\n", "39 1973020 Илья Рутберг\n", "40 1973019 Лариса Удовиченко\n", "41 1973013 Елена Цыплакова\n", "42 1973014 Борис Шувалов\n", "43 1973018 Игорь Ясулович\n", "44 2496375 Н. Острова\n", "45 2496376 Анатолий Тукиш\n", "46 1914304 Павел Бабаков\n", "47 2496373 Улдис-Янис Вейспалс\n", "48 2022950 Сергей Головкин" ] }, "execution_count": 43, "metadata": {}, "output_type": "execute_result" } ], "source": [ "main = pd.DataFrame(zip(ids, names))\n", "main.columns = [\"id\", \"name\"] \n", "main" ] }, { "cell_type": "markdown", "metadata": { "id": "L_GYmQJOaOri" }, "source": [ "Теперь переходим к более сложной части – поиску голосов за и против. Просто найти на странице кнопки красного и зеленого цвета и забрать с них текст не получится:\n", "\n", "![](НП.jpeg)\n", "\n", "Поэтому для этого на нужно найти код JavaScript, где есть записи с числами `plus` и `minus` с привязкой к id актеров. Код JavaScript, если он не вынесен в отдельный файл, заключается в тэги `