Use Cases

Парсинг данных о недвижимости с обработкой CAPTCHA

Открываете страницу поиска на портале недвижимости — и вместо списка объявлений видите проверку 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 секунды с высокой долей успешных решений на поддерживаемых типах — так что она не становится узким местом относительно самого запроса страницы.

Похожие материалы

Комментарии для этой статьи отключены.