Продолжаем изучать парсинг с Python. В прошлый раз мы использовали библиотеки Requests и Beautifulsoup — но не всегда можно обойтись только ими. Сегодня поговорим о библиотеке Selenium.

Съемка и монтаж: Глеб Лиманский

С ее помощью мы можем имитировать действия пользователя в браузере, — то есть, сайт действительно будет открываться на компьютере, а затем скрипт будет выполнять прописанные нами действия.

В первом уроке по парсингу мы работали с сайтом «КиноПоиск» и его списком топ-250 фильмов. Это было довольно просто: при переходе со страницы на страницу ссылка менялась предсказуемо, и мы смогли прописать это условие в коде. Но есть сайты, где все работает иначе. 

Например, реестр с информацией о государственных муниципальных учреждениях. Попробуем найти в нем онкологические диспансеры и посмотрим, как будет выглядеть ссылка.

На скриншоте видно, что после нашего запроса ссылка не изменилась. Она не изменится и когда мы перейдем на следующую страницу. Как раз для таких случаев пригодится библиотека Selenium,— скрипт сам забьет нужный запрос в поисковую строку, нажмет «Показать», начнет собирать нужные нам данные и так далее.

Чтобы воспользоваться этой библиотекой, необходимо скачать ChromeDriver под вашу версию браузера Chrome (если версии не будут совпадать, ваш код не сработает). Распакуйте файл chromedriver, для удобства можно положить его в корневую директорию или на рабочий стол. 

Далее через Jupyter установим саму библиотеку с помощью pip install.

Теперь импортируем из Selenium все необходимые нам команды и функции. Также нам сегодня пригодятся библиотеки Beautifulsoup, sleep и tqdm — импортируем и их. 

Библиотека tqdm нужна для того, чтобы в Jupyter отображалась полоска, заполняемая по мере прогресса работы основного скрипта

Следующие шаги будут похожи на то, что мы делали в прошлом уроке, но у библиотеки будет другой синтаксис. Сначала запустим наш ChromeDriver, на macOS это делается так: создаем переменную browser, указываем команду Chrome и в кавычках прописываем путь до ранее распакованного ChromeDriver.

После выполнения команды откроется сам браузер.

