Сценарии использования

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

Платформы недвижимости надежно защищены от автоматического сбора данных. CaptchaAI помогает вам поддерживать надежный доступ к спискам объектов недвижимости, данным о ценах и аналитике рынка.

Защита CAPTCHA на сайтах недвижимости

Тип платформы Защита Тип капчи
МЛС-агрегаторы страница Cloudflare-защиты в staging Полный вызов + прокси
Порталы типа Zillow reCAPTCHA v3 Невидимый, поведенческий
Справочники риэлторов reCAPTCHA v2 Флажок или невидимый
Налоговый учет по налогу на имущество Капча изображения Распознавание текста
Аукционные сайты Cloudflare Turnstile Виджет вызов
Коммерческие объявления reCAPTCHA v2 Enterprise Расширенная проверка

Сборщик данных о недвижимости

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")

Точки данных для сбора

Поле Источник Вариант использования
Цена листинга Страница недвижимости Рыночная оценка
Адрес Страница недвижимости Геоанализ
Кровати/Baths/Sqft Детали недвижимости Сравнительный анализ
Дни на рынке Листинг метаданных Скорость рынка
История цен Журнал изменения цен Анализ тенденций
Налог на недвижимость Налоговый учет Инвестиционный анализ
сборы ТСЖ Детали листинга Анализ затрат

Рабочий процесс анализа рынка

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

Часто задаваемые вопросы

Законен ли сбор данных о недвижимости?

Данные публичного листинга, как правило, можно очистить. Избегайте сбора личной информации о продавцах или агентах. Всегда соблюдайте условия обслуживания сайта.

Как мне справиться с нумерацией страниц?

Увеличьте параметр страницы в своих URL-адресах. Большинство сайтов недвижимости используют шаблоны ?page=N или &offset=N.

Какой тип CAPTCHA самый сложный на сайтах недвижимости?

страница Cloudflare-защиты в staging на агрегаторах MLS является наиболее сложным — для него требуются параметры прокси. reCAPTCHA v3 на крупных порталах встречается часто, но надежно решается CaptchaAI.

Связанные руководства

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