{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Основы программирования в Python\n", "\n", "*Алла Тамбовцева, НИУ ВШЭ*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Автоматизация работы в браузере: библиотека `selenium`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Библиотека `selenium` ‒ набор инструментов для интерактивной работы в браузере средствами Python. Вообще Selenium ‒ это целый проект, в котором есть разные инструменты. Мы рассмотрим один из самых распространенных ‒ Selenium WebDriver, модуль, который позволяется Python встраиваться в браузер и работать в нем как пользователь: кликать на ссылки и кнопки, заполнять формы, выбирать опции в меню и прочее. \n", "\n", "Мы будем использовать WebDriver для решения такой задачи. Необходимо выгрузить [все адреса]() участковых избирательных комиссий Ивановской области. Для этого нужно написать код, который будет открывать в окне браузера раздел *По номеру избирательного участка*, вводить в поле с номером номер участка и выбирать регион из предлагаемого списка. Итак, начнем." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Сначала загрузим веб-драйвер из библиотеки `selenium`. " ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from selenium import webdriver as wb" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если Python пишет `No module called selenium`, убедитесь, что у вас установлена эта библиотека. Самый надежный способ установить ее ‒ найти *Anaconda Command Prompt*, вписать строку `pip install selenium` и нажать *Enter*. Если *Anaconda Command Prompt* не находится, можно поступить так: запустить Jupyter Notebook, щелкнуть на черное окно консоли, нажать *Ctrl+Z* (остановить запуск Jupyter), а потом так же ввести в этом окне строку `pip install selenium` и нажать *Enter*.\n", "\n", "Затем нужно выбрать браузер и открыть новое окно через Python. Для этого нужно вызвать функцию, которая отвечает за открытие браузера. Мы будем вызывать Chrome." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "br = wb.Chrome()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если код выше не исполняется, скачайте файл с веб-драйвером [отсюда](https://sites.google.com/a/chromium.org/chromedriver/downloads), распакуйте архив и пропишите путь к файлу в круглых скобках (в примере файл с расширением exe на Windows). " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "br = wb.Chrome('C:/Users/student/Desktop/chromewebdriver/chromedriver.exe')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Плюсы такого подхода: его несложно реализовать, высоки шансы, что все заработает. Минусы такого подхода: мы не фиксируем путь к файлу глобально, всегда при работе с `selenium` придется прописывать строчку с путем. Для того, чтобы Python глобально знал, где ему искать драйвер, нужно добавить путь к нему в переменные среды (*environment variables*).\n", "\n", "Если все сработает нормально, то откроется новое окно Chrome (пока пустое). Затем мы добавим ссылку на страницу, которую мы должны открыть в браузере." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "br.get(\"http://www.cikrf.ru/services/lk_address/?do=find_by_uik\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ура, страница открылась. Что на этой странице есть интересного? Два поля: ввод номера участка и регион. Сохраним номер участка в переменную `n_uik`, а регион ‒ в `reg`." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "n_uik = 244\n", "reg = \"Ивановская область\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Вопрос: как эти два поля заполнить? Нужно найти их на странице, открытой в браузере, и вписать туда нужные строки. Только сделать это нужно через Python. Воспользуемся инструментом CSS Selector (установить расширение для Chrome можно [здесь](https://chrome.google.com/webstore/detail/copy-css-selector/kemkenbgbgodoglfkkejbdcpojnodnkg)). Для этого нужно открыть страницу в обычном браузере и кликнуть на расширение в правом углу. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![](css.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь, когда мы будем наводить курсор мыши на объект на странице в таком режиме и кликать, внизу будет отображаться его название в css. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![](css-uik.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь осталось зафиксировать поле с таким названием и ввести туда номер УИКа. " ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "# находим поле с #uik и сохраняем\n", "uik_field = br.find_element_by_css_selector(\"#uik\")\n", "\n", "# вводим номер УИКа в поле - метод send_keys\n", "uik_field.send_keys(n_uik)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![](css-enter.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ура, получилось. А как быть с регионом? Там же не поле ввода, а целое выпадающее меню с опциями... Нужно сначала сгрузить все опции, а потом написать пару строчек кода, которые будут находить подходящий регион. Почему так сложно? В случае одной Ивановской области мы можем просто посчитать, какой по счету будет соответствующая опция. Но если мы хотим написать более универсальный код, то лучше сразу учесть, что название региона может быть любым. К тому же, в нашем случае номер опции не совпадает с порядковым номером региона, если упорядочить названия по алфавиту (проверено на собственном опыте)." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[,\n", " ,\n", " ,\n", " ]" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# region_field - поле для выбора региона, нашли по названию\n", "region_field = br.find_element_by_name(\"subject\")\n", "\n", "# внутри поля для региона нашли все опции - все объекты с тэгом option\n", "options = region_field.find_elements_by_tag_name(\"option\")\n", "\n", "# несколько элементов в начале списка\n", "options[0:4]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Список опций получили. Теперь осталось научить Python выбирать нужную в браузере. Сейчас будет немного специфический код с использованием lambda-функции. Он позволяет поймать в списке опций ту, которая совпадает с `reg`, а потом запомнить ее индекс." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "# функция lamda x ловит случаи, где текст опции совпадает с reg\n", "# filter - фильтрует такие случаи в списке\n", "# затем результат filter превращаем в список и берем из него единственный элемент,\n", "# элемент с индексом 0\n", "\n", "reg_option = list(filter(lambda x : x.text == reg, options))[0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Отлично. Выбираем Ивановскую область в браузере:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "index = options.index(reg_option)\n", "options[index].click() # кликаем - выбираем опцию" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![](css-enter2.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Осталось только кликнуть на кнопку *Отправить запрос*. Сначала найдем ее с помощью CSS Selector, а потом кликнем по ней ‒ воспользуемся методом `.click()`:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "button = br.find_element_by_link_text(\"Отправить запрос\")\n", "button.click()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В браузере открылась страница с адресом избирательного участка. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![](page.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Осталось подгрузить `re` и найти на странице адрес участка с помощью регулярных выражений." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "import re" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "<_sre.SRE_Match object; span=(1636, 1814), match='Адрес помещения для голосования: 155800, Ивановск>" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p = re.search(r\"Адрес помещения для голосования: ([^<]+)\", br.page_source)\n", "p" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Адрес помещения для голосования: 155800, Ивановская область, городской округ Кинешма, город Кинешма, улица Григория Королева, дом 10, здание \"Кинешемский политехнический колледж\"'" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p.group(0) # текст адреса" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Получилось! Единственное, хорошо бы учесть случаи, когда адреса участка в таком виде на странице нет (такие случаи бывают: иногда страница создана не по шаблону, иногда указан адрес территориальной комиссии). Для этого нам понадобится условие. Добавим \"развилку\": пусть Python пробует найти адрес через указанное регулярное выражение, а если не найдет, то ищет его с помощью другого регулярного выражения. " ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "if p is None:\n", " p = re.search(r\"Адрес: ([^<]+)\", br.page_source)\n", " addr = p.group(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь у нас есть универсальный код, который позволяет найти адрес избирательного участка по номеру. В следующий раз мы оформим этот код в функцию, чтобы можно было подставлять в нее любой номер и регион, и применять ее в цикле, итерируя по номерам участков." ] } ], "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.5.2" } }, "nbformat": 4, "nbformat_minor": 2 }