Expedia, один из крупнейших онлайн-маркетплейсов для путешественников. Данные об отелях, рейсах и активностях, которые он предоставляет, действительно полезны для ценовых исследований, мониторинга рынка или создания туристических продуктов. Проблема в том, что Expedia отрисовывает свои результаты на стороне клиента и серьёзно защищается от ботов, поэтому обычный HTTP-запрос возвращает пустую оболочку. В этом руководстве показано, как парсить Expedia с помощью JavaScript: небольшое Node-приложение, которое отрисовывает страницу, парсит листинги отелей с помощью Cheerio, обрабатывает пагинацию и записывает результаты в CSV.
Чтобы всё оставалось честным и обоснованным, всё руководство ограничено публичными данными: листинги отелей и рейсов, ночные цены, доступность, рейтинги и количество отзывов, видимые любому пользователю без авторизации. Оно не затрагивает учётные записи пользователей, контент за авторизационной стеной, действия по бронированию или персональные данные. Раздел об этике и Условиях использования в конце, не шаблонный текст, поэтому прочитайте его, прежде чем направить этот инструмент на производственные объёмы.
Зачем парсить Expedia для данных о путешествиях
Публичные цены на путешествия меняются постоянно, и один просмотр страницы показывает лишь текущий тариф. Парсинг публичных листингов Expedia позволяет отслеживать ночные тарифы и доступность во времени, именно это нужно для мониторинга цен, бенчмаркинга конкурентов и исследования спроса. Для инженера ценность в структурированных данных: превращение отрисованной страницы поиска в чистые строки, которые можно запрашивать, строить в виде графиков или передавать в модель.
По форме это та же задача, что и любой другой парсинг в электронной коммерции. Разница в том, что антибот-стек Expedia агрессивнее, чем у типичного магазина, поэтому подход должен учитывать это с самого первого запроса.
Целевой ресурс: URL поиска отелей Expedia
Expedia предоставляет несколько поверхностей поиска (рейсы, автомобили, круизы, мероприятия), но поиск отелей наиболее удобен для работы и используется в данном руководстве. Его результаты находятся по предсказуемому URL, параметры запроса которого напрямую соответствуют форме поиска, поэтому можно программно создавать любой поиск без работы с интерфейсом.
Конкретный пример: двое взрослых, один номер, в Дубае, на четыре ночи.
https://www.expedia.com/Hotel-Search?adults=2&rooms=1&destination=Dubai&startDate=2026-07-10&endDate=2026-07-14
Параметры, которые важны:
- destination место поиска в URL-кодировке (город, регион или название объекта).
- adults количество взрослых гостей.
- rooms количество номеров для расчёта цены.
-
startDate и endDate период проживания в формате
YYYY-MM-DD.
Сформируйте URL с нужными параметрами и получите воспроизводимый запрос. Меняйте пункт назначения и даты в цикле, и получите задачу мониторинга цен.
Почему обычный запрос здесь не работает
Если запросить этот URL с помощью обычного HTTP-клиента, вы получите ответ со статусом 200 и почти без данных об отелях в теле. Против вас работают два фактора. Во-первых, Expedia отрисовывает свои листинги в браузере с помощью JavaScript, поэтому исходный HTML, это оболочка, которая заполняется только после выполнения скриптов страницы. Во-вторых, Expedia быстро обнаруживает автоматизированный трафик: IP дата-центров и паттерны запросов, не похожие на реальный браузер, получают задание или блокируются ещё до того, как увидят отрисованный контент.
Поэтому рабочий парсер Expedia должен объединять в одном запросе две вещи: браузер, который действительно отрисовывает страницу, и IP, воспринимаемый платформой как реальный посетитель. Можно собрать такой стек самостоятельно с помощью headless-браузера и пула ротирующих резидентных прокси, но склеивать их вместе и поддерживать в рабочем состоянии, это большая часть работы. Crawlbase Crawling API объединяет оба компонента в одном вызове: вы отправляете URL с JavaScript-токеном, API отрисовывает страницу за доверенным IP и возвращает готовый HTML для парсинга.
Crawlbase предлагает два типа токенов. Обычный токен получает статический HTML; JavaScript (JS) токен сначала отрисовывает страницу в реальном браузере. Expedia отрисовывается на стороне клиента, поэтому здесь нужен JS-токен. Использование обычного токена вернёт ту же пустую оболочку, что и обычный запрос.
Настройка проекта
Вам понадобится Node.js и npm. Убедитесь, что оба установлены, затем создайте проект и установите библиотеки.
node --version npm --version mkdir expedia-scraper && cd expedia-scraper npm init -y npm install cheerio crawlbase csv-writer
Три зависимости выполняют работу: crawlbase, клиент для Crawling API, cheerio парсит возвращённый HTML с jQuery-подобным API на сервере, а csv-writer сериализует ваши результаты. Если вам нужно хранилище с поддержкой запросов вместо плоских файлов, добавьте sqlite3 и записывайте строки в таблицу; приведённый ниже путь с CSV охватывает распространённый случай.
Вам также понадобится аккаунт Crawlbase и JS-токен, который вы получите в дашборде после регистрации. Вставьте его в код там, где указано YOUR_CRAWLBASE_JS_TOKEN.
Получение отрисованного HTML
Начните с получения готовой страницы. Вы передаёте два параметра, важных для такого сайта, как Expedia: ajax_wait указывает API ждать загрузки асинхронного контента, а page_wait делает паузу на фиксированное количество миллисекунд после загрузки, чтобы листинги с поздней отрисовкой успели появиться. Пять секунд, разумная начальная точка; увеличьте это значение, если результаты приходят скудными.
const { CrawlingAPI } = require('crawlbase') const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_JS_TOKEN' }) const options = { ajax_wait: true, page_wait: 5000, } const expediaURL = 'https://www.expedia.com/Hotel-Search?adults=2&rooms=1&destination=Paris&startDate=2026-07-10&endDate=2026-07-14' async function fetchExpediaHTML(url) { const response = await api.get(url, options) return response.body } fetchExpediaHTML(expediaURL).then((html) => console.log(html))
Запустите командой node expedia-scraper.js и вы должны увидеть реальную разметку с карточками отелей, а не пустую оболочку, которую возвращает обычный запрос. Это подтверждает работоспособность отрисовки, прежде чем вы напишете хоть один селектор.
Expedia требует отрисованной страницы за доверенным IP в одном вызове. Crawling API принимает JS-токен, запускает страницу в реальном браузере, ротирует резидентные IP на стороне сервера и передаёт вам готовый HTML, избавляя от необходимости самостоятельно запускать headless-парк и пул прокси. Начните с бесплатного тарифа на публичном поиске отелей.
Парсинг листингов отелей с помощью Cheerio
Получив HTML, загрузите его в Cheerio и пройдитесь по карточкам объектов. Каждая карточка содержит нужные поля: название отеля, цена за ночь, общая цена, рейтинг и количество отзывов. Осмотрите живую страницу в инструментах разработчика браузера, чтобы найти актуальные селекторы, затем сопоставьте каждое поле с одним из них.
const cheerio = require('cheerio') function extractHotelDetails(html) { const $ = cheerio.load(html) const hotels = [] $('div[data-stid="property-listing-results"] .uitk-card').each((i, el) => { hotels.push({ name: $(el).find('h3.uitk-heading').text().trim(), pricePerNight: $(el).find('[data-test-id="price-summary"] .uitk-text').first().text().trim(), totalPrice: $(el).find('[data-test-id="price-summary"] .uitk-text').last().text().trim(), rating: $(el).find('.uitk-badge .uitk-badge-base-text').text().trim(), reviews: $(el).find('.uitk-text[aria-hidden="false"]').last().text().trim(), }) }) return hotels }
Имена классов Expedia (префиксы uitk-* и атрибуты data-stid) меняются без предупреждения. Относитесь к приведённым выше селекторам как к начальному шаблону, а не к контракту. Когда извлечение возвращает пустые строки, проверьте живую страницу и обновите селекторы. Это обычное обслуживание для любого производственного парсера, а не признак неисправности.
Соедините получение и парсинг в функцию main, чтобы получить один запускаемый скрипт.
async function main() { const html = await fetchExpediaHTML(expediaURL) const hotels = extractHotelDetails(html) console.log(hotels) } main().catch((err) => console.error('Scrape failed:', err))
Как выглядят результаты
Запустите полный скрипт и получите массив структурированных объектов отелей. Сокращённый пример:
[ { "name": "OKKO Hotels Paris Rueil-Malmaison", "pricePerNight": "$118", "totalPrice": "$542 total", "rating": "9.0", "reviews": "286 reviews" }, { "name": "Grand Hotel Leveque", "pricePerNight": "$174", "totalPrice": "$782 total", "rating": "8.4", "reviews": "1,004 reviews" }, { "name": "citizenM Paris Opera", "pricePerNight": "$192", "totalPrice": "$871 total", "rating": "9.6", "reviews": "76 reviews" } ]
Обработка пагинации
Expedia загружает первый пакет результатов, а затем показывает дополнительные результаты за кнопкой «Show more results» вместо нумерованного списка страниц. Crawling API может нажать эту кнопку за вас с помощью параметра css_click_selector: передайте допустимый URL-кодированный CSS-селектор, и API кликнет по нему после отрисовки страницы, чтобы дополнительные листинги загрузились до возврата HTML.
const options = { ajax_wait: true, page_wait: 10000, css_click_selector: encodeURIComponent('button[data-stid="show-more-results"]'), }
Замените этим объект параметров, и следующий запуск вернёт больше отелей. Дайте page_wait немного больше времени при клике, поскольку дополнительные результаты требуют времени для отрисовки после срабатывания клика.
Сохранение результатов в CSV
Вывод в консоль удобен при итерациях, но данные нужно сохранять на диск. Библиотека csv-writer сопоставляет каждый ключ объекта со столбцом и добавляет ваши строки несколькими строками кода.
const createCsvWriter = require('csv-writer').createObjectCsvWriter function saveToCSV(data) { const writer = createCsvWriter({ path: 'hotels.csv', header: [ { id: 'name', title: 'Name' }, { id: 'pricePerNight', title: 'Price Per Night' }, { id: 'totalPrice', title: 'Total Price' }, { id: 'rating', title: 'Rating' }, { id: 'reviews', title: 'Reviews' }, ], }) return writer.writeRecords(data).then(() => console.log('Saved hotels.csv')) }
Вызовите saveToCSV(hotels) из main вместо вывода в консоль, и каждый запуск будет записывать аккуратный файл hotels.csv, который можно открыть в любой таблице или загрузить в конвейер. Если вы предпочитаете запрашивать данные с помощью SQL, записывайте те же строки в таблицу SQLite через sqlite3; парсинг остаётся идентичным.
Как оставаться незаблокированным
Даже при обработке отрисовки Expedia отслеживает трафик, характерный для парсеров. Несколько привычек помогают поддерживать работоспособность парсера и применимы к любому жёсткому коммерческому ресурсу.
- Задавайте темп запросов. Непрерывная нагрузка на один поиск в плотном цикле, самый быстрый способ получить ограничение скорости. Распределяйте запросы и меняйте параметры поиска.
- Опирайтесь на ротацию. Пул резидентных прокси распределяет запросы по множеству реальных пользовательских IP, чтобы ни один адрес не активировал ограничение частоты. Crawling API берёт это на себя; если вы создаёте собственный стек, именно это место стоит сделать правильно.
- Читайте коды статусов. Запуск, при котором начинают возвращаться задания или ошибки, сигнализирует о том, что текущая частота или уровень IP уже недостаточны. Относитесь к кодам ошибок статуса прокси как к сигналу, а не к шуму.
Более широкий план по этому вопросу описан в статье как парсить сайты без блокировок. Если вы хотите сравнить этот подход со сборкой на основе headless-браузера, статья парсинг с Python и Selenium рассматривает этот стек.
Честная часть: Условия использования и легальность
Парсинг крупного коммерческого туристического сайта находится в правовой серой зоне, и ответ на вопрос «разрешено ли это» зависит от условий использования платформы, вашей юрисдикции и того, что вы делаете с данными. Условия Expedia ограничивают автоматизированный доступ, поэтому парсинг может нарушать эти условия независимо от тщательности вашего подхода. Ни один из приведённых здесь кодов этого не меняет, он лишь обеспечивает техническую работоспособность.
Несколько правил, которых стоит придерживаться. Собирайте только публичные данные: листинги, цены, доступность и рейтинги, видимые любому пользователю без учётной записи. Соблюдайте robots.txt сайта и заявленные ожидания по частоте запросов, поддерживайте объём запросов достаточно низким, чтобы не перегружать серверы. Если вы планируете повторно использовать данные в коммерческих целях, получите разрешение или официальное соглашение о данных, а не считайте, что молчание означает согласие. И никогда не собирайте персональные данные, включая любую информацию, связанную с индивидуальными учётными записями пользователей.
Это руководство намеренно ограничено публичными данными листингов, поскольку именно это позволяет сохранить работу обоснованной. Оно не охватывает ничего за авторизационной стеной, данные учётных записей или профилей, отзывы, привязанные к идентифицируемым людям, или действия по бронированию любого рода. Если ваш проект требует больше, чем публичные листинги, правильный шаг, официальный API или соглашение о данных с Expedia, а не более умный парсер. Для понимания того, чем управляемый доступ отличается от прямого парсинга, полезно прочитать статью что такое API-прокси.
Ключевые выводы
- Expedia отрисовывается на стороне клиента. Обычный запрос возвращает пустую оболочку, поэтому страницу необходимо отрисовать перед парсингом.
-
Вам нужны отрисовка и доверенный IP вместе. Crawling API с JS-токеном делает оба в одном вызове;
ajax_waitиpage_waitконтролируют время ожидания контента. - Cheerio выполняет извлечение. Сопоставьте название, цену, рейтинг и отзывы с актуальными селекторами и учитывайте их возможное изменение со временем.
-
Пагинация, это клик. Используйте
css_click_selectorдля активации «show more results» до возврата HTML. - Оставайтесь в рамках публичных данных. Соблюдайте Условия использования Expedia и robots.txt; без учётных записей, персональных данных и действий по бронированию.
Часто задаваемые вопросы
Почему обычный запрос не возвращает данные об отелях с Expedia?
Потому что Expedia отрисовывает свои листинги на стороне клиента с помощью JavaScript. Исходный HTML, это оболочка, которая заполняется только после выполнения скриптов страницы в браузере, поэтому прямой HTTP-запрос возвращает статус 200 с пустыми полями отелей. Чтобы получить реальные данные, необходимо сначала отрисовать страницу, именно это обеспечивает JS-токен Crawling API.
Нужен ли мне обычный токен или JS-токен для Expedia?
JS-токен. Обычный токен получает статический HTML, который в случае Expedia представляет собой ту же пустую оболочку, что и обычный запрос. JS-токен отрисовывает страницу в реальном браузере перед возвратом HTML, поэтому листинги присутствуют при парсинге Cheerio.
Мои селекторы Cheerio возвращают пустые строки. Что изменилось?
Почти наверняка разметка Expedia. Её имена классов uitk-* и атрибуты data-stid меняются без предупреждения, поэтому селекторы, работавшие в прошлом месяце, могут перестать работать. Проверьте живую страницу поиска в инструментах разработчика браузера и обновите селекторы. Периодическое обслуживание селекторов, это норма для любого производственного парсера.
Как получить больше, чем первую страницу результатов?
Expedia показывает дополнительные отели за кнопкой «Show more results», а не на нумерованных страницах. Передайте CSS-селектор этой кнопки в параметр css_click_selector Crawling API в URL-кодировке, и API кликнет по ней после отрисовки, чтобы дополнительные листинги загрузились до возврата HTML. При этом дайте page_wait немного больше времени.
Как избежать блокировки при парсинге Expedia?
Поддерживайте низкую частоту запросов с каждого IP, меняйте параметры поиска вместо цикла по одному URL и маршрутизируйте через ротирующие резидентные IP, чтобы ни один адрес не активировал ограничение частоты. Crawling API управляет ротацией и пулом доверенных IP за вас; если вы создаёте собственный стек, именно это место стоит инвестировать. Следите за кодами статусов и снижайте нагрузку, когда начнёте получать задания.
Законно ли парсить Expedia?
Это зависит от условий использования Expedia, вашей юрисдикции и вашей цели, а их условия ограничивают автоматизированный доступ. Строго придерживайтесь публичных данных листингов, соблюдайте robots.txt и ожидания по частоте запросов, никогда не трогайте учётные записи, персональные данные или действия по бронированию. Для коммерческого повторного использования получите разрешение или официальное соглашение о данных, а не полагайтесь на парсер.
Обходите любой сайт в масштабе, без борьбы с инфраструктурой.
Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.
