{ "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", "Для начала установим библиотеку:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!pip install selenium" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь скачаем драйвер для браузера в виде архива с одного из сайтов: [сайт](https://chromedriver.chromium.org/downloads) для Chrome, [сайт](https://github.com/mozilla/geckodriver/releases) для Mozilla. После скачивания архив необходимо распаковать и запомнить, где лежит файл с драйвером (`chromedriver.exe` на Windows, `chromedriver` на Mac).\n", "\n", "Импортируем из `selenium` модуль `webdriver` с сокращенным названием:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from selenium import webdriver as wb" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если используете драйвер для Chrome, необходимо прописать путь к файлу с драйвером внутри функции `Chrome()`:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "# пример для Mac\n", "br = wb.Chrome('/Users/allat/Downloads/chromedriver')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# пример для Windows\n", "br = wb.Chrome(r'C:\\\\Users\\\\allat\\\\Downloads\\\\chromedriver.exe')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если используете драйвер для Mozilla, можно ничего не прописывать, функция `Firefox()` сама поймет, где найти *geckodriver*:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "br = wb.Firefox()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если на Mac файл с драйвером упорно не хочет подсоединяться к Python, попробуйте выполнить действия в инструкции на странице курса (это займет некоторое время). После запуска строки кода выше в новом окне браузера открывается пустая страница. На эту страницу мы можем отправить ссылку на сайт и открыть его. \n", "\n", "Мы будем использовать *Selenium* для решения такой задачи. Необходимо выгрузить все адреса участковых избирательных комиссий Ивановской области. Для этого нужно написать код, который будет открывать в окне браузера раздел *По номеру избирательного участка*, вводить в поле с номером номер участка и выбирать регион из предлагаемого списка. Итак, начнем. Откроем страницу по ссылке в браузере:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "br.get('http://cikrf.ru/services/lk_address/?do=find_by_uik')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ура, страница открылась. Что на этой странице есть интересного? Два поля: ввод номера участка и регион. Как эти два поля заполнить? Нужно найти их на странице, открытой в браузере, и вписать туда нужные строки. Только сделать это нужно через Python. Воспользуемся исходным кодом страницы. В коде видно, что поле для ввода региона имеет тэг `input` и `id`, равный `\"uik\"`. Выполним поиск элемента по его id и сохраним результат в переменную `uik`: " ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "uik = br.find_element_by_id('uik')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь осталось ввести в поле выше номер УИКа. Для этого нам понадобиться метод `send_keys()`:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "uik.send_keys(201)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ура, получилось (посмотрите в окно браузера, которое было открыто через Python)! А как быть с регионом? Там же не поле ввода, а целое выпадающее меню с опциями... Но действовать можно аналогичным образом. Найдем выпадающее меню на странице и заметим, что оно имеет атрибут `class`, равный `\"subject\"`:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "reg = br.find_element_by_name(\"subject\")\n", "reg.send_keys(\"Ивановская область\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Осталось только кликнуть на кнопку *Отправить запрос*. Сначала найдем ее по тексту ссылки (ссылка активируется, когда мы кликаем на *Отправить запрос*), а потом кликнем по ней ‒ воспользуемся методом `.click()`:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "button = br.find_element_by_link_text(\"Отправить запрос\")\n", "button.click()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В браузере открылась страница с адресом избирательного участка. Теперь можно попробовать извлечь исходный код страницы и с помощью `BeautifulSoup` выполнить поиск адреса избирательного участка:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "from bs4 import BeautifulSoup\n", "soup = BeautifulSoup(br.page_source)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Соберем весь код с тэгами `p`, вытащим оттуда текст и склеим в одну строку:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Уважаемый пользователь! Данные о номере и адресе избирательного участка: Участковая избирательная комиссия №201Номер Территориальной избирательной комиссии: 008 Адрес помещения УИК: 153003, Ивановская область, городской округ Иваново, город Иваново, Фрунзенский район, улица Парижской Коммуны, дом 44, здание МБОУ \"Средняя школа № 39\" Телефон УИК: 8-(4932)-38-43-66 Адрес помещения для голосования: 153003, Ивановская область, городской округ Иваново, город Иваново, Фрунзенский район, улица Парижской Коммуны, дом 44, здание МБОУ \"Средняя школа № 39\" Телефон помещения для голосования: 8-(4932)-38-43-66 В случае необходимости получения дополнительной информации, вы можете обратиться в избирательную комиссию субъекта Российской Федерации по месту жительства (адреса избирательных комиссий субъектов Российской Федерации - www.cikrf.ru/sites).'" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pars = soup.find_all('p')\n", "text = \" \".join([p.text for p in pars])\n", "text" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь можем разбить по подстроке *Адрес помещения для голосования*, а потом еще по подстроке с телефоном:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'153003, Ивановская область, городской округ Иваново, город Иваново, Фрунзенский район, улица Парижской Коммуны, дом 44, здание МБОУ \"Средняя школа № 39\" '" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "addr = text.split('Адрес помещения для голосования: ')[1].split(\n", " 'Телефон помещения')[0]\n", "addr" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь можно написать функцию для извлечения адреса участка и применить ее к списку номеров участков, который легко создать можно через `range()`. Единственное, у нас нет гарантии, что код всегда будет срабатывать без ошибок – на странице может отсутствовать адрес или вообще отсутствовать текст про УИК. Для таких случаев пригодятся исключения – почитать про них можно, например, [здесь](https://github.com/allatambov/py-dat19/blob/63117ce655881fe9820c42bc879734588feff093/28-02/lambda-except-assert.ipynb)." ] } ], "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 }