Теперь мы можем управлять браузером с помощью команд. Чтобы попасть на сайт, создадим переменную url и укажем нужный сайт (в нашем примере: https://bus.gov.ru/registry). По аналогии с Requests, воспользуемся методом get и передаем на вход эту переменную.

Сайт должен загрузиться в нашем Chrome. Забьем критерий поиска — чтобы это сделать, как и в прошлый раз, изучим HTML-код страницы с помощью функции «Посмотреть код» (делать это лучше в обычном браузере, а не в управляемом Selenium).

Как видно в панели разработчика, здесь всё достаточно приятно и понятно, тег и класс выглядят уникальными. Но при этом стоит помнить об особенности Beautifulsoup, которая есть и в Selenium — если вы указываете запрос к какому-то элементу, запрос всегда будет выбирать первый элемент из кода. 

Поэтому сперва найдем этот тег на странице с помощью команды из группы find. Если после ввода find вы нажмете Tab на клавиатуре, появятся несколько параметров поиска на выбор: по имени класса, по CSS-селектору, по id и так далее.

Нам нужен tag_name, конкретно — input

Вся страница теперь — объект Selenium

Сообщение, как на скриншоте выше, показывает, что пока мы всё сделали правильно — объект был найден браузером. Можем попробовать также поиск по class_name. Но будьте внимательны: если в HTML-коде внутри названия класса есть пробелы, то это на самом деле несколько разных названий. Поэтому, если скопировать всё целиком, то Selenium выдаст ошибку. 

Для примера возьмем первый класс элемента (search-input) и снова получим ответ от Selenium. Есть еще способы найти нужный элемент. Например, кликнуть правой кнопкой мыши на нужный кусок кода и в контекстном меню Copy выбрать Copy XPath.

XPath — это прямой путь до элемента в дереве сайта

Снова воспользуемся командой find, но выберем поиск по xpath.

По аналогии в контекстном меню Copy можно выбрать Copy selector — тогда соответственно меняем критерий в find на css_selector.

Таким же образом можно искать ссылки по тексту на странице. В шапке виден, например, кликабельный текст «Результаты независимой оценки». Критерий для команды find в таком случае будет link_text. Чтобы вытащить саму ссылку, добавим команду get по атрибуту href.

Вернемся к полю ввода. Выберем одну из наших поисковых функций и зададим этой переменной название input_tab. Добавим в нее наш поисковый запрос с помощью функции send_keys и проверим, выполнился ли запрос в окне браузера, которым управляет ChromeDriver.

Просим Selenium поискать «онкологический диспансер»...
...и он ищет

Следующий шаг — клик по кнопке «Показать». Посмотрим ее код. 

У кнопки есть тег button и несколько классов. Чаще всего (и в нашем примере тоже) не стоит надеяться, что мы просто вызовем тег button, и это окажется именно нужная нам кнопка. Лучше вытащить конкретный XPath элемента самим.

Для этого воспользуемся уже знакомой командой find_element_by_xpath, пропишем название тега, в котором хотим найти нужный XPath, и в квадратных скобках через @ добавим атрибут, по которому уточним запрос. В нашем случае самый удачный атрибут — type и его подходящее значение submit

Обратите внимание на синтаксис и особенно на кавычки: сначала одинарные, затем двойные

Назовем эту переменную button, и следующим шагом просто кликнем на нее командой click.

В браузере должна появиться выдача по ранее введенному запросу.

Кстати, в этом случае можно было не искать кнопку, а с помощью команды send_keys имитировать нажатие Enter на клавиатуре, чтобы запустить поиск. Так можно имитировать нажатие любых клавиш или их сочетаний.

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

Вытянем название по уже привычной схеме: кликнем на «Посмотреть код» и найдем тег (a) и класс (result__title). Дальше воспользуемся сразу двумя библиотеками, Selenium и Beautifulsoup. Код страницы, который мы читаем с помощью Selenium, передаем в Beautifulsoup через команду browser.page_source. Теперь, если вызвать эту команду, мы увидим код страницы целиком.

Раньше мы получали код с помощью r.text

С помощью Beautifulsoup и команды find найдем тег a и класс result__title, сразу переведя результат в текст и убрав лишние пробельные символы (командой strip). Назовем переменную name.

Следующий шаг — ссылка на регистрационные данные. Снова кликнем на «Посмотреть код», находим тег и класс. Классов указано несколько, возьмем только второй, потому что он выглядит уникальным для этого элемента.

Создаем переменную link, вводим необходимые тег и класс, запросим href командой get. В начало добавим домен, чтобы получить ссылку целиком.

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

В нашем случае это тег div с классом result

По карточкам можно пройтись как с помощью Beautifulsoup, так и с помощью Selenium. Сначала сделаем так, как уже делали в прошлом уроке. Введем команду findAll, чтобы найти все нужные div-ы с классом result. Проверим с помощью len, сколько их на странице. Должно получиться 10

Если выполнять то же через Selenium, нужно использовать переменную browser, команду find_elements_by_class_name (обратите внимание: elements сейчас во множественном числе, а не в единственном, как мы писали ранее сегодня) и название класса result. Для уверенности можно снова перепроверить длину списка.

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

Создаем цикл. Для каждой организации будем доставать сначала название, затем ссылку. В цикле не забудьте заменить soup на org. Добавим в начало ячейки переменную data, куда запишем все результаты. 

Теперь запускаем код. Но он вернет нам ошибку.

Дело в том, что Selenium устроен таким образом, что пока страница не прогрузится целиком, функции Beautifulsoup не смогут ничего найти. Здесь нам поможет библиотека sleep — сделаем небольшую задержку между поисковым запросом и непосредственно сбором данных со страницы.

В конце дописали print(len(data)), чтобы проверить работоспособность кода. Напечаталось 10 — значит, скрипт прошелся по всем карточкам

Запросим содержание data — и увидим, что в этот раз всё получилось.

Осталось дописать только автопереходы между страницами. Проверим код кнопки «Следующая», чтобы дописать эту команду в цикл. 

Очень удобный класс

Страниц в нашем случае 9 — значит, необходимо 9 раз кликнуть на кнопку «Следующая» и собрать данные. Воспользуемся, как и в прошлом уроке, циклом for p in range 10 (на 1 больше, чем нужно). Попросим скрипт печатать не только количество собранных карточек, но и пройденную страницу.

Допишем команду find по названию класса pagination_next и непосредственно click. После перехода добавим задержку с помощью sleep, чтобы данные успели прогрузиться.

Лучше ставить задержку в районе 10 секунд

Теперь можно снова запросить содержание data, чтобы получить все названия организаций и ссылки, который выводит сайт по нашему запросу. После этого их можно сохранить в таблицу, как мы делали в прошлый раз, и работать с ними дальше.

Остались вопросы? Задавайте их в нашем Telegram-чате, постараемся ответить.