Открываете страницу поиска на портале недвижимости — и вместо списка объявлений видите проверку CAPTCHA. На агрегаторах MLS, порталах вроде Zillow и в архивах налога на имущество это не случайность, а первая линия защиты от скриптов. Ниже — какие типы CAPTCHA реально стоят на таких сайтах, рабочий сборщик на Python и на что смотреть при построении пайплайна, который не сыпется через неделю после запуска.
Скрипт сбора объявлений на Python
Ниже — рабочий сборщик: он открывает страницу поиска, определяет тип CAPTCHA по разметке страницы (обычный data-sitekey для reCAPTCHA, атрибут cf-turnstile для Turnstile), решает её через CaptchaAI и уже потом парсит цену, адрес, площадь и число комнат из HTML.
import requests
import time
import re
import json
import csv
import os
from datetime import datetime
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
def solve_captcha(params):
params["key"] = API_KEY
resp = requests.get("https://ocr.captchaai.com/in.php", params=params)
if not resp.text.startswith("OK|"):
raise Exception(f"Submit: {resp.text}")
task_id = resp.text.split("|")[1]
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id,
})
if result.text == "CAPCHA_NOT_READY":
continue
if result.text.startswith("OK|"):
return result.text.split("|", 1)[1]
raise Exception(f"Solve: {result.text}")
raise TimeoutError()
class PropertyCollector:
def __init__(self):
self.session = requests.Session()
self.session.headers["User-Agent"] = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/120.0.0.0"
)
def fetch(self, url):
"""Fetch page with automatic CAPTCHA handling."""
resp = self.session.get(url)
# reCAPTCHA
match = re.search(
r'data-sitekey=["\']([A-Za-z0-9_-]+)["\']', resp.text
)
if match:
# Detect v3
is_v3 = "recaptcha/api.js?render=" in resp.text
params = {
"method": "userrecaptcha",
"googlekey": match.group(1),
"pageurl": url,
}
if is_v3:
params["version"] = "v3"
params["action"] = "search"
token = solve_captcha(params)
resp = self.session.post(url, data={
"g-recaptcha-response": token,
})
# Turnstile
if "cf-turnstile" in resp.text:
match = re.search(r'data-sitekey=["\']([^"\']+)', resp.text)
if match:
token = solve_captcha({
"method": "turnstile",
"sitekey": match.group(1),
"pageurl": url,
})
resp = self.session.post(url, data={
"cf-turnstile-response": token,
})
return resp.text
def collect_listings(self, urls):
"""Collect property listings from multiple pages."""
listings = []
for url in urls:
try:
html = self.fetch(url)
page_listings = self._parse_listings(html)
listings.extend(page_listings)
print(f" {len(page_listings)} listings from {url}")
time.sleep(3)
except Exception as e:
print(f" Error: {url} - {e}")
return listings
def _parse_listings(self, html):
"""Extract property data from HTML."""
listings = []
# Price extraction
prices = re.findall(r'\$\s*([\d,]+)', html)
# Address extraction
addresses = re.findall(
r'class="address"[^>]*>(.*?)</(?:div|span|p)', html
)
# Bed/Bath extraction
beds = re.findall(r'(\d+)\s*(?:bed|br|bedroom)', html, re.I)
baths = re.findall(r'(\d+)\s*(?:bath|ba|bathroom)', html, re.I)
# Sqft extraction
sqft = re.findall(r'([\d,]+)\s*(?:sq\s*ft|sqft)', html, re.I)
# Combine available data
count = max(len(prices), len(addresses), 1)
for i in range(min(count, 50)): # Cap at 50 per page
listing = {
"price": prices[i] if i < len(prices) else None,
"address": (
addresses[i].strip() if i < len(addresses) else None
),
"beds": beds[i] if i < len(beds) else None,
"baths": baths[i] if i < len(baths) else None,
"sqft": sqft[i] if i < len(sqft) else None,
"collected_at": datetime.utcnow().isoformat(),
}
if listing["price"] or listing["address"]:
listings.append(listing)
return listings
def export_csv(self, listings, filename):
if not listings:
print("No listings to export")
return
keys = ["price", "address", "beds", "baths", "sqft", "collected_at"]
with open(filename, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=keys)
writer.writeheader()
writer.writerows(listings)
print(f"Exported {len(listings)} listings to {filename}")
# Usage
collector = PropertyCollector()
search_urls = [
"https://example-realty.com/search?city=austin&type=sale&page=1",
"https://example-realty.com/search?city=austin&type=sale&page=2",
"https://example-realty.com/search?city=austin&type=sale&page=3",
]
listings = collector.collect_listings(search_urls)
collector.export_csv(listings, "austin_listings.csv")
Обратите внимание на time.sleep(3) между страницами в collect_listings — это не про решение CAPTCHA, а про то, чтобы не долбить сайт частыми запросами и не словить блокировку по IP раньше, чем сработает сама проверка. Пример пагинации в search_urls — через ?page=N; на других порталах встречается &offset=N, логика сборщика от этого не меняется.
Какие CAPTCHA встречаются на сайтах недвижимости
Разные типы источников защищены по-разному, и от этого зависит, какой метод API вызывать в solve_captcha:
| Тип платформы | Защита | Тип CAPTCHA |
|---|---|---|
| MLS-агрегаторы | Cloudflare Challenge | полная проверка + прокси |
| Порталы вроде Zillow | reCAPTCHA v3 | невидимая, поведенческая |
| Справочники риэлторов | reCAPTCHA v2 | флажок или невидимая |
| Архивы налога на имущество | CAPTCHA-изображение | распознавание текста |
| Аукционные площадки | Cloudflare Turnstile | виджет-проверка |
| Коммерческие объявления | reCAPTCHA v2 Enterprise | расширенная проверка |
Одна и та же команда часто держит на радаре сразу несколько рынков: агентство, которое парсит Zillow и MLS-агрегаторы для клиентов в США, нередко ведёт похожий сбор по Циан, Avito Недвижимость или Krisha.kz для СНГ-заказчиков. Логика защиты почти всегда одна и та же — reCAPTCHA v2/v3 на карточках объявлений, Cloudflare Turnstile или Challenge на уровне всего домена, — так что один и тот же сборщик с минимальными правками закрывает оба контура.
Отдельная оговорка про данные: собирайте только то, что вправе обрабатывать. Публичная цена, адрес и метраж — обычно не проблема; персональные данные продавца или агента (телефон, e-mail, паспортные реквизиты в карточке) требуют отдельного основания для обработки — для читателей из РФ это прямо регулируется 152-ФЗ «О персональных данных», для трансграничных проектов стоит держать в голове GDPR-подход к минимизации данных. Это вопрос комплаенса самого проекта, а не CaptchaAI.
Какие поля собирать с карточки объекта
| Поле | Источник | Применение |
|---|---|---|
| Цена объявления | Страница объекта | Оценка рынка |
| Адрес | Страница объекта | Гео-анализ |
| Спальни / ванные / площадь | Карточка объекта | Сравнительный анализ |
| Дней на рынке | Метаданные листинга | Скорость продаж |
| История цены | Журнал изменений цены | Анализ тренда |
| Налог на недвижимость | Налоговые записи | Инвестиционный анализ |
| Взнос ТСЖ / кондо-сбор | Карточка объекта | Анализ издержек |
Для истории цены и дней на рынке одного прохода недостаточно — эти поля появляются только при регулярном сборе одного и того же объекта на протяжении недель. Разовый парсинг даёт снимок рынка, ежедневный — уже тренд.
От ежедневного сбора к отчёту по рынку
Сырые карточки объявлений сами по себе малополезны — ценность появляется, когда сбор идёт по расписанию и данные накапливаются в динамике:
Daily Collection
→ Property listings (500-1000 per market)
→ Price changes (delta from previous day)
→ New listings vs delisted
Weekly Analysis
→ Median price trends
→ Inventory levels
→ Days-on-market averages
→ Price-per-sqft by neighborhood
Monthly Report
→ Market heat map
→ Competitive pricing analysis
→ Investment opportunity scoring
На практике ежедневный прогон стоит разносить по времени суток — не только чтобы снизить нагрузку на источник, но и чтобы у reCAPTCHA v3 не накапливался паттерн подозрительного поведения от одного и того же скрипта, бьющего строго по расписанию каждые 24 часа.
Частые вопросы
Законно ли парсить сайты недвижимости?
Публичные данные объявления — цена, адрес, площадь — как правило, можно собирать. А вот персональные данные продавца или агента (телефон, e-mail) — уже нет без отдельного основания; см. оговорку про 152-ФЗ и GDPR выше. Всегда сверяйтесь с условиями использования конкретного сайта.
Сколько потоков нужно для парсинга около 1000 объявлений в час?
Зависит от типа проверки: reCAPTCHA v3 решается быстрее, чем классическая reCAPTCHA v2, поэтому при прочих равных для потока, где преобладает v3, хватит меньшего числа одновременных потоков, чем для v2-тяжёлого источника. Для старта на один-два источника обычно достаточно STANDARD ($30/мес, 15 потоков); при расширении на несколько рынков разумно переходить на ADVANCE ($90/мес, 50 потоков).
Нужен ли прокси, если CaptchaAI уже решает CAPTCHA?
Да. CaptchaAI закрывает саму проверку CAPTCHA, но не решает вопрос блокировки по IP при частых запросах с одного адреса — это разные механизмы защиты. Прокси и ротация IP остаются отдельной частью инфраструктуры; подробности — в руководстве по настройке прокси.
Насколько быстро решается reCAPTCHA v3 на порталах недвижимости?
CaptchaAI обрабатывает reCAPTCHA v3 менее чем за 4 секунды с высокой долей успешных решений на поддерживаемых типах — так что она не становится узким местом относительно самого запроса страницы.
Похожие материалы
- Мониторинг цен в электронной коммерции
- Настройка прокси: лучшие практики
- Парсинг сайтов с защитой от ботов