{ "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 для решения такой задачи. Необходимо выгрузить [все адреса](http://www.cikrf.ru/services/lk_address/) участковых избирательных комиссий Ивановской области. Для этого нужно написать код, который будет открывать в окне браузера раздел *По номеру избирательного участка*, вводить в поле с номером номер участка и выбирать регион из предлагаемого списка. Итак, начнём. Попробуем импортировать библиотеку:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import selenium" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если Python пишет `No module called selenium`, убедитесь, что у вас установлена эта библиотека. Самый надёжный способ установить её ‒ найти *Anaconda Command Prompt*, вписать строку `pip install selenium` и нажать *Enter*. А можно просто в Jupyter Notebook запустить следующую ячейку (`!` отвечает за режим исполнения ячейки, как будто запускаем в консоли)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!pip install selenium" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Загрузим веб-драйвер из библиотеки `selenium`. " ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from selenium import webdriver as wb" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Затем нужно выбрать браузер и открыть новое окно через 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), распакуйте архив и пропишите путь к файлу в круглых скобках (на Windows будет файл с расширением `.exe`). " ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "br = wb.Chrome('/Users/allat/Downloads/chromedriver')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Плюсы такого подхода: его несложно реализовать, высоки шансы, что все заработает. Минусы такого подхода: мы не фиксируем путь к файлу глобально, всегда при работе с `selenium` придется прописывать строчку с путем. Для того, чтобы Python глобально знал, где ему искать драйвер, нужно добавить путь к нему в переменные среды (*environment variables*).\n", "\n", "Если всё сработает нормально, то откроется новое окно Chrome (пока пустое). Затем мы добавим ссылку на страницу, которую мы должны открыть в браузере." ] }, { "cell_type": "code", "execution_count": 9, "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": 10, "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": 11, "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": [ "Ура, получилось. А как быть с регионом? Там же не поле ввода, а целое выпадающее меню с опциями... На самом деле, можно точно так же воспользоваться методом `send_keys()`:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "# region_field - поле для выбора региона, нашли по названию атрибута name в исходном коде страницы\n", "region_field = br.find_element_by_name(\"subject\")\n", "region_field.send_keys(reg)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![](css-enter2.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Осталось только кликнуть на кнопку *Отправить запрос*. Сначала найдем ее с помощью CSS Selector, а потом кликнем по ней ‒ воспользуемся методом `.click()`:" ] }, { "cell_type": "code", "execution_count": 14, "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": [ "Теперь считаем эту страницу с помощью `BeautifulSoup`:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "from bs4 import BeautifulSoup " ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "# br.page source - строка с исходным кодом страницы\n", "soup = BeautifulSoup(br.page_source, 'lxml')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Найдём все кусочки текста, они заключены в тэги `

`, и извлечём из них сам текст:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "texts = [a.text for a in soup.find_all('p')]" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['Уважаемый пользователь!',\n", " 'Данные о номере и адресе избирательного участка:',\n", " 'Участковая избирательная комиссия №244Номер Территориальной избирательной комиссии: 011',\n", " 'Адрес помещения УИК: 155800, Ивановская область, городской округ Кинешма, город Кинешма, улица Григория Королева, дом 10, здание \"Кинешемский политехнический колледж\"',\n", " 'Телефон УИК: 8-(49331)-21885',\n", " 'Адрес помещения для голосования: 155800, Ивановская область, городской округ Кинешма, город Кинешма, улица Григория Королева, дом 10, здание \"Кинешемский политехнический колледж\"',\n", " 'Телефон помещения для голосования: 8-(49331)-21885',\n", " 'В случае необходимости получения дополнительной информации, вы можете обратиться в избирательную комиссию субъекта Российской Федерации по месту жительства (адреса избирательных комиссий субъектов Российской Федерации - www.cikrf.ru/sites).']" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "texts" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Напишем небольшую lambda-функцию и заключим её в `filter()`, чтобы отобрать из списка выше только тот элемент, в котором встречается текст \"Адрес помещения для голосования:\" (если забыли про lambda-функции и `filter()`, см. [здесь](https://nbviewer.jupyter.org/github/allatambov/py-icef/blob/master/3-12-03/lambda-except-assert.ipynb))." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "address = list(filter(lambda x: \"Адрес помещения для голосования:\" in x, \n", " texts))[0] # извлекаем единственный элемент из списка - элемент с индексом 0" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Адрес помещения для голосования: 155800, Ивановская область, городской округ Кинешма, город Кинешма, улица Григория Королева, дом 10, здание \"Кинешемский политехнический колледж\"'" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "address" ] }, { "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.6.8" } }, "nbformat": 4, "nbformat_minor": 2 }