Основы программирования в Python

Алла Тамбовцева, НИУ ВШЭ

Автоматизация работы в браузере: библиотека selenium

Библиотека selenium ‒ набор инструментов для интерактивной работы в браузере средствами Python. Вообще Selenium ‒ это целый проект, в котором есть разные инструменты. Мы рассмотрим один из самых распространенных ‒ Selenium WebDriver, модуль, который позволяется Python встраиваться в браузер и работать в нем как пользователь: кликать на ссылки и кнопки, заполнять формы, выбирать опции в меню и прочее.

Мы будем использовать WebDriver для решения такой задачи. Необходимо выгрузить все адреса участковых избирательных комиссий Ивановской области. Для этого нужно написать код, который будет открывать в окне браузера раздел По номеру избирательного участка, вводить в поле с номером номер участка и выбирать регион из предлагаемого списка. Итак, начнем.

Сначала загрузим веб-драйвер из библиотеки selenium.

In [1]:
from selenium import webdriver as wb

Если Python пишет No module called selenium, убедитесь, что у вас установлена эта библиотека. Самый надежный способ установить ее ‒ найти Anaconda Command Prompt, вписать строку pip install selenium и нажать Enter. Если Anaconda Command Prompt не находится, можно поступить так: запустить Jupyter Notebook, щелкнуть на черное окно консоли, нажать Ctrl+Z (остановить запуск Jupyter), а потом так же ввести в этом окне строку pip install selenium и нажать Enter.

Затем нужно выбрать браузер и открыть новое окно через Python. Для этого нужно вызвать функцию, которая отвечает за открытие браузера. Мы будем вызывать Chrome.

In [2]:
br = wb.Chrome()

Если код выше не исполняется, скачайте файл с веб-драйвером отсюда, распакуйте архив и пропишите путь к файлу в круглых скобках (в примере файл с расширением exe на Windows).

In [ ]:
br = wb.Chrome('C:/Users/student/Desktop/chromewebdriver/chromedriver.exe')

Плюсы такого подхода: его несложно реализовать, высоки шансы, что все заработает. Минусы такого подхода: мы не фиксируем путь к файлу глобально, всегда при работе с selenium придется прописывать строчку с путем. Для того, чтобы Python глобально знал, где ему искать драйвер, нужно добавить путь к нему в переменные среды (environment variables).

Если все сработает нормально, то откроется новое окно Chrome (пока пустое). Затем мы добавим ссылку на страницу, которую мы должны открыть в браузере.

In [3]:
br.get("http://www.cikrf.ru/services/lk_address/?do=find_by_uik")

Ура, страница открылась. Что на этой странице есть интересного? Два поля: ввод номера участка и регион. Сохраним номер участка в переменную n_uik, а регион ‒ в reg.

In [4]:
n_uik = 244
reg = "Ивановская область"

Вопрос: как эти два поля заполнить? Нужно найти их на странице, открытой в браузере, и вписать туда нужные строки. Только сделать это нужно через Python. Воспользуемся инструментом CSS Selector (установить расширение для Chrome можно здесь). Для этого нужно открыть страницу в обычном браузере и кликнуть на расширение в правом углу.

Теперь, когда мы будем наводить курсор мыши на объект на странице в таком режиме и кликать, внизу будет отображаться его название в css.

Теперь осталось зафиксировать поле с таким названием и ввести туда номер УИКа.

In [5]:
# находим поле с #uik и сохраняем
uik_field = br.find_element_by_css_selector("#uik")

# вводим номер УИКа в поле - метод send_keys
uik_field.send_keys(n_uik)

Ура, получилось. А как быть с регионом? Там же не поле ввода, а целое выпадающее меню с опциями... Нужно сначала сгрузить все опции, а потом написать пару строчек кода, которые будут находить подходящий регион. Почему так сложно? В случае одной Ивановской области мы можем просто посчитать, какой по счету будет соответствующая опция. Но если мы хотим написать более универсальный код, то лучше сразу учесть, что название региона может быть любым. К тому же, в нашем случае номер опции не совпадает с порядковым номером региона, если упорядочить названия по алфавиту (проверено на собственном опыте).

In [6]:
# region_field - поле для выбора региона, нашли по названию
region_field = br.find_element_by_name("subject")

# внутри поля для региона нашли все опции - все объекты с тэгом option
options = region_field.find_elements_by_tag_name("option")

# несколько элементов в начале списка
options[0:4]
Out[6]:
[<selenium.webdriver.remote.webelement.WebElement (session="1611df67266f4d68d03dc261a9c0c77a", element="0.4606233083419453-3")>,
 <selenium.webdriver.remote.webelement.WebElement (session="1611df67266f4d68d03dc261a9c0c77a", element="0.4606233083419453-4")>,
 <selenium.webdriver.remote.webelement.WebElement (session="1611df67266f4d68d03dc261a9c0c77a", element="0.4606233083419453-5")>,
 <selenium.webdriver.remote.webelement.WebElement (session="1611df67266f4d68d03dc261a9c0c77a", element="0.4606233083419453-6")>]

Список опций получили. Теперь осталось научить Python выбирать нужную в браузере. Сейчас будет немного специфический код с использованием lambda-функции. Он позволяет поймать в списке опций ту, которая совпадает с reg, а потом запомнить ее индекс.

In [7]:
# функция lamda x ловит случаи, где текст опции совпадает с reg
# filter - фильтрует такие случаи в списке
# затем результат filter превращаем в список и берем из него единственный элемент,
# элемент с индексом 0

reg_option = list(filter(lambda x : x.text == reg, options))[0]

Отлично. Выбираем Ивановскую область в браузере:

In [8]:
index = options.index(reg_option)
options[index].click() # кликаем - выбираем опцию

Осталось только кликнуть на кнопку Отправить запрос. Сначала найдем ее с помощью CSS Selector, а потом кликнем по ней ‒ воспользуемся методом .click():

In [9]:
button = br.find_element_by_link_text("Отправить запрос")
button.click()

В браузере открылась страница с адресом избирательного участка.

Осталось подгрузить re и найти на странице адрес участка с помощью регулярных выражений.

In [10]:
import re
In [11]:
p = re.search(r"Адрес помещения для голосования: ([^<]+)", br.page_source)
p
Out[11]:
<_sre.SRE_Match object; span=(1636, 1814), match='Адрес помещения для голосования: 155800, Ивановск>
In [12]:
p.group(0) # текст адреса
Out[12]:
'Адрес помещения для голосования: 155800, Ивановская область, городской округ Кинешма, город Кинешма, улица Григория Королева, дом 10, здание "Кинешемский политехнический колледж"'

Получилось! Единственное, хорошо бы учесть случаи, когда адреса участка в таком виде на странице нет (такие случаи бывают: иногда страница создана не по шаблону, иногда указан адрес территориальной комиссии). Для этого нам понадобится условие. Добавим "развилку": пусть Python пробует найти адрес через указанное регулярное выражение, а если не найдет, то ищет его с помощью другого регулярного выражения.

In [13]:
if p is None:
    p = re.search(r"Адрес: ([^<]+)", br.page_source)
    addr = p.group(1)

Теперь у нас есть универсальный код, который позволяет найти адрес избирательного участка по номеру. В следующий раз мы оформим этот код в функцию, чтобы можно было подставлять в нее любой номер и регион, и применять ее в цикле, итерируя по номерам участков.