Google Hotels это место, где большинство путешественников сравнивают варианты размещения перед бронированием: цены, рейтинги, фотографии и доступность собраны рядом в одном месте. Для любого, кто работает над мониторингом цен, анализом конкурентов или моделями спроса на путешествия, эта витрина является богатым источником структурированных данных. Загвоздка в том, что Google Hotels отрисовывает свои списки на стороне клиента и жёстко защищается от автоматического трафика, поэтому обычный HTTP запрос отдаёт вам почти пустую страницу вместо отелей, за которыми вы пришли.
Это руководство показывает, как надёжно парсить Google Hotels с помощью Python. Вы создадите небольшой готовый к запуску парсер, который получает отрисованные результаты через Crawling API, разбирает их с помощью BeautifulSoup и извлекает по каждому отелю: название, цену, рейтинг и ссылку. Весь разбор ограничен публичными данными результатов, а раздел о юридических аспектах ближе к концу не является шаблонной формальностью, поэтому прочитайте его, прежде чем направлять это на любой реальный объём.
Что вы создадите
Скрипт на Python, который принимает URL поиска Google Hotels, получает отрисованный HTML через Crawling API и извлекает структурированную запись для каждого отеля на странице. В качестве сквозного примера мы используем поиск по городу и выберем следующие поля:
- Название имя отеля, как оно показано на карточке.
- Цена цена за ночь для выбранных дат.
- Рейтинг средняя оценка гостей там, где она показана.
- Ссылка URL на результат этого отеля в Google.
Почему обычный запрос не работает на Google Hotels
Запросите URL поиска Google Hotels через простой HTTP клиент, и вы получите ответ со статусом 200 и почти без данных списка в теле. Против вас работают две силы. Во-первых, Google Hotels собирает карточки результатов в браузере с помощью JavaScript, поэтому исходный HTML это оболочка, которая заполняется только после выполнения скриптов страницы. Во-вторых, Google быстро отмечает автоматический трафик: IP адреса дата-центров и шаблоны запросов, которые не похожи на настоящий браузер, получают проверку или блокировку ещё до того, как доберутся до отрисованного контента.
Поэтому работающему парсеру Google Hotels нужны две вещи в одном запросе: браузер, который действительно отрисовывает страницу, и IP, который платформа воспринимает как реального посетителя. Вы можете собрать это самостоятельно с помощью headless браузера плюс пула ротируемых резидентных прокси, но сшить их вместе и поддерживать в рабочем состоянии это и есть большая часть работы. Crawling API объединяет оба компонента в одном вызове: вы отправляете ему URL с токеном JavaScript, он отрисовывает страницу за доверенным IP и возвращает вам готовый HTML для разбора.
Crawlbase предлагает два типа токенов. Обычный токен получает статический HTML; токен JavaScript (JS) сначала отрисовывает страницу в настоящем браузере. Google Hotels отрисовывается на стороне клиента, поэтому здесь вам нужен JS токен. Использование обычного токена вернёт ту же пустую оболочку, что и обычный запрос, и из неё нечего разбирать.
Предварительные требования
Прежде чем писать какой-либо код, вам нужно подготовить несколько вещей. Ни одна из них не займёт много времени.
Базовый Python. Вам стоит уверенно писать и запускать скрипт на Python и устанавливать пакеты через pip. Если вы новичок в языке, официальная документация Python и любой курс для начинающих доведут вас до уровня, который предполагает этот учебник.
Python 3.8 или новее. Проверьте свою версию командой python --version. Если у вас её нет, установите её с python.org или через дистрибутив вроде Anaconda.
Аккаунт Crawlbase и JS токен. Зарегистрируйтесь, откройте панель управления и скопируйте свой токен JavaScript (JS) со страницы документации аккаунта. Новые аккаунты включают бесплатные запросы без необходимости указывать карту. Относитесь к токену как к паролю: он аутентифицирует ваши запросы, поэтому держите его вне системы контроля версий.
Настройте проект
Создайте виртуальное окружение, чтобы зависимости проекта оставались изолированными, затем установите две библиотеки, которые нужны парсеру.
python --version python -m venv hotels_env source hotels_env/bin/activate pip install crawlbase beautifulsoup4
В Windows активируйте окружение командой hotels_env\Scripts\activate вместо строки source. Работу делают две зависимости: crawlbase это официальный клиент для Crawling API, а beautifulsoup4 разбирает возвращённый HTML, чтобы вы могли извлекать отдельные поля по CSS селектору.
Шаг 1: Получите отрисованные результаты
Начните с получения готовой страницы. Импортируйте класс CrawlingAPI, инициализируйте его своим JS токеном и запросите URL поиска. Чтение кода статуса перед разбором делает сбои громкими, а не тихими, что очень важно на цели, которая ограничивает трафик.
from crawlbase import CrawlingAPI api = CrawlingAPI({"token": "YOUR_CRAWLBASE_JS_TOKEN"}) def crawl(page_url): options = {"ajax_wait": "true", "page_wait": 5000} response = api.get(page_url, options) status = response["headers"].get("cb_status") if status == "200": return response["body"].decode("utf-8") print(f"Request failed: {status}") return None if __name__ == "__main__": page_url = "https://www.google.com/travel/search?q=hotels+in+New+York¤cy=USD" html = crawl(page_url) print(html[:500] if html else "No HTML returned")
Две опции ожидания важны для отрисовываемой на стороне клиента цели, как эта. ajax_wait сообщает API, что нужно дождаться окончания загрузки асинхронного контента, а page_wait выдерживает фиксированное число миллисекунд после загрузки, чтобы поздно отрисовываемые карточки появились до того, как страница будет захвачена. Пять секунд это разумное начало; увеличьте, если список результатов возвращается коротким или пустым. Обратите внимание на заголовок cb_status (legacy pc_status): это собственный статус Crawlbase для нижележащего запроса, и именно этому значению стоит доверять больше, чем внешнему коду HTTP, когда вы решаете, удался ли запрос. Запустите скрипт, и вы должны увидеть реальную разметку списков, а не пустую оболочку, которую возвращает обычный запрос.
Google Hotels требует отрисованной страницы за доверенным IP, в одном вызове. Crawling API принимает JS токен, запускает страницу в настоящем браузере, ротирует резидентные IP на стороне сервера и отдаёт вам готовый HTML, так что вам не нужно самостоятельно держать парк headless браузеров и пул прокси. Сначала направьте его на публичный поиск на бесплатном тарифе.
Шаг 2: Разберите карточки отелей с помощью BeautifulSoup
Имея на руках отрисованный HTML, загрузите его в BeautifulSoup и извлеките каждый отель из его карточки результата. Google выкладывает списки как повторяющиеся элементы карточек, поэтому вы находите все карточки, а затем считываете название, цену, рейтинг и ссылку из каждой. Защитите каждое поле, чтобы отсутствующий рейтинг или цена не обрушили запуск.
from bs4 import BeautifulSoup def parse_hotels(html): soup = BeautifulSoup(html, "html.parser") hotels = [] for card in soup.find_all("div", class_="BcKagd"): name = card.find("h2", class_="BgYkof") price = card.find("span", class_="qQOQpe prxS3d") rating = card.find("span", class_="KFi5wf lA0BZ") link = card.find("a", class_="PVOOXe") hotels.append({ "name": name.text.strip() if name else None, "price": price.text.strip() if price else None, "rating": rating.text.strip() if rating else None, "link": "https://www.google.com" + link["href"] if link else None, }) return hotels
Каждая защита if name else None делает одно и то же: она возвращает None, когда элемент отсутствует, вместо того чтобы выбросить ошибку при вызове .text на ничём. Это сохраняет извлечение устойчивым, когда одной карточке не хватает цены или у неё ещё нет рейтинга, что часто встречается на странице результатов. Ссылка считывается из href якоря и снабжается префиксом хоста Google, поскольку разметка использует относительные пути.
Имена классов Google (BcKagd, BgYkof, qQOQpe prxS3d и остальные) обфусцированы и меняются без предупреждения. Относитесь к селекторам выше как к стартовому шаблону, а не как к контракту. Когда поле возвращается как None по всем карточкам, заново осмотрите живую страницу в инструментах разработчика своего браузера и обновите селектор. Периодическое обслуживание селекторов это норма для любого боевого парсера, а не признак того, что что-то сломалось.
Шаг 3: Соберите всё вместе
Теперь свяжите получение и разбор в один готовый к запуску скрипт. Получите отрисованный HTML, передайте его парсеру и запишите структурированные записи в файл JSON.
import json from crawlbase import CrawlingAPI from bs4 import BeautifulSoup api = CrawlingAPI({"token": "YOUR_CRAWLBASE_JS_TOKEN"}) def crawl(page_url): options = {"ajax_wait": "true", "page_wait": 5000} response = api.get(page_url, options) status = response["headers"].get("cb_status") if status == "200": return response["body"].decode("utf-8") print(f"Request failed: {status}") return None def parse_hotels(html): soup = BeautifulSoup(html, "html.parser") hotels = [] for card in soup.find_all("div", class_="BcKagd"): name = card.find("h2", class_="BgYkof") price = card.find("span", class_="qQOQpe prxS3d") rating = card.find("span", class_="KFi5wf lA0BZ") link = card.find("a", class_="PVOOXe") hotels.append({ "name": name.text.strip() if name else None, "price": price.text.strip() if price else None, "rating": rating.text.strip() if rating else None, "link": "https://www.google.com" + link["href"] if link else None, }) return hotels def main(): page_url = "https://www.google.com/travel/search?q=hotels+in+New+York¤cy=USD" html = crawl(page_url) if not html: return hotels = parse_hotels(html) with open("google_hotels.json", "w", encoding="utf-8") as f: json.dump(hotels, f, ensure_ascii=False, indent=2) print(f"Saved {len(hotels)} hotels to google_hotels.json") if __name__ == "__main__": main()
Как выглядит вывод
Запустите полный скрипт, и вы получите чистый список структурированных записей, готовый к записи в CSV или базу данных либо к передаче в задачу отслеживания цен.
[ { "name": "The One Boutique Hotel", "price": "$90", "rating": "3.3", "link": "https://www.google.com/travel/search?q=New+York&..." }, { "name": "Ly New York Hotel", "price": "$153", "rating": "4.4", "link": "https://www.google.com/travel/search?q=New+York&..." } ]
Загрузка большего числа результатов
Одна страница поиска показывает ограниченную часть отелей; остальное Google открывает за кнопкой "more results", которая подгружает дополнительные карточки на месте. Crawling API может выполнить это взаимодействие за вас. Передайте css_click_selector для кнопки и задайте ajax_wait, чтобы API кликнул, дождался отрисовки новых карточек и затем вернул расширенный HTML. Та же функция parse_hotels читает более длинную страницу без каких-либо изменений.
def crawl_expanded(page_url, click_selector): options = { "ajax_wait": "true", "page_wait": 5000, "css_click_selector": click_selector, } response = api.get(page_url, options) if response["headers"].get("cb_status") == "200": return response["body"].decode("utf-8") return None
Селектор для этой кнопки сам является обфусцированным значением, которое Google ротирует, поэтому осмотрите живую страницу и подтвердите его, прежде чем полагаться на него. Держите число расширений умеренным. Каждый клик это ещё один отрисованный запрос к чувствительной цели, а нагромождение многих из них в плотном цикле это именно тот шаблон, из-за которого запуск получает ограничение.
Как оставаться незаблокированным
Даже когда отрисовка решена, Google следит за трафиком, похожим на парсер, жёстче, чем почти любая другая цель. Несколько привычек помогают запуску оставаться здоровым, и они применимы к любому агрессивному коммерческому сайту.
- Регулируйте темп запросов. Долбить поиски в плотном цикле это самый быстрый способ навлечь проверку. Распределяйте запросы во времени и варьируйте свои запросы вместо того, чтобы обходить одну локацию на полной скорости.
- Опирайтесь на ротацию. Пул резидентных IP распределяет запросы по множеству адресов реальных пользователей, так что ни один из них не срабатывает лимит частоты. Crawling API делает это за вас; если вы собираете свой собственный стек, именно эту часть нужно сделать правильно.
-
Читайте коды статуса. Запуск, который начинает возвращать не-200
cb_statusили видимую разметку проверки, говорит вам, что текущая частота или уровень IP уже недостаточны. Воспринимайте это как сигнал отступить, а не как шум, который можно игнорировать.
За более широким сценарием обратитесь к тому, как обходить капчи при парсинге Google и тому, как ротировать прокси для парсинга результатов поиска Google. Если вы предпочитаете направлять свой собственный трафик через ротируемый пул вместо использования управляемого API, Smart AI Proxy (также называемый AI Proxy) даёт вам ту же ротацию резидентных IP в виде drop-in прокси эндпоинта. Для более глубокого погружения в обработку проверок руководство о том, как обходить капчи при веб-скрапинге охватывает общий случай.
Законно ли парсить Google Hotels?
Разрешено ли парсить Google Hotels, зависит от условий обслуживания Google, вашей юрисдикции и того, что вы делаете с данными. Условия Google ограничивают автоматический доступ, поэтому парсинг может противоречить этим условиям независимо от того, насколько аккуратен ваш инструментарий. Ничего из кода здесь это не меняет; он лишь заставляет работать техническую часть. Прочитайте условия обслуживания Google и robots.txt для туристической витрины и относитесь к обоим как к границе того, что вы собираете.
Несколько правил, которых стоит придерживаться. Собирайте только публичные данные результатов: название отеля, цену, рейтинг и ссылку, которые любой видит в поиске без входа в систему. Уважайте заявленные Google ожидания по частоте и держите объём своих запросов достаточно низким, чтобы не нагружать его серверы. Не парсите ничего за входом в систему, ничего персонализированного под вошедший аккаунт или любые данные, ограниченные аутентификацией, и никогда не пытайтесь обойти аутентификацию, чтобы добраться до них. Избегайте персональных данных, включая всё, что привязано к идентифицируемым лицам, сверх того, что указано публично.
Это руководство намеренно ограничено публичными результатами поиска, потому что это та граница, которая делает работу защитимой. Только публичные результаты, разумный объём. Если вашему проекту нужно больше публичной витрины, правильным путём является лицензированный поток туристических данных или официальное партнёрство, а не более хитрый парсер.
Ключевые выводы
- Google Hotels отрисовывается на стороне клиента. Обычный запрос возвращает пустую оболочку, поэтому вы должны отрисовать страницу, прежде чем её разбирать.
-
Вам нужны отрисовка и доверенный IP вместе. Crawling API с JS токеном делает оба в одном вызове;
ajax_waitиpage_waitуправляют тем, как долго он ждёт контент. - BeautifulSoup выполняет извлечение. Пройдите по карточкам результатов и считайте название, цену, рейтинг и ссылку из каждой, с защитой на каждом поле, и ожидайте, что обфусцированные селекторы будут дрейфовать.
-
Регулируйте темп и ротируйте. Google жёстко ограничивает, поэтому распределяйте запросы, ротируйте резидентные IP и читайте заголовок
cb_status, чтобы знать, когда отступить. - Оставайтесь на публичных данных. Уважайте ToS и robots.txt Google, собирайте только публичные результаты, держите объём разумным и никогда не трогайте данные за стеной входа, персонализированные или аутентифицированные.
Часто задаваемые вопросы
Почему обычный запрос не возвращает отели из Google Hotels?
Потому что Google Hotels собирает свои карточки результатов на стороне клиента с помощью JavaScript. Исходный HTML это оболочка, которая заполняется только после выполнения скриптов страницы в браузере, поэтому сырой HTTP запрос возвращает статус 200 с пустыми списками. Чтобы получить реальные данные, вам нужно сначала отрисовать страницу, что и делает за вас JS токен Crawling API.
Мне нужен обычный токен или JS токен для Google Hotels?
JS токен. Обычный токен получает статический HTML, который на Google Hotels это та же пустая оболочка, что возвращает обычный запрос. JS токен отрисовывает страницу в настоящем браузере, прежде чем вернуть HTML, поэтому карточки отелей присутствуют, когда BeautifulSoup их разбирает.
Мои селекторы возвращают None по каждой карточке. Что изменилось?
Почти наверняка разметка Google. Имена классов на карточках (BcKagd, BgYkof и остальные) обфусцированы и ротируются без предупреждения, поэтому селекторы, которые работали в прошлом месяце, могут сломаться. Заново осмотрите живую страницу результатов в инструментах разработчика своего браузера и обновите селекторы. Периодическое обслуживание селекторов это норма для любого боевого парсера.
Как получить больше одной страницы результатов?
Google открывает дополнительные отели за кнопкой "more results", а не за классической пагинацией. Передайте селектор этой кнопки в Crawling API через css_click_selector и задайте ajax_wait, чтобы API кликнул её, дождался отрисовки новых карточек и вернул расширенный HTML. Держите число расширений умеренным, чтобы не навлечь проверку.
Как избежать блокировки при парсинге Google Hotels?
Держите частоту запросов на один IP низкой, варьируйте свои запросы вместо зацикливания на одной локации и направляйте трафик через ротируемые резидентные IP, чтобы ни один адрес не срабатывал лимит частоты. Crawling API управляет ротацией и пулом доверенных IP за вас; если вы строите свой собственный стек, Smart AI Proxy даёт вам эту ротацию в виде drop-in эндпоинта. Следите за заголовком cb_status и отступайте, когда начинаете видеть ответы не-200.
Можно ли парсить данные бронирования или аккаунта из Google Hotels?
Нет, и это руководство этого не охватывает. Всё, что привязано к вошедшему аккаунту, процессу бронирования или персонализированному ценообразованию, находится за аутентификацией, поэтому это не публичные данные результатов. Парсинг контента за стеной входа или персонализированного, либо обход аутентификации, чтобы добраться до него, выходит за рамки здесь и противоречит условиям Google. Придерживайтесь публичной витрины поиска и держите объём разумным.
Обходите любой сайт в масштабе, без борьбы с инфраструктурой.
Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.
