Homes.com содержит данные об объектах недвижимости по всей территории США, а страницы поиска и листинга включают именно те структурированные поля, которые необходимы для отслеживания цен, проведения рыночных исследований и инвестиционного анализа: заголовок объявления, адрес, цена, количество спален, ванных комнат, площадь и ссылка на каждый объект. Загрузив эти данные в таблицу, можно сравнивать районы, отслеживать динамику цен и находить интересные объявления, не просматривая сотни страниц вручную.
В этом руководстве показано, как парсить Homes.com на Python надёжным способом. Вы создадите небольшой работающий парсер, который получает отрендеренную страницу поиска через Crawling API, извлекает нужные поля с помощью BeautifulSoup, обходит пагинацию и записывает результаты в JSON и CSV. Весь разбор ограничен публичными данными листинга, а раздел о легальности в конце не является формальностью, поэтому прочитайте его перед запуском на реальных объёмах.
Что вы создадите
Python-скрипт, который принимает публичный URL поиска на Homes.com, получает отрендеренный HTML через Crawling API, обходит несколько страниц результатов и извлекает структурированную запись по каждому листингу. В качестве примера используем поиск по одному городу и извлекаем следующие поля:
- Title тип листинга, например "House for Rent" или "Condo for Rent".
- Address адрес объекта недвижимости.
- Price запрашиваемая цена или ежемесячная аренда, указанная на карточке.
- Beds количество спален.
- Baths количество ванных комнат.
- Size площадь в квадратных футах, если указана в листинге.
- Link абсолютный URL страницы объекта недвижимости.
Почему обычный запрос не работает на Homes.com
Если отправить запрос к URL поиска Homes.com с помощью обычного HTTP-клиента, вы получите ответ со статусом 200, но без каких-либо данных листинга в теле. Этому мешают два фактора. Во-первых, Homes.com отображает большую часть контента в браузере с помощью JavaScript: исходный HTML является лишь оболочкой, которая заполняется только после выполнения скриптов страницы. Во-вторых, сайт быстро обнаруживает автоматизированный трафик: IP-адреса дата-центров и паттерны запросов, непохожие на реальный браузер, подвергаются ограничению скорости, проверке или блокировке ещё до получения отрендеренного контента.
Поэтому рабочий парсер Homes.com должен решать две задачи в одном запросе: браузер, который реально рендерит страницу, и IP-адрес, который платформа воспринимает как настоящего посетителя. Можно собрать такое решение самостоятельно из headless-браузера и пула ротирующихся резидентных прокси, но поддержание их в рабочем состоянии занимает большую часть времени. Crawling API объединяет оба компонента в одном вызове: вы передаёте URL с JavaScript-токеном, API рендерит страницу за доверенным IP и возвращает готовый HTML для парсинга. Подробнее о том, почему динамические сайты требуют такого подхода, читайте в статье как краулить JavaScript-сайты.
Crawlbase предлагает два типа токенов. Обычный токен получает статический HTML; JavaScript (JS) токен сначала рендерит страницу в реальном браузере. Homes.com заполняет поля листинга на стороне клиента, поэтому здесь нужен JS-токен. Использование обычного токена вернёт ту же пустую оболочку, что и обычный запрос, и в ней не будет ничего полезного для парсинга.
Предварительные требования
Перед написанием кода необходимо выполнить несколько условий. Это не займёт много времени.
Базовые знания Python. Вы должны уметь писать и запускать Python-скрипты, а также устанавливать пакеты через pip. Если вы только начинаете знакомство с языком, руководство по парсингу сайта на Python охватывает основы, которые предполагает этот туториал.
Python 3.8 или выше. Проверьте версию командой python --version. Если она не установлена, скачайте её с python.org или через дистрибутив вроде Anaconda.
Аккаунт Crawlbase и JS-токен. Зарегистрируйтесь, откройте панель управления и скопируйте JavaScript (JS) токен со страницы документации аккаунта. Вы получаете до 20 000 бесплатных запросов, карта не требуется. Относитесь к токену как к паролю: он аутентифицирует ваши запросы, поэтому не добавляйте его в систему контроля версий.
Настройка проекта
Создайте виртуальное окружение, чтобы зависимости проекта были изолированы, затем установите две библиотеки, необходимые парсеру.
python --version python -m venv homes_scraping_env source homes_scraping_env/bin/activate pip install crawlbase beautifulsoup4
В Windows активируйте окружение командой homes_scraping_env\Scripts\activate вместо строки source. Две зависимости выполняют основную работу: crawlbase является официальным клиентом для Crawling API, а beautifulsoup4 парсит возвращаемый HTML, позволяя извлекать отдельные поля по CSS-селектору. Если вы ещё не работали с этим парсером, руководство по BeautifulSoup станет хорошим дополнением к этому туториалу.
Шаг 1: Получение отрендеренной страницы поиска
Начните с получения готовой страницы. Импортируйте класс CrawlingAPI, инициализируйте его JS-токеном и запросите URL поиска. Две опции ожидания важны для клиентски-рендеренного ресурса: ajax_wait указывает API дождаться завершения загрузки асинхронного контента, а page_wait задерживает фиксированное количество миллисекунд после загрузки, чтобы поздно рендеримые элементы появились до захвата страницы. Проверка статуса перед парсингом позволяет выявлять ошибки явно, а не незаметно.
from crawlbase import CrawlingAPI crawling_api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) options = { "ajax_wait": "true", "page_wait": 10000, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", } def make_crawlbase_request(url): response = crawling_api.get(url, options) if response["headers"]["cb_status"] == "200": return response["body"].decode("utf-8") print(f"Failed to fetch the page. Crawlbase status: {response['headers']['cb_status']}") return None if __name__ == "__main__": url = "https://www.homes.com/los-angeles-ca/homes-for-rent/p1/" html = make_crawlbase_request(url) print(html[:500] if html else "No HTML returned")
Функция читает response["headers"]["cb_status"], статус каждого запроса, который Crawling API возвращает вместе с телом ответа, и передаёт HTML только при значении "200". Десять секунд page_wait являются разумной отправной точкой для Homes.com; увеличьте значение, если поля листинга возвращаются пустыми. Запустите скрипт командой python homes_scraper.py, и вы должны увидеть реальную разметку поиска, а не пустую оболочку обычного запроса. Это подтверждает работу рендеринга до написания первого селектора.
Этот единственный вызов make_crawlbase_request выполняет за вас всю сложную часть. Homes.com требует отрендеренной страницы за доверенным IP, а Crawling API принимает ваш JS-токен, запускает страницу в реальном браузере, ротирует резидентные IP на стороне сервера и возвращает готовый HTML, избавляя вас от необходимости самостоятельно управлять флотом headless-браузеров и пулом прокси. Начните с публичной страницы поиска на бесплатном тарифе.
Шаг 2: Парсинг карточек листинга с BeautifulSoup
Получив отрендеренный HTML, загрузите его в BeautifulSoup и извлеките каждую карточку. Проинспектируйте страницу поиска Homes.com в инструментах разработчика браузера, и вы увидите, что каждый листинг обёрнут в div с классом for-rent-content-container. Выберите все такие элементы, затем считайте отдельные поля с каждого. Заголовок находится в p.property-name, адрес в p.address, а цена, количество спален и ванных комнат берутся из элементов li внутри ul.detailed-info-container в указанном порядке.
from bs4 import BeautifulSoup BASE_URL = "https://www.homes.com" def parse_listings(html): soup = BeautifulSoup(html, "html.parser") cards = soup.select("div.for-rent-content-container") properties = [] for card in cards: title_elem = card.select_one("p.property-name") address_elem = card.select_one("p.address") info_container = card.select_one("ul.detailed-info-container") info = info_container.find_all("li") if info_container else [] link_elem = card.select_one("a") properties.append({ "title": title_elem.text.strip() if title_elem else "N/A", "address": address_elem.text.strip() if address_elem else "N/A", "price": info[0].text.strip() if len(info) > 0 else "N/A", "beds": info[1].text.strip() if len(info) > 1 else "N/A", "baths": info[2].text.strip() if len(info) > 2 else "N/A", "size": info[3].text.strip() if len(info) > 3 else "N/A", "link": BASE_URL + link_elem["href"] if link_elem and link_elem.get("href") else "N/A", }) return properties
Каждая защитная проверка возвращает "N/A" вместо исключения при отсутствии элемента, поэтому одно отсутствующее поле не прерывает весь запуск. Строка с деталями позиционная: Homes.com располагает цену, количество спален, ванных комнат и площадь в виде упорядоченных элементов li, поэтому код считывает их по индексу и предварительно проверяет длину. Ссылка берётся из якоря карточки как относительный путь, поэтому добавление префикса BASE_URL даёт абсолютный URL, по которому можно сразу перейти на страницу объекта.
Имена классов Homes.com (карточка for-rent-content-container, поля property-name и address, строки detailed-info-container) изменяются без предупреждения. Рассматривайте приведённые выше селекторы как начальный шаблон, а не как неизменный контракт. Когда поле возвращается как "N/A", заново проинспектируйте живую страницу в инструментах разработчика браузера и обновите селектор. Периодическое обслуживание селекторов является нормальной практикой для любого рабочего парсера, а не признаком неисправности.
Шаг 3: Обход пагинации
Одна страница, это демонстрация; реальная задача охватывает весь набор результатов. Homes.com добавляет сегмент страницы к пути поиска, поэтому листинги для города доступны по адресам .../homes-for-rent/p1/, .../p2/ и так далее. Переберите в цикле фиксированное количество страниц, получите каждую через ту же функцию запроса, распарсите её карточки и соберите всё в единый список. Небольшая пауза между страницами предотвращает перегрузку сайта.
import time SEARCH_URL = "https://www.homes.com/los-angeles-ca/homes-for-rent" MAX_PAGES = 3 def scrape_search(): properties = [] for page in range(1, MAX_PAGES + 1): url = f"{SEARCH_URL}/p{page}/" print(f"Scraping page {page}: {url}") html = make_crawlbase_request(url) if html: properties.extend(parse_listings(html)) time.sleep(2) return properties
Пауза time.sleep(2) между страницами сделана намеренно: она регулирует скорость выполнения, чтобы не перегружать сайт, и является наиболее эффективной мерой для сохранения доступа. Настройте MAX_PAGES и слаг города в SEARCH_URL под свою задачу. Чтобы парсить аренду в Чикаго, замените на chicago-il; чтобы парсить дома на продажу, измените homes-for-rent на соответствующий путь.
Шаг 4: Экспорт в JSON и CSV
Имея список записей, запишите его в том формате, который удобен для последующей обработки. JSON сохраняет структуру для кода, который будет читать данные; CSV сразу открывается в таблице для сортировки и построения графиков. Два небольших вспомогательных метода покрывают оба варианта.
import json import csv def save_to_json(properties, filename="properties.json"): with open(filename, "w") as f: json.dump(properties, f, indent=4) def save_to_csv(properties, filename="properties.csv"): if not properties: return with open(filename, "w", newline="") as f: writer = csv.DictWriter(f, fieldnames=properties[0].keys()) writer.writeheader() writer.writerows(properties)
Модуль записи CSV берёт заголовки столбцов из ключей первой записи, поэтому столбцы синхронизируются с полями, извлекаемыми на шаге 2. Если вы добавляете или переименовываете поле там, оба экспорта автоматически следуют этому изменению.
Всё вместе
Вот полный рабочий парсер: получение каждой страницы поиска через Crawling API, парсинг карточек, обход пагинации и запись результатов в JSON и CSV. Вставьте ваш токен и запустите.
import json import csv import time from crawlbase import CrawlingAPI from bs4 import BeautifulSoup crawling_api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) BASE_URL = "https://www.homes.com" SEARCH_URL = "https://www.homes.com/los-angeles-ca/homes-for-rent" MAX_PAGES = 3 options = { "ajax_wait": "true", "page_wait": 10000, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", } def make_crawlbase_request(url): response = crawling_api.get(url, options) if response["headers"]["cb_status"] == "200": return response["body"].decode("utf-8") print(f"Failed to fetch the page. Crawlbase status: {response['headers']['cb_status']}") return None def parse_listings(html): soup = BeautifulSoup(html, "html.parser") cards = soup.select("div.for-rent-content-container") properties = [] for card in cards: title_elem = card.select_one("p.property-name") address_elem = card.select_one("p.address") info_container = card.select_one("ul.detailed-info-container") info = info_container.find_all("li") if info_container else [] link_elem = card.select_one("a") properties.append({ "title": title_elem.text.strip() if title_elem else "N/A", "address": address_elem.text.strip() if address_elem else "N/A", "price": info[0].text.strip() if len(info) > 0 else "N/A", "beds": info[1].text.strip() if len(info) > 1 else "N/A", "baths": info[2].text.strip() if len(info) > 2 else "N/A", "size": info[3].text.strip() if len(info) > 3 else "N/A", "link": BASE_URL + link_elem["href"] if link_elem and link_elem.get("href") else "N/A", }) return properties def scrape_search(): properties = [] for page in range(1, MAX_PAGES + 1): url = f"{SEARCH_URL}/p{page}/" print(f"Scraping page {page}: {url}") html = make_crawlbase_request(url) if html: properties.extend(parse_listings(html)) time.sleep(2) return properties def save_to_json(properties, filename="properties.json"): with open(filename, "w") as f: json.dump(properties, f, indent=4) def save_to_csv(properties, filename="properties.csv"): if not properties: return with open(filename, "w", newline="") as f: writer = csv.DictWriter(f, fieldnames=properties[0].keys()) writer.writeheader() writer.writerows(properties) if __name__ == "__main__": listings = scrape_search() save_to_json(listings) save_to_csv(listings) print(f"Saved {len(listings)} listings to properties.json and properties.csv")
Как выглядят результаты
Запустите полный скрипт командой python homes_scraper.py, и вы получите чистую запись по каждому листингу, готовую для записи в JSON, CSV или базу данных.
[ { "title": "Condo for Rent", "address": "3824 Keystone Ave Unit 2, Culver City, CA 90232", "price": "$3,300 per month", "beds": "2 Beds", "baths": "1.5 Baths", "size": "1,100 Sq Ft", "link": "https://www.homes.com/los-angeles-ca/homes-for-rent/property/3824-keystone-ave-culver-city-ca-unit-2/2er2mwklw8zq6/" }, { "title": "House for Rent", "address": "3901 Alonzo Ave, Encino, CA 91316", "price": "$17,000 per month", "beds": "4 Beds", "baths": "3.5 Baths", "size": "3,400 Sq Ft", "link": "https://www.homes.com/los-angeles-ca/homes-for-rent/property/3901-alonzo-ave-encino-ca/879negnf45nee/" } ]
CSV-версия тех же данных содержит по одной строке на листинг со столбцами title, address, price, beds, baths, size и link, которая без проблем открывается в любой таблице для сортировки по цене или фильтрации по районам.
Масштабирование до страниц объектов
Парсер поиска даёт вам поля на уровне карточки. Когда вам нужны полные данные отдельного объекта, перейдите по уже полученной ссылке link и распарсите страницу объекта, которая содержит более богатые поля: площадь участка, расширенное описание и публичные контактные данные листингового агента. На странице объекта используются собственные селекторы: адрес находится в div.property-info-address, цена в span#price, спальни и ванные комнаты в span.feature-beds и span.feature-baths, а площадь участка в span.property-info-feature.lotsize. Используйте make_crawlbase_request для получения страницы, затем примените те же селекторы, что и для карточек. Добавляйте небольшую паузу между запросами объектов, как это делает цикл поиска.
Как оставаться незаблокированным
Даже при наличии рендеринга Homes.com отслеживает трафик, похожий на парсер. Несколько привычек позволяют поддерживать запуск в рабочем состоянии, и они применимы к любому сложному коммерческому ресурсу.
-
Регулируйте скорость запросов. Отправка запросов в плотном цикле, самый быстрый способ получить ограничение скорости или CAPTCHA. Распределяйте запросы, как это делает
sleepвыше, и варьируйте цели вместо краулинга одного пути на полной скорости. - Используйте ротацию. Пул резидентных IP распределяет запросы по множеству реальных пользовательских адресов, чтобы ни один из них не превышал лимит. Crawling API выполняет это за вас; если вы используете собственный стек, это ключевая часть, которую нужно реализовать правильно.
- Следите за кодами статуса. Запуск, который начинает возвращать проверки или ошибки, сигнализирует о том, что текущая скорость или уровень IP больше недостаточны. Воспринимайте это как сигнал для снижения активности, а не как шум, который можно игнорировать.
Подробный сценарий работы описан в статье как парсить сайты, не получая блокировок. Если вы предпочитаете направлять собственный трафик через ротирующийся пул вместо управляемого API, Smart AI Proxy (также называемый AI Proxy) обеспечивает ту же ротацию резидентных IP как прокси-эндпоинт для подключения. Тот же подход работает для других сайтов недвижимости: смотрите наши руководства по парсингу Zillow, парсингу Redfin и парсингу Realtor.com.
Законен ли парсинг Homes.com?
Допустимость парсинга Homes.com зависит от условий использования Homes.com, вашей юрисдикции и того, что вы делаете с данными. Условия использования ограничивают автоматизированный доступ, поэтому парсинг может противоречить этим условиям независимо от аккуратности используемых инструментов. Ни один из приведённых здесь кодов не меняет этого; он лишь делает техническую часть работоспособной. Прочитайте Условия использования Homes.com и его robots.txt, соблюдайте любые указанные ограничения скорости и рассматривайте оба документа как границу того, что вы собираете.
Несколько принципов, которых стоит придерживаться. Собирайте только публичные данные листинга: заголовок, адрес, цену, количество спален, ванных комнат, площадь и ссылку, которые любой может видеть без аккаунта. Поддерживайте объём запросов на достаточно низком уровне, чтобы не нагружать серверы сайта. Избегайте всего, что связано с идентификацией конкретных лиц, включая имена и контактные данные листинговых агентов, владельцев или управляющих недвижимостью, отображаемые на странице. Это персональные данные, сбор или хранение которых может подпадать под действие законов о конфиденциальности, таких как GDPR и CCPA, поэтому не включайте их, если у вас нет чёткого законного основания и реальной необходимости.
Ещё один момент, характерный для недвижимости: значительная часть базовых данных листинга происходит из Multiple Listing Services (MLS), и эти данные часто лицензируются на условиях, ограничивающих их распространение. Если ваш проект требует такой глубины или коммерческого массового переиспользования, правильный путь, лицензированный MLS-фид или официальное соглашение о данных, а не более хитроумный парсер. Это руководство намеренно ограничено публичными страницами листингов, поскольку именно это позволяет работе оставаться в рамках допустимого. Оно не охватывает ничего за логином, данные аккаунта или сохранённых поисков, личные данные агентов или владельцев, а также любые попытки обойти аутентификацию. Только публичные данные листинга.
Ключевые выводы
- Homes.com отображается на стороне клиента. Обычный запрос возвращает пустую оболочку, поэтому необходимо отрендерить страницу перед её парсингом.
-
Нужны рендеринг и доверенный IP одновременно. Crawling API с JS-токеном делает оба в одном вызове;
ajax_waitиpage_waitконтролируют время ожидания контента. -
BeautifulSoup выполняет извлечение. Сопоставьте title, address, price, beds, baths, size и link с селекторами карточки
for-rent-content-containerи учитывайте, что эти селекторы могут меняться. -
Обходите пагинацию, затем экспортируйте. Итерируйте сегмент
p{page}, собирайте каждую карточку, добавляйте паузы между запросами и записывайте результаты в JSON и CSV. - Оставайтесь в рамках публичных данных. Соблюдайте Условия использования и robots.txt Homes.com, собирайте только публичные поля листингов, не включайте личные данные агентов и владельцев, а для коммерческих или массовых задач используйте лицензированный MLS-фид.
Часто задаваемые вопросы
Почему обычный запрос не возвращает данные с Homes.com?
Потому что Homes.com рендерит контент листинга на стороне клиента с помощью JavaScript. Исходный HTML является оболочкой, которая заполняется только после выполнения скриптов страницы в браузере, поэтому необработанный HTTP-запрос возвращает статус 200, но поля цены, спален, ванных комнат и площади пусты. Чтобы получить реальные данные, необходимо сначала отрендерить страницу, что и обеспечивает JS-токен Crawling API.
Нужен ли обычный токен или JS-токен для Homes.com?
JS-токен. Обычный токен получает статический HTML, который на Homes.com является той же пустой оболочкой, что и обычный запрос. JS-токен рендерит страницу в реальном браузере перед возвратом HTML, поэтому поля листинга присутствуют при парсинге BeautifulSoup. Опции ajax_wait и page_wait указывают рендереру, сколько времени ждать этого контента.
Какие данные можно парсить со страницы листинга Homes.com?
Публичные поля листинга: заголовок, адрес, запрашиваемую цену или ежемесячную аренду, количество спален и ванных комнат, площадь там, где она указана, и ссылку на страницу объекта. Ограничивайтесь данными, видимыми любому посетителю без аккаунта, и избегайте личных данных агентов или владельцев, которые выходят за рамки публичного листинга, охваченного этим руководством.
Мои селекторы возвращают "N/A". Что изменилось?
Почти наверняка разметка Homes.com. Карточка for-rent-content-container, поля property-name и address, строки detailed-info-container изменяются без предупреждения, поэтому селекторы, работавшие в прошлом месяце, могут перестать работать. Заново проинспектируйте живую страницу в инструментах разработчика браузера и обновите селекторы. Периодическое обслуживание селекторов нормально для любого рабочего парсера.
Как обойти пагинацию по всем листингам города?
Homes.com добавляет сегмент p{page} к пути поиска, поэтому в цикле запрашивайте .../p1/, .../p2/ и так далее, парсите карточки на каждой странице и собирайте их в один список. Добавляйте небольшую паузу между запросами и останавливайтесь на выбранном лимите страниц. Функция scrape_search выше показывает полный цикл.
Как не получить блокировку при парсинге Homes.com?
Поддерживайте низкую скорость запросов с одного IP, добавляйте паузу между запросами, варьируйте цели вместо цикличного прохода по одному пути, и направляйте трафик через ротирующиеся резидентные IP, чтобы ни один адрес не превысил лимит. Crawling API управляет ротацией и пулом доверенных IP за вас; если вы строите собственный стек, именно эта часть требует инвестиций. Следите за кодами статуса и снижайте активность при появлении проверок.
Обходите любой сайт в масштабе, без борьбы с инфраструктурой.
Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.
