Agoda предлагает миллионы отелей и объектов краткосрочного проживания по всей Азии и остальному миру, и каждый результат поиска содержит именно те структурированные данные, которые лежат в основе мониторинга цен, туристических исследований и конкурентного анализа: название отеля, ночная цена, оценка гостей, местоположение и ссылка на полное объявление. Для тех, кто следит за рынком или создаёт инструмент сравнения путешествий, эти публичные данные листинга являются исходным материалом, а сбор их вручную на странице результатов оказывается медленным и быстро устаревает.
Это руководство показывает, как парсить данные об отелях с Agoda с помощью Python надёжным способом. Вы создадите небольшой рабочий скрейпер, который получает отрендеренную страницу поиска Agoda через Crawling API, обрабатывает загрузку на основе прокрутки Agoda, чтобы появился полный набор результатов, разбирает нужные поля с помощью BeautifulSoup и сохраняет чистые записи в JSON. Всё руководство ограничено публичными данными листинга, а раздел о законности в конце, не просто формальность, поэтому прочитайте его перед тем, как применять это на реальных объёмах.
Что вы создадите
Python-скрипт, который принимает публичный URL поиска Agoda по городу, получает отрендеренную страницу результатов через Crawling API, прокручивает её, чтобы загрузились все карточки объектов, и извлекает структурированную запись на каждый отель. Рабочий пример, отели в Куала-Лумпуре. Мы извлекаем следующие поля:
- Название отеля, название объекта, как показано на карточке.
- Цена, ночная цена, отображаемая для выбранных дат.
- Рейтинг, оценка гостей, сигнализирующая о качестве и популярности.
- Местоположение, город или район, по которому выполнялся поиск.
- Ссылка, полный URL страницы объявления отеля.
Почему обычный запрос не работает на Agoda
Если запросить URL поиска Agoda с помощью обычного HTTP-клиента, вы получите ответ со статусом 200 и почти без листингов в теле. Работают против вас два фактора. Во-первых, Agoda формирует результаты поиска в браузере через JavaScript и загружает больше карточек объектов только при прокрутке, поэтому исходный HTML является тонкой оболочкой, которая заполняется после выполнения скриптов страницы и прокрутки пользователем. Разберите первый ответ и вы захватите несколько карточек вместо полной страницы. Во-вторых, Agoda быстро выявляет автоматизированный трафик: IP-адреса датацентров и паттерны запросов, не похожие на браузерные, подвергаются ограничению скорости, IP-блокировке или вызовам прежде, чем добираются до отрендеренного контента.
Таким образом, рабочий скрейпер Agoda нуждается в трёх вещах одновременно: в браузере, рендерящем страницу, в способе инициировать прокрутку для появления ленивых карточек и в IP-адресе, воспринимаемом платформой как реальный посетитель. Можно собрать это самостоятельно с помощью headless-браузера, процедуры прокрутки и пула ротирующихся жилых прокси, но поддержка такого стека требует большей части усилий. Crawling API объединяет всё это в один вызов: вы отправляете URL с JavaScript-токеном и опциями прокрутки, он рендерит и прокручивает страницу за доверенным IP и возвращает готовый HTML для парсинга.
Crawlbase предлагает два типа токенов. Обычный токен получает статический HTML; JavaScript (JS) токен сначала рендерит страницу в настоящем браузере. Agoda заполняет результаты поиска на стороне клиента и загружает их при прокрутке, поэтому здесь нужен JS-токен. Обычный токен возвращает ту же тонкую оболочку, что и обычный запрос, из которой мало что можно полезно разобрать.
Предварительные требования
Перед написанием кода вам потребуется несколько вещей. Ни одна из них не занимает много времени.
Базовые знания Python. Вы должны уметь писать и запускать Python-скрипты и устанавливать пакеты с помощью pip. Если вы новичок в парсинге, руководство по BeautifulSoup является хорошим дополнением к данному материалу.
Python 3.8 или новее. Проверьте версию командой python --version. Если Python не установлен, загрузите его с python.org или через дистрибутив вроде Anaconda и убедитесь, что Python находится в PATH.
Аккаунт Crawlbase и JS-токен. Зарегистрируйтесь, откройте дашборд и скопируйте ваш JavaScript (JS) токен со страницы документации аккаунта. Crawlbase включает до 20 000 бесплатных запросов для начала, чего вполне достаточно для прохождения данного руководства. Обращайтесь с токеном как с паролем: он аутентифицирует ваши запросы, поэтому не добавляйте его в систему контроля версий.
Настройка проекта
Создайте виртуальное окружение, чтобы зависимости проекта были изолированы, затем установите необходимые библиотеки.
python --version python -m venv agoda_env source agoda_env/bin/activate pip install crawlbase beautifulsoup4
В Windows активируйте окружение командой agoda_env\Scripts\activate вместо строки с source. Два зависимых пакета выполняют всю работу: crawlbase является официальным клиентом Crawling API, а beautifulsoup4 разбирает возвращаемый HTML для извлечения отдельных полей по CSS-селектору. Модуль json входит в стандартную библиотеку, поэтому для шага экспорта ничего дополнительно устанавливать не нужно.
Шаг 1: Получение отрендеренной страницы Agoda
Начните с получения готовой страницы. Импортируйте класс CrawlingAPI, инициализируйте его с помощью JS-токена и запросите URL поиска Agoda. Agoda загружает карточки по мере прокрутки, поэтому передайте опции scroll и scroll_interval, чтобы API прокрутил страницу до её захвата. Проверка cb_status (legacy pc_status) Crawlbase перед парсингом делает сбои явными, а не скрытыми.
from crawlbase import CrawlingAPI api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) OPTIONS = { "scroll": "true", "scroll_interval": "20", } def fetch_agoda_page(page_url): response = api.get(page_url, OPTIONS) if response["headers"]["cb_status"] == "200": return response["body"].decode("utf-8") print(f"Request failed: {response['headers']['cb_status']}") return None if __name__ == "__main__": search_url = "https://www.agoda.com/search?city=14524&adults=2&rooms=1" html = fetch_agoda_page(search_url) print(html[:500] if html else "No HTML returned")
Две опции прокрутки важны для цели с ленивой загрузкой, такой как Agoda. scroll указывает API прокрутить страницу, а не захватить её немедленно, а scroll_interval задаёт, сколько секунд продолжать прокрутку, чтобы поздно рендерящиеся карточки появились до захвата страницы. Двадцать секунд, задокументированный максимум и разумное начальное значение для загруженной страницы результатов. Запустите скрипт командой python agoda_scraper.py и вы должны увидеть реальную разметку поиска Agoda, а не оболочку от обычного запроса. Это подтверждает работоспособность рендеринга и прокрутки до написания единого селектора.
Agoda требует отрендеренную, прокрученную страницу за доверенным IP в одном вызове, что именно настраивают опции scroll и scroll_interval выше. Crawling API принимает JS-токен, запускает страницу в настоящем браузере, прокручивает её для инициирования загрузки карточек с ленивой загрузкой, ротирует жилые IP на стороне сервера и возвращает готовый HTML, избавляя вас от необходимости запускать headless-флот и пул прокси самостоятельно. Направьте его на публичную страницу поиска с бесплатного уровня.
Шаг 2: Изучение страницы и парсинг карточек отелей
Перед написанием селекторов откройте страницу поиска Agoda в браузере, щёлкните правой кнопкой на карточке отеля и выберите «Просмотр кода», чтобы прочитать её HTML-структуру. На странице результатов по Куала-Лумпуру каждое объявление находится в карточке объекта с несколькими стабильными хуками, которые можно использовать:
-
Название отеля находится в элементе
<h3>с атрибутомdata-selenium="hotel-name". -
Цена находится в элементе
<div>с атрибутомdata-element-name="final-price". -
Рейтинг находится в элементе
<p>с атрибутомdata-element-name="review-score". -
Ссылка на объявление является
hrefякоряa.PropertyCard__Link.
Загрузите отрендеренный HTML в BeautifulSoup, переберите каждую карточку объекта и извлеките эти четыре поля. Каждый запрос защищён, чтобы карточка с отсутствующим полем возвращала пустую строку, а не прерывала цикл, а относительная ссылка объединяется с доменом Agoda для получения полного URL.
from bs4 import BeautifulSoup CARD_SELECTOR = "div#contentContainer ol.hotel-list-container > li.PropertyCard" def text_of(card, selector): el = card.select_one(selector) return el.get_text(strip=True) if el else "" def extract_agoda_data(html, location): soup = BeautifulSoup(html, "html.parser") hotels = [] for card in soup.select(CARD_SELECTOR): link_el = card.select_one("a.PropertyCard__Link") href = link_el["href"] if link_el and link_el.get("href") else "" hotels.append({ "name": text_of(card, 'h3[data-selenium="hotel-name"]'), "price": text_of(card, 'div[data-element-name="final-price"]'), "rating": text_of(card, 'p[data-element-name="review-score"]'), "location": location, "link": f"https://www.agoda.com{href}" if href else "", }) return hotels
Вспомогательный метод text_of запрашивает один элемент внутри карточки и возвращает его очищенный текст или пустую строку при отсутствии элемента, так что объявление с отсутствующим полем не прерывает цикл. Селектор карточки проходит от контейнера контента страницы до каждого элемента li.PropertyCard, а четыре полевых селектора берутся прямо из разметки Agoda. Запустите это против прокрученного HTML из шага 1 и получите по одной записи на отель на странице.
Хуки data-selenium и data-element-name Agoda и его генерируемые имена классов изменяются без предупреждения. Воспринимайте приведённые здесь селекторы как отправную точку, а не контракт. Когда список возвращается пустым, повторно проверьте живую страницу в инструментах разработчика браузера и обновите селектор. Периодическое обслуживание селекторов нормально для любого продакшен-скрейпера, это не признак неисправности.
Шаг 3: Сборка полного скрипта
Теперь объедините части в один рабочий скрипт: получите прокрученную страницу поиска, разберите каждую карточку отеля и экспортируйте записи в JSON-файл.
import json from crawlbase import CrawlingAPI from bs4 import BeautifulSoup api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) OPTIONS = { "scroll": "true", "scroll_interval": "20", } CARD_SELECTOR = "div#contentContainer ol.hotel-list-container > li.PropertyCard" def fetch_agoda_page(page_url): response = api.get(page_url, OPTIONS) if response["headers"]["cb_status"] == "200": return response["body"].decode("utf-8") print(f"Request failed: {response['headers']['cb_status']}") return None def text_of(card, selector): el = card.select_one(selector) return el.get_text(strip=True) if el else "" def extract_agoda_data(html, location): soup = BeautifulSoup(html, "html.parser") hotels = [] for card in soup.select(CARD_SELECTOR): link_el = card.select_one("a.PropertyCard__Link") href = link_el["href"] if link_el and link_el.get("href") else "" hotels.append({ "name": text_of(card, 'h3[data-selenium="hotel-name"]'), "price": text_of(card, 'div[data-element-name="final-price"]'), "rating": text_of(card, 'p[data-element-name="review-score"]'), "location": location, "link": f"https://www.agoda.com{href}" if href else "", }) return hotels def save_to_json(data, filename="agoda_hotels.json"): with open(filename, "w") as f: json.dump(data, f, indent=2) print(f"Saved {len(data)} hotels to {filename}") def main(): location = "Kuala Lumpur" search_url = "https://www.agoda.com/search?city=14524&adults=2&rooms=1" html = fetch_agoda_page(search_url) if html: hotels = extract_agoda_data(html, location) save_to_json(hotels) if __name__ == "__main__": main()
Скрипт получает прокрученную страницу поиска, разбирает каждую карточку объекта в запись и записывает список в agoda_hotels.json с двухпробельным отступом. Параметр city=14524 является внутренним идентификатором Agoda для Куала-Лумпура; замените его на идентификатор другого города и добавьте свои параметры заезда, выезда и количества гостей, чтобы направить скрейпер на другой рынок. Поскольку метка местоположения передаётся, а не парсится, каждая запись содержит город, по которому вы искали.
Как выглядит результат
Запустите полный скрипт командой python agoda_scraper.py и получите чистую структурированную запись на каждый отель, готовую для анализа, базы данных или таблицы. Значения цены и рейтинга приходят в том виде, в каком Agoda форматирует их на карточке.
[ { "name": "Summer Suites KLCC By Castle Classy", "price": "USD34", "rating": "8.4", "location": "Kuala Lumpur", "link": "https://www.agoda.com/summer-suites-klcc-by-castle-classy/hotel/kuala-lumpur-my.html" }, { "name": "Riveria City Kuala Lumpur by Guestonic", "price": "USD20", "rating": "9.2", "location": "Kuala Lumpur", "link": "https://www.agoda.com/riveria-city-kuala-lumpur-by-guestonic/hotel/kuala-lumpur-my.html" } ]
Если вам нужны те же данные в таблице, замените save_to_json на csv.DictWriter Python, используя ключи первой записи в качестве заголовка. Структура записей остаётся той же, поэтому они напрямую загружаются в pandas для фильтрации по ценовому диапазону или сортировки по оценке отзывов.
Масштабирование по городам и сохранение незаблокированности
Одна страница поиска выше является строительным блоком. Чтобы охватить больше рынка, перебирайте список идентификаторов городов Agoda и вызывайте fetch_agoda_page для каждого, отмечая каждый пакет его местоположением. Agoda использует загрузку на основе прокрутки, а не нумерацию страниц, поэтому scroll_interval управляет тем, какую часть набора результатов вы захватываете; увеличивайте его к максимуму на плотных страницах городов, где карточки продолжают загружаться.
Даже при обработке рендеринга и прокрутки, Agoda отслеживает трафик, похожий на скрейперский. Несколько привычек помогают поддерживать работоспособность более длительных запусков, и они применимы к любой сложной коммерческой цели.
- Задавайте темп запросов. Получение множества страниц городов в жёстком цикле, это самый быстрый способ получить ограничение или вызов. Добавляйте небольшую паузу между запросами и варьируйте цели вместо сканирования одного пути на полной скорости.
- Опирайтесь на ротацию. Пул жилых IP-адресов распределяет запросы по множеству реальных пользовательских адресов, чтобы ни один не превысил лимит скорости. Crawling API делает это за вас; если вы используете собственный стек, именно здесь нужно всё правильно настроить.
-
Читайте коды статусов. Если запуск начинает возвращать значения
cb_status, отличные от 200, это означает, что текущего темпа или уровня IP уже не достаточно. Воспринимайте это как сигнал снизить нагрузку, а не шум, который можно игнорировать.
Для более крупных сканирований асинхронный Crawler ставит запросы в очередь и доставляет результаты на вебхук, что подходит для запуска многих страниц городов без удержания открытых соединений. Для более широкого руководства по стратегии см. статьи о том, как парсить сайты без блокировки и о сканировании JavaScript-сайтов. Если вы хотите сравнить данные об отелях на разных порталах, тот же подход применим к парсингу Expedia, TripAdvisor и Google Hotels.
Законно ли парсить Agoda?
Допустимость парсинга Agoda зависит от условий использования Agoda, вашей юрисдикции и того, что вы делаете с данными. Условия Agoda ограничивают автоматизированный доступ и массовый сбор данных, поэтому парсинг может противоречить этим условиям независимо от тщательности инструментария. Ни один из приведённых здесь примеров кода этого не меняет, он лишь обеспечивает техническую реализацию. Прочитайте условия использования Agoda и его robots.txt, поддерживайте объём запросов скромным, чтобы не перегружать серверы, и руководствуйтесь обоими документами как границами того, что вы собираете. Веб-скрейпинг находится в правовой серой зоне, и наиболее безопасная позиция состоит в сборе только того, что публично видимо, и строгом соблюдении разумных лимитов скорости.
Несколько правил, которых стоит придерживаться. Собирайте только публичные поля листинга: название отеля, ночную цену, оценку гостей, местоположение и ссылку на объявление, которые может видеть любой без авторизации. Избегайте всего, связанного с идентифицируемыми лицами, включая имена гостей в отзывах или любые контактные данные хозяев и гостей, что является персональными данными и выходит за рамки публичного листинга. Законы о конфиденциальности, такие как GDPR и CCPA, применяются в момент появления персональных данных, поэтому ограничивайте набор данных фактической информацией об объекте. Не распространяйте защищённый авторским правом контент Agoda, такой как фотографии или полные тексты отзывов, и не пытайтесь получить доступ к чему-либо за логином, сохранённым бронированием или аккаунтом.
Это руководство намеренно ограничено публичными результатами поиска, поскольку именно здесь проходит граница, позволяющая сохранять обоснованную позицию. Если ваш проект требует большего, или вы строите что-то коммерческое на основе данных об отелях, правильный путь состоит в лицензионном соглашении: Agoda располагает официальными партнёрскими и аффилиатными программами с санкционированным API для разрешённых случаев использования, что является правильным маршрутом для массового или продакшен использования, а не разработка более умного скрейпера.
Ключевые выводы
- Agoda рендерится на стороне клиента и загружается при прокрутке. Обычный запрос возвращает тонкую оболочку лишь с несколькими карточками, поэтому страницу необходимо отрендерить и прокрутить перед парсингом.
-
Рендеринг, прокрутка и доверенный IP нужны вместе. Crawling API с JS-токеном делает все три в одном вызове;
scrollиscroll_intervalуправляют тем, какая часть набора результатов загружается. -
Используйте стабильные хуки Agoda. Читайте название отеля из
data-selenium="hotel-name", цену изdata-element-name="final-price", рейтинг изdata-element-name="review-score"и ссылку изa.PropertyCard__Link. - Экспортируйте структурированные записи. Перебирайте карточки объектов в записи с именем, ценой, рейтингом, местоположением и ссылкой, затем записывайте их в JSON (или CSV) для анализа.
- Оставайтесь в рамках публичных данных. Соблюдайте условия использования Agoda и robots.txt, исключайте персональные данные и защищённые авторским правом медиафайлы из набора данных и используйте официальный партнёрский API Agoda для коммерческого или массового использования.
Часто задаваемые вопросы
Почему обычный запрос возвращает только несколько отелей Agoda?
Потому что Agoda формирует результаты поиска на стороне клиента с помощью JavaScript и загружает больше карточек объектов только при прокрутке. Исходный HTML является оболочкой, которая заполняется после выполнения скриптов страницы и её прокрутки, поэтому обычный HTTP-запрос возвращает статус 200 с большинством карточек, которые отсутствуют. Для получения полной страницы необходимо сначала отрендерить и прокрутить её, чем и занимаются JS-токен и опции scroll Crawling API.
Какой токен нужен для Agoda: обычный или JS?
JS-токен. Обычный токен получает статический HTML, который на Agoda представляет собой ту же тонкую оболочку, что и обычный запрос. JS-токен рендерит страницу в настоящем браузере и при установленных опциях scroll прокручивает её перед тем, как вернуть HTML, поэтому карточки объектов присутствуют при их парсинге BeautifulSoup.
Какие данные можно парсить из объявления на Agoda?
Публичные поля листинга: название отеля, ночную цену, оценку гостей, местоположение и ссылку на объявление. Ограничивайтесь данными, видимыми любому посетителю без авторизации, и избегайте имён гостей в отзывах или любых контактных данных хозяев и гостей, что является персональными данными и выходит за рамки публичного листинга, рассматриваемого в данном руководстве.
Как парсить отели для другого города?
Agoda идентифицирует каждый город числовым параметром city в URL поиска (например, city=14524 для Куала-Лумпура). Найдите идентификатор, выполнив поиск в браузере и прочитав его из URL, затем подставьте его в search_url вместе со своими параметрами заезда, выезда и количества гостей. Для охвата нескольких городов перебирайте список идентификаторов и вызывайте fetch_agoda_page для каждого.
Мои селекторы возвращают пустые строки. Что изменилось?
Почти наверняка разметка Agoda. Его хуки data-selenium и data-element-name и генерируемые имена классов изменяются без предупреждения, поэтому работавшие в прошлом месяце селекторы могут сломаться. Повторно проверьте живую страницу в инструментах разработчика браузера и обновите селекторы. Периодическое обслуживание селекторов нормально для любого продакшен-скрейпера.
Как обрабатывать CAPTCHA и блокировки на Agoda?
Agoda использует CAPTCHA и другие методы обнаружения ботов на автоматизированном трафике. Crawling API справляется с этим с помощью браузерного рендеринга и ротации IP на стороне сервера, поэтому большинство вызовов разрешается до того, как HTML достигает вашего кода. Помимо этого, задавайте темп запросов, варьируйте цели и снижайте нагрузку при появлении значений cb_status, отличных от 200, а не пытайтесь пробиться силой.
Обходите любой сайт в масштабе, без борьбы с инфраструктурой.
Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.
