Redfin публикует объявления о продаже и аренде жилья по всей территории США и Канады, и каждая страница листинга содержит именно те структурированные данные, которые движут отслеживанием цен, исследованием рынка и анализом недвижимости: цена запроса, спальни, ванные, площадь в квадратных футах, почтовый адрес и обратная ссылка на листинг. Соберите это по городу или региону, и у вас будет набор данных, который можно сравнивать, отображать в графиках и наблюдать во времени.
Это руководство показывает, как парсить данные о недвижимости Redfin на Python надёжным способом. Вы создадите небольшой готовый к запуску парсер, который получает отрисованный листинг Redfin через Crawling API, разбирает нужные вам поля с помощью BeautifulSoup, проходит пагинацию поиска и экспортирует результат в JSON и CSV. Всё руководство ограничено публичными данными листингов, а раздел о законности ближе к концу это не формальность, поэтому прочитайте его, прежде чем направлять это на любой реальный объём.
Что вы создадите
Скрипт на Python, который принимает URL публичного листинга Redfin, получает отрисованный HTML через Crawling API и извлекает структурированную запись по объекту. В качестве сквозного примера мы используем один листинг продажи и собираем следующие поля:
- Цена указанная цена продажи, показанная на странице объекта.
- Спальни количество спален.
- Ванные количество ванных комнат.
- Площадь отделанная площадь дома в квадратных футах.
- Адрес почтовый адрес: улица, город, штат и почтовый индекс.
- Ссылка канонический URL самого листинга.
Почему обычный запрос не работает с Redfin
Если вы запрашиваете URL листинга Redfin простым HTTP-клиентом, вы обычно получаете один из двух разочаровывающих результатов: тонкую HTML-оболочку, где поля цены, спален и ванных всё ещё пусты, или страницу блокировки ещё до того, как вы доберётесь до листинга. Против вас работают две вещи. Во-первых, Redfin отрисовывает значительную часть деталей листинга в браузере с помощью JavaScript, поэтому исходный HTML, который видит простой запрос, ещё не содержит чисел, за которыми вы пришли. Во-вторых, Redfin применяет меры против парсинга, включая ограничение частоты по IP, CAPTCHA и определение user-agent, поэтому IP центров обработки данных и шаблоны запросов, которые не похожи на настоящий браузер, быстро получают проверку.
Поэтому рабочему парсеру Redfin нужны две вещи в одном запросе: браузер, который действительно отрисовывает страницу, и IP, который платформа воспринимает как настоящего посетителя. Вы можете собрать это сами с помощью headless-браузера плюс пула ротируемых резидентных прокси, но сшивание всего этого вместе и поддержание в рабочем состоянии и составляет основную часть работы. Crawling API объединяет оба в один вызов: вы отправляете ему URL с токеном JavaScript, он отрисовывает страницу за доверенным IP и возвращает готовый HTML для разбора.
Crawlbase предлагает два типа токенов. Обычный токен получает статический HTML; токен JavaScript (JS) сначала отрисовывает страницу в настоящем браузере. Redfin заполняет поля листинга на стороне клиента, поэтому здесь вам нужен токен JS. Обычный токен возвращает ту же частичную оболочку, что и простой запрос, и из неё мало что полезно разобрать.
Предварительные требования
Прежде чем писать код, нужно подготовить несколько вещей. Ни одна из них не займёт много времени.
Базовый Python. Вы должны уверенно писать и запускать скрипт на Python и устанавливать пакеты через pip. Если язык для вас новый, руководство по веб-парсингу на Python покрывает основу, которую предполагает этот учебник.
Python 3.8 или выше. Проверьте версию командой python --version. Если её нет, установите с python.org или через дистрибутив вроде Anaconda.
Учётная запись Crawlbase и токен JS. Зарегистрируйтесь, откройте панель управления и скопируйте токен JavaScript (JS). Вы получаете до 20 000 бесплатных запросов Crawling API, и карта не требуется. Относитесь к токену как к паролю: он аутентифицирует ваши запросы, поэтому держите его вне системы контроля версий.
Настройка проекта
Создайте виртуальное окружение, чтобы зависимости проекта оставались изолированными, затем установите две библиотеки, нужные парсеру.
python --version python -m venv redfin_env source redfin_env/bin/activate pip install crawlbase beautifulsoup4
В Windows активируйте окружение командой redfin_env\Scripts\activate вместо строки source. Работу делают две зависимости: crawlbase это официальный клиент для Crawling API, а beautifulsoup4 разбирает возвращённый HTML, чтобы вы могли извлечь отдельные поля по CSS-селектору. Если вы не использовали парсер раньше, руководство по BeautifulSoup хороший спутник этого учебника.
Шаг 1: получение отрисованного листинга
Начните с получения готовой страницы. Импортируйте класс CrawlingAPI, инициализируйте его своим токеном JS и запросите URL листинга. Проверка статуса перед разбором делает сбои громкими, а не тихими.
from crawlbase import CrawlingAPI crawling_api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) def crawl(page_url): options = {"ajax_wait": "true", "page_wait": 5000} response = crawling_api.get(page_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__": listing_url = "https://www.redfin.com/CA/North-Hollywood/6225-Coldwater-Canyon-Ave-91606/unit-106/home/5104172" html = crawl(listing_url) print(html[:500] if html else "No HTML returned")
Два параметра ожидания имеют значение для отрисовываемой на клиенте цели вроде этой. ajax_wait сообщает API, что нужно дождаться завершения загрузки асинхронного содержимого, а page_wait удерживает фиксированное число миллисекунд после загрузки, чтобы поздно отрисовываемые элементы появились до захвата страницы. Пять секунд это разумное начало; поднимите значение, если поля цены или деталей возвращаются пустыми. Библиотека Crawlbase возвращает статус под response["headers"]["cb_status"]; значение "200" означает, что страница была получена и отрисована. Запустите скрипт, и вы должны увидеть реальную разметку листинга, а не страницу блокировки, что подтверждает работу отрисовки, ещё до того, как вы напишете хоть один селектор.
Redfin нужна отрисованная страница за доверенным IP в одном вызове, и он активно ограничивает частоту и выдаёт CAPTCHA всему, что выглядит автоматизированным. Crawling API берёт токен JS, запускает страницу в настоящем браузере, ротирует резидентные IP на стороне сервера и отдаёт вам готовый HTML, так что вы избегаете содержания собственного парка headless-браузеров и пула прокси. Вы получаете до 20 000 бесплатных запросов.
Шаг 2: разбор полей листинга с BeautifulSoup
Имея на руках отрисованный HTML, загрузите его в BeautifulSoup и извлеките каждое поле по его селектору. Redfin раскладывает основные детали листинга в предсказуемой структуре, так что вы можете сопоставить цену, адрес и ключевые факты с отдельными селекторами. У Redfin нет публичного API для страниц продажи, поэтому это работа с XPath и CSS-селекторами: цена находится в блоке data-rf-test-id="abp-price", адрес разделён на .street-address и .bp-cityStateZip, а ключевые факты (спальни, ванные, площадь) живут в строках .keyDetails-value. Оберните извлечение во вспомогательную функцию, которая возвращает None, когда элемент отсутствует, чтобы одно недостающее поле не обрушило запуск.
from bs4 import BeautifulSoup def text_of(soup, selector): el = soup.select_one(selector) return el.get_text(strip=True) if el else None def find_detail(details, keyword): for value in details: if keyword in value.lower(): return value return None def parse_property(html, listing_url): soup = BeautifulSoup(html, "html.parser") price = text_of(soup, 'div[data-rf-test-id="abp-price"] div') street = text_of(soup, ".street-address") city_state_zip = text_of(soup, ".bp-cityStateZip") address = " ".join(filter(None, [street, city_state_zip])) details = [d.get_text(strip=True) for d in soup.select(".keyDetails-value")] return { "address": address or None, "price": price, "beds": find_detail(details, "bed"), "baths": find_detail(details, "bath"), "sqft": find_detail(details, "sq ft"), "link": listing_url, }
Вспомогательная функция text_of запрашивает элемент и возвращает None, когда он отсутствует, вместо того чтобы выбросить исключение при вызове против ничего. Адрес собирается заново из двух частей, которые Redfin отрисовывает отдельно: строки улицы и строки город/штат/индекс, соединённых пробелом. Ключевые факты возвращаются плоским списком строк .keyDetails-value; find_detail выбирает записи о спальнях, ванных и площади по ключевому слову, а не полагаясь на фиксированную позицию, что переживает небольшие перестановки в макете. Такая структура сохраняет извлечение устойчивым, когда одно поле отсутствует в конкретном листинге.
Имена классов и test ID Redfin (блок abp-price, .street-address, .bp-cityStateZip, строки .keyDetails-value) меняются без уведомления. Относитесь к селекторам выше как к стартовому шаблону, а не к контракту. Когда поле возвращается как None, заново изучите живой листинг в инструментах разработчика браузера и обновите селектор. Периодическое обслуживание селекторов нормально для любого промышленного парсера.
Шаг 3: соберите всё вместе
Теперь свяжите получение и разбор в один готовый к запуску скрипт. Получите отрисованный HTML, передайте его парсеру и выведите структурированную запись.
import json from crawlbase import CrawlingAPI from bs4 import BeautifulSoup crawling_api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) def crawl(page_url): options = {"ajax_wait": "true", "page_wait": 5000} response = crawling_api.get(page_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 text_of(soup, selector): el = soup.select_one(selector) return el.get_text(strip=True) if el else None def find_detail(details, keyword): for value in details: if keyword in value.lower(): return value return None def parse_property(html, listing_url): soup = BeautifulSoup(html, "html.parser") price = text_of(soup, 'div[data-rf-test-id="abp-price"] div') street = text_of(soup, ".street-address") city_state_zip = text_of(soup, ".bp-cityStateZip") address = " ".join(filter(None, [street, city_state_zip])) details = [d.get_text(strip=True) for d in soup.select(".keyDetails-value")] return { "address": address or None, "price": price, "beds": find_detail(details, "bed"), "baths": find_detail(details, "bath"), "sqft": find_detail(details, "sq ft"), "link": listing_url, } def main(): listing_url = "https://www.redfin.com/CA/North-Hollywood/6225-Coldwater-Canyon-Ave-91606/unit-106/home/5104172" html = crawl(listing_url) if not html: return data = parse_property(html, listing_url) print(json.dumps(data, indent=2)) if __name__ == "__main__": main()
Как выглядит результат
Запустите полный скрипт, и вы получите аккуратную структурированную запись для листинга, готовую к записи в JSON, CSV или базу данных.
{ "address": "6225 Coldwater Canyon Ave #106 Valley Glen, CA 91606", "price": "$627,000", "beds": "2 beds", "baths": "2 baths", "sqft": "1,209 sq ft", "link": "https://www.redfin.com/CA/North-Hollywood/6225-Coldwater-Canyon-Ave-91606/unit-106/home/5104172" }
Масштабирование по страницам поиска и пагинация
Один листинг это демонстрация; реальная задача проходит по целому поиску. Redfin разбивает результаты поиска на страницы, и у каждого города или региона есть путь результатов, к которому можно добавить номер страницы, например /page-2. Паттерн состоит из двух слоёв: обойдите каждую страницу результатов поиска, чтобы собрать URL листингов, затем получите каждый листинг через ту же функцию parse_property, которую вы уже написали. Карточки листингов на странице поиска Redfin отдают свой URL через якорь с обёрткой .bp-Homecard__Photo--image или элементом ссылки карточки, так что вы можете собрать href и разрешить их относительно домена Redfin.
import time from urllib.parse import urljoin BASE = "https://www.redfin.com" def collect_listing_urls(search_html): soup = BeautifulSoup(search_html, "html.parser") cards = soup.select("a.bp-Homecard") urls = [urljoin(BASE, a["href"]) for a in cards if a.get("href")] return list(dict.fromkeys(urls)) def scrape_search(search_url, pages): listings = [] for page in range(1, pages + 1): page_url = search_url if page == 1 else f"{search_url}/page-{page}" search_html = crawl(page_url) if not search_html: continue for url in collect_listing_urls(search_html): html = crawl(url) if html: listings.append(parse_property(html, url)) time.sleep(2) print(f"Scraped {len(listings)} listings") return listings
Шаг dict.fromkeys удаляет дублирующиеся URL, которые появляются, когда карточка ссылается на один и тот же листинг более одного раза. time.sleep(2) между получениями листингов сделан намеренно: он регулирует темп запуска, чтобы вы не молотили Redfin, что является единственной самой эффективной привычкой для того, чтобы оставаться незаблокированным. Подстройте число страниц и слаг поиска под ваш целевой регион.
Экспорт в JSON и CSV
Как только у вас есть список записей, вывод их это две короткие функции. JSON сохраняет полную структуру для последующего кода; CSV это формат, который читают все таблицы и BI-инструменты.
import csv def save_json(records, path="redfin_listings.json"): with open(path, "w", encoding="utf-8") as f: json.dump(records, f, indent=2, ensure_ascii=False) def save_csv(records, path="redfin_listings.csv"): if not records: return fields = ["address", "price", "beds", "baths", "sqft", "link"] with open(path, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fields) writer.writeheader() writer.writerows(records) if __name__ == "__main__": results = scrape_search("https://www.redfin.com/city/11203/CA/Los-Angeles", pages=3) save_json(results) save_csv(results)
Писатель CSV закрепляет явный порядок столбцов, чтобы заголовок был стабильным от запуска к запуску, и оба писателя используют UTF-8, чтобы адреса с буквами с диакритикой пережили обход туда и обратно. С JSON и CSV на диске вы можете подавать данные прямо в блокнот, дашборд или загрузку в базу данных.
Как оставаться незаблокированным
Даже когда отрисовка решена, Redfin следит за трафиком, похожим на парсер, с помощью ограничения частоты по IP, CAPTCHA и проверок user-agent. Несколько привычек поддерживают запуск здоровым, и они применимы к любой сложной коммерческой цели.
-
Регулируйте темп запросов. Молотить листинги в плотном цикле это самый быстрый способ получить троттлинг или CAPTCHA. Разносите запросы, как делает
sleepвыше, и варьируйте цели, а не обходите один путь на полной скорости. - Опирайтесь на ротацию. Пул резидентных IP распределяет запросы по множеству адресов реальных пользователей, так что ни один из них не срабатывает ограничение частоты. Crawling API делает это за вас; если вы строите собственный стек, именно эту часть нужно сделать правильно.
-
Читайте коды состояния. Запуск, который начинает возвращать проверки или значения
cb_status(legacypc_status), отличные от 200, говорит вам, что текущей частоты или уровня IP уже недостаточно. Относитесь к этому как к сигналу сбавить темп, а не как к шуму, который можно игнорировать.
Более широкий план смотрите в материале как парсить сайты, не попадая в блокировки. Если вы предпочитаете направлять собственный трафик через ротируемый пул, а не использовать управляемый API, Smart AI Proxy даёт вам ту же ротацию резидентных IP в виде встраиваемой прокси-точки. Работаете дальше с другими порталами недвижимости? Тот же паттерн «отрисовать, затем разобрать» переносится на парсинг Zillow и парсинг Realtor.com, меняются лишь селекторы.
Законно ли парсить Redfin?
Разрешён ли парсинг Redfin зависит от условий обслуживания Redfin, вашей юрисдикции и того, что вы делаете с данными. Условия Redfin ограничивают автоматический доступ, поэтому парсинг может противоречить этим условиям независимо от того, насколько аккуратен ваш инструментарий. Ничего из кода здесь это не меняет; он лишь заставляет работать техническую часть. Прочитайте Условия обслуживания Redfin и его robots.txt, уважайте подразумеваемые ими ожидания по частоте и относитесь к обоим как к границе того, что вы собираете.
Несколько правил, которых стоит держаться. Собирайте только публичные данные листингов: цену, спальни, ванные, площадь, адрес и ссылку на листинг, которые любой видит без учётной записи. Держите объём запросов достаточно низким, чтобы не нагружать серверы Redfin. Держитесь подальше от всего, что привязано к идентифицируемым лицам, включая имена и контактные данные агентов листингов или владельцев, которые появляются на странице; публичный листинг недвижимости не лицензия на построение профилей привязанных к нему людей. Значительная часть базовых данных о недвижимости на порталах поступает из фидов MLS, которые лицензируются на определённых условиях, поэтому повторная публикация или перепродажа их в массовом порядке может столкнуться с лицензионными ограничениями, даже когда сама страница публична.
Это руководство намеренно ограничено публичными страницами листингов, потому что это та черта, которая делает работу защитимой. Оно не охватывает ничего за входом в систему, данные сохранённых поисков или учётной записи, персональные данные агентов или владельцев, или любую попытку обойти аутентификацию. Только публичные данные о недвижимости. Если вашему проекту нужен устойчивый санкционированный фид, Redfin и другие порталы предлагают партнёрства по данным, а лицензированные фиды MLS существуют именно для этой цели; лицензионное соглашение или поставщик данных о недвижимости это верный путь для промышленного объёма, а не более хитрый парсер.
Ключевые выводы
- Redfin отрисовывается на стороне клиента и хорошо защищён. Простой запрос возвращает частичную оболочку или страницу блокировки, поэтому вы должны отрисовать страницу за доверенным IP, прежде чем её разбирать.
-
Crawling API делает оба в одном вызове. Токен JS отрисовывает страницу и ротирует резидентные IP на стороне сервера;
ajax_waitиpage_waitуправляют тем, сколько он ждёт содержимое, аcb_statusсообщает вам, сработало ли получение. -
Извлечением занимается BeautifulSoup. Сопоставьте цену, адрес, спальни, ванные, площадь и ссылку на листинг с текущими селекторами вроде
abp-priceи.keyDetails-value, и ожидайте, что эти селекторы будут дрейфовать. - Масштабируйте через пагинацию поиска, затем цикл по листингам. Соберите URL с каждой страницы результатов, получите каждый листинг тем же парсером, отрегулируйте темп запуска короткой паузой и экспортируйте в JSON и CSV.
- Оставайтесь на публичных данных. Уважайте Условия обслуживания Redfin и robots.txt, собирайте только публичные поля недвижимости, помните, что данные MLS часто лицензированы, и никогда не трогайте учётные записи, входы или персональные данные агентов и владельцев.
Часто задаваемые вопросы
Почему обычный запрос не возвращает данные из Redfin?
Потому что Redfin отрисовывает значительную часть деталей листинга на стороне клиента с помощью JavaScript, и он проверяет автоматический трафик. Сырой HTTP-запрос часто возвращает тонкую оболочку с пустыми полями цены, спален и ванных, или страницу блокировки ещё до того, как загрузится листинг. Чтобы получить реальные данные, вам нужно отрисовать страницу за доверенным IP, что и делает за вас токен JS Crawling API.
Нужен ли мне обычный токен или токен JS для Redfin?
Токен JS. Обычный токен получает статический HTML, который на Redfin это та же частичная оболочка, что возвращает простой запрос. Токен JS отрисовывает страницу в настоящем браузере, прежде чем вернуть HTML, поэтому поля листинга присутствуют, когда BeautifulSoup их разбирает.
Какие данные я могу парсить из листинга Redfin?
Публичные поля листинга: цену, количество спален и ванных, площадь, почтовый адрес и ссылку на листинг. Оставайтесь на данных, видимых любому посетителю без учётной записи, и избегайте имён и контактных данных агентов или владельцев, которые выходят за рамки публичного листинга, охватываемого этим руководством.
Мои селекторы возвращают None. Что изменилось?
Почти наверняка разметка Redfin. Блок abp-price, элементы .street-address и .bp-cityStateZip, а также строки .keyDetails-value меняются без уведомления, поэтому селекторы, работавшие в прошлом месяце, могут сломаться. Заново изучите живой листинг в инструментах разработчика браузера и обновите селекторы. Периодическое обслуживание селекторов нормально для любого промышленного парсера.
Как мне обработать пагинацию по поиску Redfin?
Redfin добавляет сегмент страницы вроде /page-2 к пути поиска региона, поэтому вы обходите каждую страницу результатов по очереди, собираете ссылки на листинги с карточек на ней и получаете каждый листинг тем же парсером. Держите короткую паузу между запросами и останавливайтесь, когда страница не возвращает новых карточек. Функция scrape_search выше показывает полный цикл.
Могу ли я парсить страницы аренды и продажи Redfin одним подходом?
Да. Паттерн «отрисовать, затем разобрать» одинаков для обоих; различаются лишь селекторы, поскольку страницы аренды и продажи раскладывают свои поля по-разному. Получите отрисованный HTML через Crawling API, затем нацельте BeautifulSoup на селекторы, которые соответствуют типу страницы, которую вы парсите. Для других порталов тот же поток переносится на парсинг Trulia и другие сайты недвижимости.
Обходите любой сайт в масштабе, без борьбы с инфраструктурой.
Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.
