Библиотека selenium
– набор инструментов для интерактивной работы в браузере средствами Python. В широком смысле Selenium – это целый проект, который предлагает различные возможности для управления браузером с использованием популярных языков программирования (Python, Java, R и другие).
Мы рассмотрим один из самых распространённых инструментов – Selenium WebDriver, модуль, который позволяет Python встраиваться в браузер и имитировать в нём работу пользователя: кликать на ссылки и кнопки, заполнять формы, выбирать опции в меню, скроллить страницы и прочее.
Для начала установим библиотеку:
!pip install selenium
Теперь скачаем драйвер для браузера в виде архива (файлы для Chrome, файлы для Firefox). Этот драйвер нужен для того, чтобы Python получил доступ к браузеру и мог открыть в нём новое окно, управляемое автоматически.
После скачивания архив необходимо распаковать и запомнить, где лежит файл с драйвером (chromedriver.exe
на Windows, chromedriver
на Mac). Сам файл с драйвером открывать/запускать не нужно
Обратите внимание: версия драйвера должна совпадать с версией браузера!
Импортируем из selenium
модуль webdriver
с сокращённым названием:
from selenium import webdriver as wd
Если используете драйвер для Chrome, необходимо прописать путь к файлу с драйвером внутри функции Chrome()
:
# пример для Mac
br = wd.Chrome('/Users/allat/Downloads/chromedriver')
# пример для Windows
br = wb.Chrome(r'C:\\Users\\allat\\Downloads\\chromedriver.exe')
Если используете драйвер для Mozilla Firefox, можно ничего не прописывать, функция Firefox()
сама поймет, где найти geckodriver
:
Если на Mac файл с драйвером упорно не хочет подсоединяться к Python, попробуйте выполнить действия в инструкции на странице курса (это займет некоторое время).
После запуска строки кода выше в новом окне браузера открывается пустая страница. На эту страницу мы можем отправить ссылку на сайт и открыть его. Зайдём на сайт «Библио-глобуса»:
br.get("http://www.biblio-globus.ru/")
Чтобы выполнить поиск в каталоге, нам нужно ввести запрос в поле поиска. Найдём это поле! Обратимся к исходному коду страницы и заметим, что поле для ввода запроса имеет атрибут id
равный search_string
(искать по id – самый надёжный способ, так как id всегда уникальный). Попросим selenium
запомнить это поле:
# find_element_by_ – набор методов
# для поиска по id, тэгам, классам и проч
search = br.find_element_by_id("search_string")
А теперь введём в это поле слово Python, используя метод .send_keys()
:
search.send_keys("Python")
Отлично! В окне браузера должны были отразиться изменения. Осталось найти кнопку для поиска и кликнуть на неё. Опять вернёмся к изучению исходного кода страницы:
button = br.find_element_by_id("search_submit") # снова id
button.click()
Python в браузере кликнул на кнопку, теперь там должен быть список книг по запросу Python. Для того, чтобы собрать информацию по книгам с первой страницы результатов, selenium
не понадобится, достаточно задействовать знакомый BeautifulSoup
. Однако для дальнейшей работы нам будет нужен исходный код страницы, которая открыта в браузере в данный момент. Извлечём его:
html = br.page_source
Теперь импортируем BeautifulSoup
и обработаем исходный код HTML:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html)
Выгрузим основную информацию о книгах: название, ссылку, авторов, расположение в магазине и цену. Обратите внимание – информация по каждой книге находится в разделах с атрибутом class
равным details_1
. Найдём все такие блоки информации:
books = soup.find_all("div", {"class" : "details_1"})
Теперь поработаем с одним из них.
books[0]
<div class="details_1"> <div class="author">Криволапов С.Я.</div> <a class="name" href="/search/catalog/details/10835402">Математика на Python. (Бакалавриат). Учебник.</a> <div class="title_data green"> В наличии </div> <div class="placement"><b>Расположение в торговом зале:</b> <br/>Уровень 1, зал № 07, секция 08, шкаф 76, полка 05</div> <div class="title_data price">Цена: <span>1639,00</span> руб.</div> </div>
Выполним поиск по тэгам:
book = books[0]
# ищем по тэгу с классом и извлекаем текст
author = book.find("div", {"class" : "author"}).text
name = book.find("a", {"class" : "name"}).text
place = book.find("div", {"class" : "placement"}).text
# ищем по тэгу с классом и извлекаем значение атрибута href
href = book.find("a", {"class" : "name"})["href"]
# тэгу с классом и извлекаем текст
# убираем лишний текст из цены и приводим её к числовому типу
price_str = book.find("div", {"class" : "title_data price"}).text
price = int(price_str.split()[1].split(",")[0])
Пояснения к последней строке кода:
price_str
выглядит так: 'Цена: 1639,00 руб.'
..split()
: ['Цена:', '1639,00', 'руб.']
.1639,00
..split(",")
: ['1639', '00']
.'1639'
.'1639'
в целое число 1639
через int()
.Готово! Теперь напишем функцию для выгрузки всей этой информации и применим ко всем книгам в списке books
:
def books_info(book):
author = book.find("div", {"class" : "author"}).text
href = book.find("a", {"class" : "name"})["href"]
name = book.find("a", {"class" : "name"}).text
place = book.find("div", {"class" : "placement"}).text
price_str = book.find("div", {"class" : "title_data price"}).text
price = int(price_str.split()[1].split(",")[0])
return author, href, name, place, price
res = []
for b in books:
res.append(books_info(b))
Объект res
– это список, состоящий из кортежей. Его можно превратить в красивый датафрейм pandas
:
import pandas as pd
dat = pd.DataFrame(res)
dat
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
0 | Криволапов С.Я. | /search/catalog/details/10835402 | Математика на Python. (Бакалавриат). Учебник. | Расположение в торговом зале: Уровень 1, зал №... | 1639 |
1 | Криволапов С.Я. | /search/catalog/details/10835400 | Статистические вычисления на платформе Jupyter... | Расположение в торговом зале: Уровень 1, зал №... | 1499 |
2 | М. Лутц | /search/catalog/details/10597875 | Изучаем Python, том 1 | Расположение в торговом зале: Уровень 1, зал №... | 2849 |
3 | Кольцов Д.М. | /search/catalog/details/10829190 | Python. Полное руководство | Расположение в торговом зале: Уровень 1, зал №... | 909 |
4 | Н. Гифт, К. Берман, А. Деза, Г. Георгиу | /search/catalog/details/10814639 | Python и DevOps: Ключ к автоматизации Linux | Расположение в торговом зале: Уровень 1, зал №... | 2529 |
5 | Д. М. Кольцов , Е. В. Дубовик | /search/catalog/details/10776656 | Справочник PYTHON. Кратко, быстро, под рукой | Расположение в торговом зале: Уровень 1, зал №... | 499 |
6 | М. Яворски , Т. Зиаде | /search/catalog/details/10766703 | Python. Лучшие практики и инструменты | Расположение в торговом зале: Уровень 1, зал №... | 2759 |
7 | Б. Любанович | /search/catalog/details/10736311 | Простой Python. Современный стиль программиров... | Расположение в торговом зале: Уровень 1, зал №... | 2019 |
8 | М.Лутц | /search/catalog/details/10632642 | Изучаем Python, том 2, | Расположение в торговом зале: Уровень 1, зал №... | 2849 |
9 | Л.Грессер,В.Кенг | /search/catalog/details/10831874 | Глубокое обучение с подкреплением: теория и пр... | Расположение в торговом зале: Уровень 1, зал №... | 2509 |
Добавим названия столбцов:
dat.columns = ["author", "link", "title", "place", "price"]
И сделаем все ссылки в столбце link
полными – доклеим к ним ссылку на сам сайт. Чтобы избежать циклов, воспользуемся методом .apply()
, который позволяет применить некоторую функцию ко всем ячейкам в столбце, а саму функцию опишем через lambda
:
dat["link"] = dat["link"].apply(lambda x: "http://www.biblio-globus.ru" + x)
Выведем описательные статистики для цен (да, книг мало, но для примера):
# n, среднее, ст отклонение, квантили...
dat["price"].describe()
count 10.000000 mean 2006.000000 std 842.035233 min 499.000000 25% 1534.000000 50% 2264.000000 75% 2701.500000 max 2849.000000 Name: price, dtype: float64
Отлично! Однако мы можем пойти дальше и написать более интересный код, который не просто сгружает информацию с первой страницы, а проходит по всем страницам с результатами поиска. Это можно сделать и без selenium
, но раз мы обсуждаем его, давайте всё-таки его применим! Выполним поиск по странице, открытой в браузере, по тексту ссылки – мы знаем, что последняя страница поиска на сайте называется «Последняя»:
last = br.find_element_by_link_text("Последняя")
Забираем из полученного элемента ссылку на эту страницу:
last_href = last.get_attribute("href")
last_href
'http://www.biblio-globus.ru/search/catalog/products?query=Python&page=14'
Разбиваем строку со ссылкой по page=
, чтобы извлечь только номер, и делаем этот номер целочисленным:
last_page = int(last_href.split("page=")[1])
last_page
14
Идеально! Теперь мы сможем получить ссылки на все страницы из результатов поиска – достаточно подставить в цикле в «шаблонную» строку номера страниц от 1 до 14:
all_pages = []
for n in range(1, last_page + 1):
h = f"http://www.biblio-globus.ru/search/catalog/products?query=Python&page={n}"
all_pages.append(h)
all_pages
['http://www.biblio-globus.ru/search/catalog/products?query=Python&page=1', 'http://www.biblio-globus.ru/search/catalog/products?query=Python&page=2', 'http://www.biblio-globus.ru/search/catalog/products?query=Python&page=3', 'http://www.biblio-globus.ru/search/catalog/products?query=Python&page=4', 'http://www.biblio-globus.ru/search/catalog/products?query=Python&page=5', 'http://www.biblio-globus.ru/search/catalog/products?query=Python&page=6', 'http://www.biblio-globus.ru/search/catalog/products?query=Python&page=7', 'http://www.biblio-globus.ru/search/catalog/products?query=Python&page=8', 'http://www.biblio-globus.ru/search/catalog/products?query=Python&page=9', 'http://www.biblio-globus.ru/search/catalog/products?query=Python&page=10', 'http://www.biblio-globus.ru/search/catalog/products?query=Python&page=11', 'http://www.biblio-globus.ru/search/catalog/products?query=Python&page=12', 'http://www.biblio-globus.ru/search/catalog/products?query=Python&page=13', 'http://www.biblio-globus.ru/search/catalog/products?query=Python&page=14']
На этом мы пока остановимся, при желании можно написать универсальный код для поиска на сайте по любому запросу! Алгоритм примерно следующий: пройти в цикле по всем ссылкам на результаты в списке all_pages
и выгрузить с каждой страницы информацию по книгам на ней, снова задействовав цикл и функцию books_info()
.