Веб-парсинг Booking.Com

Booking.com, насчитывающий более 28 млн. объявлений, является одним из крупнейших сайтов для поиска места для проживания во время путешествия. Если вы открываете новый отель в каком-либо районе, вам, возможно, захочется следить за конкурентами и получать уведомления об открытии новых объектов. Все это можно автоматизировать с помощью веб-парсинга! В этой статье вы узнаете, как с помощью Python и Selenium получить данные со страницы результатов поиска Booking.com, а также разобраться с пагинацией.

Настройка необходимых условий

В данном руководстве используется Python 3.10, но он должен работать с большинством версий Python. Начните с создания нового каталога, в котором будут храниться все файлы проекта, а затем создайте в нем новый Python-файл для самого кода:

$ mkdir booking_scraper
$ cd booking_scraper
$ touch app.py

Для дальнейшей работы вам потребуется установить несколько различных библиотек:

  • selenium
  • selenium-wire
  • webdriver-manager

Вы можете установить обе эти библиотеки с помощью этой команды:

$ pip install selenium selenium-wire webdriver-manager

Selenium предоставит вам все API для программного доступа к браузеру, а Selenium Wire обеспечит дополнительные возможности поверх него. Эти дополнительные возможности мы обсудим позже, когда будем их использовать. Менеджер webdriver-manager поможет вам легко установить бинарный драйвер браузера без необходимости вручную загружать и размещать ссылки на него в коде.

Получение страницы результатов поиска Booking.com

Попробуем получить страницу результатов поиска с помощью Selenium, чтобы убедиться, что все настроено правильно. Сохраните следующий код в файле app.py:

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager

driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
url = "https://www.booking.com/searchresults.en-gb.html?ss=San+Francisco%2C+California%2C+United+States"
driver.get(url)

Примечание: Данный URL-адрес не содержит информации о датах заезда и выезда, поэтому если вы хотите получить информацию об объектах недвижимости, доступных в определенные даты, выберите эти даты в форме поиска и скопируйте полученный URL-адрес.

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

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

browser = webdriver.Chrome(executable_path=r "C:\\path\\to\\chromedriver.exe")

Самым большим недостатком этого способа является то, что каждый раз, когда браузер обновляется, необходимо загружать обновленный двоичный файл драйвера для браузера. Это быстро надоедает, поэтому библиотека webdriver_manager упрощает задачу, позволяя передавать команду ChromeDriverManager().install(). Она автоматически загрузит нужный двоичный файл и вернет соответствующий путь, так что вам больше не придется об этом беспокоиться.

Последняя строка кода просит драйвер перейти на страницу с результатами поиска.

Как извлечь информацию о свойствах из страницы результатов поиска

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

  • Название
  • Населенный пункт
  • Рейтинг
  • Количество отзывов
  • Эскиз
  • Цена

Для доступа к элементам DOM и извлечения из них данных вы будете использовать методы по умолчанию (find_element + find_elements), предоставляемые Selenium. Кроме того, для поиска элементов DOM будут использоваться селекторы CSS и XPath. То, какой именно метод вы используете для поиска элемента, будет зависеть от структуры DOM и от того, какой метод является наиболее подходящим.

Извлечение имени свойства и ссылки

Начнем с исследования структуры DOM на предмет имени свойства. Щелкните правой кнопкой мыши на любом имени свойства и выберите пункт Inspect. Откроется окно инструментов разработчика:
Property title devtools
Как видно, название отеля заключено в тег якоря. Этот тег якоря имеет data-testid, который мы можем использовать для извлечения ссылки на свойство. В инкапсулированном div также определен атрибут data-testid (title), который мы можем использовать для извлечения названия отеля. Чтобы упростить задачу, давайте сначала извлечем все карточки объектов недвижимости в отдельный список, а затем будем работать с каждым объектом по очереди. Это не является обязательным условием, но помогает сохранить структуру.
 
Вы можете извлечь карточки свойств, нацелившись на div, у которых значение data-testid установлено на property-card:
property_cards = driver.find_elements(By.CSS_SELECTOR, 'div[data-testid="property-card"]')
Теперь можно извлечь имя свойства и ссылку на него с помощью следующего кода:
 
for property in property_cards:
    name = property.find_element(By.CSS_SELECTOR,'div[data-testid="title"]').text
    link = property.find_element(By.CSS_SELECTOR,'a[data-testid="title-link"]').get_attribute('href')

Извлечение адреса и цены объекта недвижимости

Аналогичным образом можно извлечь адрес объекта недвижимости. Исследуйте структуру DOM в инструментах разработчика, и вы снова увидите уникальный data-testid:
 
Для извлечения адреса/местности можно использовать следующий код:
for property in property_cards:
  # ...
address = property.find_element(By.CSS_SELECTOR, '[data-testid="address"]').text
Аналогичной стратегии придерживается и цена. Вы можете извлечь ее, ориентируясь на значение data-testid price-and-discounted-price. В каждом объявлении о продаже недвижимости этот атрибут data-testid определен для конечной цены. Он не включает в себя налоги, поэтому, если вы хотите извлечь и их, вам придется дополнительно изучить DOM.
 
Для извлечения цены работает следующий код:
for property in property_cards:
price = property.find_element(By.CSS_SELECTOR, '[data-testid="price-and-discounted-price"]').text

Извлечение рейтинга объекта и количества отзывов

Рейтинг и количество отзывов вложены в div с data-testid в виде review-score.
 
Для извлечения рейтинга и количества отзывов можно воспользоваться одной из нескольких стратегий. Можно извлечь их отдельно, обращаясь к каждому div, а можно быть ленивым и умным и извлечь весь видимый текст из этого div, а затем разделить его соответствующим образом. Вот как выглядит извлечение в исходном виде:
>>> print(property_cards[0].find_element(By.CSS_SELECTOR, '[data-testid="review-score"]').text)
7.8
Хорошо
491 отзыв
Вы можете разделить этот текст на символ новой строки (\\n), и это даст вам рейтинг и количество отзывов. Вот как это можно сделать:
for property in property_cards:
# ...
    review_score, _, review_count = property.find_element(By.CSS_SELECTOR, '[data-testid="review-score"]').text.split('\\n')

Извлечение миниатюры свойства

Миниатюра находится в теге img с data-testid image.
Вы, наверное, уже успели привыкнуть к этому. Напишем код для обращения к этому тегу image и извлечения атрибута src:
for property in property_cards:
# ...
image = property.find_element(By.CSS_SELECTOR, '[data-testid="image"]').

Переход к следующей странице результатов

Переход на следующую страницу результатов поиска осуществляется с помощью следующих операторов кода:

next_page_btn = driver.find_element(By.XPATH, '\/\/button[contains(@aria-label, "Следующая страница")]')
next_page_btn.click()

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

Именно здесь и проявляет себя Selenium Wire. Если вы следовали инструкциям по установке, приведенным в начале статьи, то он у вас уже установлен. Selenium Wire позволяет проверять отдельные запросы и выяснять, успешно ли вернулся ответ. К счастью, он является полноценной заменой Selenium и не требует изменения кода. Просто обновите имп