Airbnb является одной из крупнейших платформ краткосрочной аренды в интернете, а её публичные страницы листингов содержат именно те структурированные поля, которые используются в рыночных исследованиях, отслеживании цен и сравнении путешествий: заголовок объявления, цена за ночь, рейтинг и количество отзывов, местоположение и заявленные удобства. Для тех, кто изучает ночные тарифы по городу или отслеживает динамику рынка, эти публичные данные листингов служат исходным материалом, а их ручной сбор по десяткам вариантов размещения медленен и чреват ошибками.
Это руководство покажет, как надёжно скрейпить листинги Airbnb с помощью Python. Вы создадите небольшой рабочий скрейпер, который загружает отрендеренные страницы поиска Airbnb через Crawling API, разбирает нужные поля листинга с помощью BeautifulSoup, обрабатывает пагинацию и экспортирует чистые JSON и CSV. Всё руководство ограничено публичными данными листингов: никаких персональных данных хозяев или гостей, никаких отдельных отзывов, привязанных к конкретному человеку. Раздел о законности в конце не является формальностью, поэтому прочитайте его до того, как направите скрейпер на реальный объём данных.
Что вы создадите
Python-скрипт, который принимает публичный URL поиска Airbnb для местоположения и дат проживания, собирает карточки листингов на каждой странице результатов и извлекает структурированную запись для каждого листинга. В качестве рабочего примера используются варианты размещения в США, но тот же подход работает для любого публичного URL поиска. Извлекаем следующие поля:
- Название заголовок листинга, отображаемый на карточке, например "Cabin in Woodstock".
- Цена цена за ночь, отображаемая в листинге.
- Рейтинг общий рейтинг гостей и количество отзывов рядом с ним.
- Местоположение название места, извлечённое из заголовка листинга.
- Удобства ключевые удобства, отображаемые на карточке, например бассейн, Wi-Fi или кухня.
- Ссылка канонический URL страницы листинга.
Все эти поля являются публичными и не персональными. Скрейпер никогда не обращается к имени хозяина, профилю гостя, личным сообщениям или отзывам, приписанным конкретному человеку.
Почему простой запрос не работает на Airbnb
Если вы запрашиваете URL поиска Airbnb обычным HTTP-клиентом, вы получаете ответ со статусом 200 и почти без данных листингов в теле. Два фактора работают против вас. Во-первых, Airbnb рендерит результаты поиска в браузере через JavaScript, поэтому исходный HTML представляет собой тонкую оболочку, которая заполняется лишь после выполнения скриптов страницы. Разбирая этот первый ответ, вы получите пустую сетку вместо карточек листингов. Во-вторых, Airbnb быстро помечает автоматизированный трафик: IP дата-центров и паттерны запросов, непохожие на реальный браузер, получают ограничение скорости, IP-бан или запрос на прохождение проверки ещё до того, как добираются до отрендеренного контента.
Таким образом, работающий скрейпер Airbnb требует в одном запросе двух вещей: браузера, который реально рендерит страницу, и IP, воспринимаемого платформой как реальный посетитель. Можно собрать это самостоятельно из headless-браузера и пула ротирующих жилых прокси, но объединить их и поддерживать в рабочем состоянии составляет большую часть работы. Crawling API сворачивает всё это в один вызов: вы отправляете ему URL с JavaScript-токеном, он рендерит страницу за доверенным IP и возвращает готовый HTML для парсинга.
Crawlbase предлагает два типа токенов. Обычный токен загружает статический HTML; JavaScript (JS) токен сначала рендерит страницу в настоящем браузере. Airbnb заполняет сетку поиска на стороне клиента, поэтому здесь вам нужен JS-токен. Обычный токен возвращает ту же тонкую оболочку, что и обычная загрузка, и из неё практически нечего парсить.
Предварительные требования
Перед написанием кода нужно подготовить несколько вещей. Это не займёт много времени.
Базовый Python. Вы должны уметь писать и запускать Python-скрипты, а также устанавливать пакеты через pip. Если вы новичок в парсинге, руководство по BeautifulSoup станет хорошим дополнением к этому туториалу.
Python 3.8 или выше. Проверьте версию командой python --version. Если Python не установлен, скачайте его с python.org или через дистрибутив вроде Anaconda и убедитесь, что Python есть в PATH.
Аккаунт Crawlbase и JS-токен. Зарегистрируйтесь, откройте личный кабинет и скопируйте свой JavaScript (JS) токен со страницы документации аккаунта. Crawlbase предоставляет 1000 бесплатных запросов для старта, чего вполне достаточно для прохождения этого руководства, и вы платите только за успешные запросы. Обращайтесь с токеном как с паролем: он аутентифицирует ваши запросы, поэтому не добавляйте его в систему контроля версий.
Настройка проекта
Создайте виртуальное окружение, чтобы зависимости проекта были изолированы, затем установите необходимые библиотеки.
python --version python -m venv airbnb_env source airbnb_env/bin/activate pip install crawlbase beautifulsoup4
В Windows активируйте окружение командой airbnb_env\Scripts\activate вместо строки с source. Две зависимости выполняют всю работу: crawlbase, официальный клиент для Crawling API, а beautifulsoup4 разбирает возвращаемый HTML, позволяя извлекать отдельные поля по CSS-селектору. Как json, так и csv входят в стандартную библиотеку, поэтому для шага экспорта ничего дополнительно устанавливать не нужно.
Шаг 1: Загрузка отрендеренной страницы поиска Airbnb
Начните с получения готовой страницы. Импортируйте класс CrawlingAPI, инициализируйте его с помощью вашего JS-токена и запросите URL поиска Airbnb. Airbnb загружает результаты асинхронно, поэтому передайте параметры ajax_wait и page_wait, чтобы дождаться динамического контента до захвата страницы. Проверка Crawlbase pc_status перед парсингом делает ошибки явными, а не скрытыми.
from crawlbase import CrawlingAPI api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) OPTIONS = { "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/122.0", "ajax_wait": "true", "page_wait": 5000, } def crawl(page_url): response = api.get(page_url, OPTIONS) if response["headers"]["pc_status"] == "200": return response["body"].decode("utf-8") print(f"Request failed: {response['headers']['pc_status']}") return None if __name__ == "__main__": search_url = "https://www.airbnb.com/s/United-States/homes?checkin=2026-07-10&checkout=2026-07-12&adults=2" html = crawl(search_url) print(html[:500] if html else "No HTML returned")
Два параметра ожидания важны для цели, рендерящейся на стороне клиента, как Airbnb. ajax_wait указывает API дождаться завершения загрузки асинхронного контента, а page_wait выдерживает фиксированное количество миллисекунд после загрузки, чтобы карточки, рендерящиеся с задержкой, отобразились до захвата страницы. Пять секунд, разумное начало; увеличьте это значение, если результаты возвращаются неполными. URL поиска содержит местоположение, даты заезда и выезда, а также количество взрослых, точно так же как собственный поиск Airbnb. Запустите скрипт командой python airbnb_scraper.py, и вы должны увидеть настоящую разметку поиска Airbnb, а не оболочку, возвращаемую обычным запросом. Это подтверждает работу рендеринга ещё до написания единственного селектора.
Airbnb требует отрендеренной страницы за доверенным IP в одном вызове, и именно это настраивают параметры ajax_wait и page_wait выше. Crawling API принимает JS-токен, запускает страницу в настоящем браузере, ротирует жилые IP на стороне сервера и возвращает готовый HTML, поэтому вам не нужно самостоятельно управлять парком headless-браузеров и пулом прокси. Начните с публичной страницы поиска на бесплатном тарифе и платите только за успешные запросы.
Шаг 2: Изучение карточек листингов и поиск селекторов
Имея готовую страницу, следующий шаг, найти расположение каждого поля. Откройте тот же URL поиска в браузере, кликните правой кнопкой мыши на карточку листинга и выберите «Просмотреть код» для открытия инструментов разработчика. Airbnb оборачивает каждый результат в элемент-элемент внутри области контента сайта, а заголовок, рейтинг и цена располагаются в предсказуемых местах внутри этой карточки.
По устаревшей разметке контейнер листинга и его внутренние поля соответствуют следующим селекторам. Это стартовый шаблон: сгенерированные имена классов Airbnb ротируются, поэтому перепроверяйте живую страницу при пустых значениях полей.
-
Контейнер листинга:
div#site-content div[itemprop="itemListElement"] -
Заголовок:
div[data-testid="listing-card-title"] -
Рейтинг и отзывы:
span.r1dxllybвнутри карточки -
Цена за ночь:
div._i5duul span.a8jt5op -
Ссылка: атрибут
hrefякоря карточки, объединённый с хостом Airbnb
Шаг 3: Парсинг полей листинга
Загрузите отрендеренный HTML в BeautifulSoup, переберите каждый контейнер листинга и извлеките поля с помощью приведённых выше селекторов. Каждый поиск защищён так, что отсутствующее поле возвращает None вместо сбоя. Заголовок также служит источником для местоположения: Airbnb пишет заголовки карточек в формате «Тип в Место», поэтому текст после «in» является местоположением.
from urllib.parse import urljoin from bs4 import BeautifulSoup CARD = 'div#site-content div[itemprop="itemListElement"]' def text_of(node, selector): el = node.select_one(selector) return el.get_text(strip=True) if el else None def location_from_title(title): if title and " in " in title: return title.split(" in ", 1)[1] return None def amenities_of(node): spans = node.select('div[data-testid="listing-card-subtitle"] span') items = [s.get_text(strip=True) for s in spans] return [a for a in items if a] def parse_card(node): title = text_of(node, 'div[data-testid="listing-card-title"]') anchor = node.select_one("a") href = anchor["href"] if anchor and anchor.get("href") else None return { "title": title, "price": text_of(node, 'div._i5duul span.a8jt5op'), "rating": text_of(node, 'span.r1dxllyb'), "location": location_from_title(title), "amenities": amenities_of(node), "link": urljoin("https://www.airbnb.com", href) if href else None, } def scrape_page(html): soup = BeautifulSoup(html, "html.parser") return [parse_card(node) for node in soup.select(CARD)]
Вспомогательная функция text_of запрашивает один элемент и возвращает его очищенный текст или None при отсутствии элемента, поэтому карточка без какого-либо поля не прерывает цикл. Селектор рейтинга захватывает объединённые рейтинг и количество отзывов, отображаемые Airbnb вместе, например "4.99 (85)". location_from_title считывает место из заголовка карточки, а amenities_of собирает краткие описания, отображаемые Airbnb в подзаголовке карточки. Атрибут href якоря является относительным, поэтому urljoin превращает его в полный URL листинга. Обратите внимание на то, чего здесь нет: ни имени хозяина, ни профиля хозяина, ни текста отзывов гостей. Карточка отображает только публичные атрибуты листинга, и именно их собирает парсер.
Сгенерированные имена классов Airbnb, такие как r1dxllyb и a8jt5op, меняются без предупреждения. Относитесь к селекторам здесь как к стартовому шаблону, а не к договору. Когда поле возвращает пустое значение, переизучите живую карточку в инструментах разработчика браузера и обновите селектор. Периодическое обслуживание селекторов, норма для любого продакшн-скрейпера, а не признак поломки.
Шаг 4: Обработка пагинации по страницам поиска
Одна страница поиска, лишь часть набора результатов. Airbnb использует пагинацию с параметром запроса items_offset, увеличивая смещение на размер страницы (18 карточек) для перехода по результатам. Считывайте смещение для следующей страницы из навигации по страницам, когда она присутствует, или увеличивайте его самостоятельно до потолка, чтобы большой рынок не привёл к бесконтрольному росту. Небольшая обёртка с повторными попытками вокруг загрузки не позволит одной медленной странице завершить весь запуск.
import time PAGE_SIZE = 18 def fetch_html(page_url, max_retries=2): for attempt in range(max_retries + 1): html = crawl(page_url) if html: return html if attempt < max_retries: print(f"Retrying ({attempt + 1}/{max_retries})...") time.sleep(1) print(f"Unable to fetch {page_url}") return None def collect_all_listings(base_url, max_pages): records = [] for page in range(max_pages): offset = page * PAGE_SIZE sep = "&" if "?" in base_url else "?" page_url = f"{base_url}{sep}items_offset={offset}" html = fetch_html(page_url) if not html: break page_records = scrape_page(html) if not page_records: break records.extend(page_records) time.sleep(2) return records
fetch_html повторяет неудавшуюся загрузку до двух раз с коротким интервалом, возвращая HTML при успехе и None после исчерпания попыток. collect_all_listings перебирает страницы, увеличивая items_offset, ограничивает краулинг установленным потолком max_pages и останавливается досрочно, когда страница возвращает отсутствие карточек (естественный конец результатов). Пауза time.sleep(2) между страницами задаёт темп, не позволяя вам перегружать сайт.
Шаг 5: Сборка полного скрипта
Теперь соедините части в один запускаемый скрипт: соберите листинги по страницам, затем экспортируйте записи в JSON и CSV.
import csv import json import time from urllib.parse import urljoin from crawlbase import CrawlingAPI from bs4 import BeautifulSoup api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) OPTIONS = { "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/122.0", "ajax_wait": "true", "page_wait": 5000, } CARD = 'div#site-content div[itemprop="itemListElement"]' PAGE_SIZE = 18 def crawl(page_url): response = api.get(page_url, OPTIONS) if response["headers"]["pc_status"] == "200": return response["body"].decode("utf-8") print(f"Request failed: {response['headers']['pc_status']}") return None def fetch_html(page_url, max_retries=2): for attempt in range(max_retries + 1): html = crawl(page_url) if html: return html if attempt < max_retries: time.sleep(1) return None def text_of(node, selector): el = node.select_one(selector) return el.get_text(strip=True) if el else None def location_from_title(title): if title and " in " in title: return title.split(" in ", 1)[1] return None def amenities_of(node): spans = node.select('div[data-testid="listing-card-subtitle"] span') items = [s.get_text(strip=True) for s in spans] return [a for a in items if a] def parse_card(node): title = text_of(node, 'div[data-testid="listing-card-title"]') anchor = node.select_one("a") href = anchor["href"] if anchor and anchor.get("href") else None return { "title": title, "price": text_of(node, 'div._i5duul span.a8jt5op'), "rating": text_of(node, 'span.r1dxllyb'), "location": location_from_title(title), "amenities": amenities_of(node), "link": urljoin("https://www.airbnb.com", href) if href else None, } def scrape_page(html): soup = BeautifulSoup(html, "html.parser") return [parse_card(node) for node in soup.select(CARD)] def collect_all_listings(base_url, max_pages): records = [] for page in range(max_pages): offset = page * PAGE_SIZE sep = "&" if "?" in base_url else "?" html = fetch_html(f"{base_url}{sep}items_offset={offset}") if not html: break page_records = scrape_page(html) if not page_records: break records.extend(page_records) time.sleep(2) return records def save_outputs(records): with open("airbnb_listings.json", "w") as f: json.dump(records, f, indent=2) if not records: return with open("airbnb_listings.csv", "w", newline="") as f: writer = csv.DictWriter(f, fieldnames=records[0].keys()) writer.writeheader() for r in records: row = {**r, "amenities": ", ".join(r["amenities"])} writer.writerow(row) def main(): search_url = "https://www.airbnb.com/s/United-States/homes?checkin=2026-07-10&checkout=2026-07-12&adults=2" records = collect_all_listings(search_url, max_pages=2) save_outputs(records) print(f"Saved {len(records)} listings") if __name__ == "__main__": main()
Скрипт собирает листинги с максимум двух страниц поиска, разбирает каждую карточку в запись и задаёт темп цикла двухсекундной паузой. save_outputs записывает JSON-файл и CSV; для CSV он разворачивает список удобств в строку, разделённую запятыми, чтобы столбец оставался читаемым. Настройте max_pages и URL поиска под ваше целевое местоположение и даты.
Как выглядит результат
Запустите полный скрипт командой python airbnb_scraper.py и получите чистую структурированную запись на каждый листинг, готовую для анализа, загрузки в базу данных или электронную таблицу. Заголовки, рейтинги и цены ниже отражают форму, которую Airbnb отображает на своих карточках.
[ { "title": "Cabin in Woodstock", "price": "$70 per night", "rating": "4.9 (41)", "location": "Woodstock", "amenities": ["Wifi", "Kitchen", "Free parking"], "link": "https://www.airbnb.com/rooms/12345678" }, { "title": "Farm stay in Kalispell", "price": "$199 per night", "rating": "5.0 (161)", "location": "Kalispell", "amenities": ["Wifi", "Pool", "Kitchen"], "link": "https://www.airbnb.com/rooms/23456789" } ]
Соответствующий CSV содержит те же столбцы, по одной строке на листинг, что позволяет сразу загрузить его в pandas или любую электронную таблицу для фильтрации по ценовому диапазону, рейтингу или местоположению. Если ваша цель конкретно отслеживание тарифов, дополнительное руководство по скрейпингу цен Airbnb с помощью Python подробнее рассматривает поле цены, а статья о ценовой разведке с помощью веб-скрейпинга объясняет, что делать с полученными числами.
Как не попасть в блок при масштабировании
Даже при решённом вопросе рендеринга Airbnb отслеживает трафик, характерный для скрейпера. Несколько привычек помогают дольше работать в здоровом режиме, и они применимы к любой защищённой коммерческой цели.
- Соблюдайте темп запросов. Бомбардировка страниц поиска в тесном цикле, быстрейший способ получить дросселирование или блокировку. Двухсекундные паузы выше, это минимум, а не потолок; расширяйте их для больших задач и варьируйте цели вместо полноскоростного краулинга одного пути.
- Опирайтесь на ротацию. Пул жилых IP распределяет запросы по множеству реальных пользовательских адресов, поэтому ни один из них не достигает лимита скорости. Crawling API берёт это на себя; если вы собираете собственный стек, именно это нужно настроить правильно.
-
Читайте коды статуса. Запуск, начавший возвращать значения
pc_status, отличные от 200, сигнализирует о недостаточности текущей скорости или IP-уровня. Воспринимайте это как сигнал к отступлению, а не как шум, который можно игнорировать.
Для более крупных краулингов асинхронный Crawler ставит запросы в очередь и доставляет результаты на вебхук, что подходит для обработки многих страниц поиска без удержания открытых соединений. Общая методология описана в статье как скрейпить сайты без блокировок. Тот же двухуровневый подход переносится и на другие порталы листингов, например скрейпинг Apartments.com.
Законно ли скрейпить Airbnb?
Допустимость скрейпинга Airbnb зависит от условий использования Airbnb, вашей юрисдикции и того, как вы используете данные. Условия использования Airbnb ограничивают автоматизированный доступ, скрейпинг и сбор контента с платформы, поэтому скрейпинг может противоречить этим условиям независимо от аккуратности вашего инструментария. Ни один из приведённых здесь фрагментов кода не изменяет этого; он лишь обеспечивает техническую работоспособность. Ознакомьтесь с Условиями использования Airbnb и его robots.txt, соблюдайте подразумеваемые ими ограничения скорости и рассматривайте оба документа как границы допустимого сбора данных. Держите объём запросов достаточно низким, чтобы не перегружать серверы Airbnb.
Более серьёзная граница связана с персональными данными. Листинги Airbnb являются пользовательским контентом, то есть страница может содержать информацию о реальных людях: хозяевах и гостях. Это руководство намеренно ограничено публичными, неперсональными полями листинга (заголовок, цена за ночь, рейтинг, количество отзывов, местоположение, удобства и ссылка), поскольку именно это удерживает работу в защищаемых рамках. Не собирайте имена хозяев или гостей, фотографии профилей, контактные данные или любую другую персональную информацию, а также не скрейпите отдельные отзывы, привязанные к конкретному гостю, и не составляйте профили хозяев. Это персональные данные, и там, где они фигурируют, применяются законы о конфиденциальности, такие как GDPR в ЕС и CCPA в Калифорнии, со своими требованиями и санкциями. Публичная доступность поля не означает, что его можно свободно собирать, если оно идентифицирует человека.
Для всего, что выходит за рамки небольшой публичной, неперсональной выборки, правильный путь, официальный канал, а не более умный скрейпер. Airbnb ведёт партнёрские и API-программы для разрешённых интеграций, и именно они являются верным маршрутом для коммерческого или массового использования. При сомнениях в конкретном сценарии использования получите юридическую консультацию до того, как строить продукт на основе этих данных. Приведённое выше техническое руководство, способ изучить механику на публичных данных, а не лицензия на массовый сбор или на работу с чем-либо, связанным с конкретным человеком.
Ключевые выводы
- Airbnb рендерится на стороне клиента. Простой запрос возвращает тонкую оболочку с пустой сеткой, поэтому страницу необходимо отрендерить перед парсингом.
-
Нужны одновременно рендеринг и доверенный IP. Crawling API с JS-токеном обеспечивает и то, и другое в одном вызове;
ajax_waitиpage_waitуправляют временем ожидания контента. -
Парсите карточку, а не личность. Перебирайте контейнеры
itemListElementи читайте заголовок, цену, рейтинг с количеством отзывов, местоположение, удобства и ссылку, всё это публичные, неперсональные поля. -
Пагинация и экспорт. Увеличивайте параметр
items_offsetAirbnb до потолка, задавайте темп короткими паузами и записывайте записи в JSON и CSV. - Работайте только с публичными данными. Соблюдайте Условия использования и robots.txt Airbnb, никогда не собирайте персональные данные хозяев или гостей и именные отзывы, помните о применимости GDPR и CCPA к персональным данным и используйте официальный или партнёрский API Airbnb для продакшн-применения.
Часто задаваемые вопросы
Почему простой запрос возвращает пустую сетку Airbnb?
Потому что Airbnb загружает результаты поиска на стороне клиента через JavaScript. Исходный HTML является оболочкой, которая заполняется лишь после выполнения скриптов страницы в браузере, поэтому сырой HTTP-запрос возвращает статус 200 без карточек листингов. Чтобы получить полный набор, необходимо сначала отрендерить страницу, и именно это делает JS-токен Crawling API.
Нужен ли обычный токен или JS-токен для Airbnb?
JS-токен. Обычный токен загружает статический HTML, который на Airbnb аналогичен той же тонкой оболочке, что возвращает обычная загрузка. JS-токен рендерит страницу в настоящем браузере перед возвратом HTML, поэтому карточки листингов и их поля присутствуют при парсинге BeautifulSoup. Стоимость обычных и JavaScript-запросов различается, поэтому проверьте свой личный кабинет.
Какие поля можно скрейпить из листинга Airbnb?
Публичные, неперсональные поля листинга: заголовок, цена за ночь, рейтинг и количество отзывов, местоположение, ключевые удобства и ссылка. Ограничивайтесь данными, видимыми любому посетителю без аккаунта, и никогда не собирайте имена хозяев или гостей, профили, контактные данные или отдельные отзывы, привязанные к конкретному человеку. Это персональные данные, выходящие за рамки публичных листингов, рассматриваемых в этом руководстве.
Мои селекторы возвращают None. Что изменилось?
Почти наверняка разметка Airbnb. Сгенерированные имена классов (например, r1dxllyb для рейтинга, a8jt5op для цены и test id listing-card-title) меняются без предупреждения, поэтому селекторы, работавшие в прошлом месяце, могут сломаться. Переизучите живую карточку в инструментах разработчика браузера и обновите селекторы. Периодическое обслуживание селекторов, норма для любого продакшн-скрейпера.
Как обработать пагинацию листингов для местоположения?
Airbnb перемещается по результатам с помощью параметра запроса items_offset, увеличиваясь на размер страницы в 18 карточек. Функция collect_all_listings выше последовательно увеличивает смещение, ограничивает краулинг потолком max_pages и останавливается при отсутствии карточек на странице. Делайте короткие паузы между страницами, чтобы запуск оставался вежливым.
Можно ли использовать скрейпинговые данные Airbnb в коммерческих целях?
Это юридический, а не технический вопрос. Условия использования Airbnb ограничивают скрейпинг и повторное использование, а листинги могут содержать персональные данные, подпадающие под действие законов GDPR и CCPA, поэтому коммерческое или массовое использование в целом требует разрешения. Ознакомьтесь с условиями использования, пользуйтесь официальным или партнёрским API Airbnb для продакшн-применения и получайте юридическую консультацию до создания продукта на основе этих данных.
Обходите любой сайт в масштабе, без борьбы с инфраструктурой.
Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.
