CAPSOLVER
Blog
Cloudflare Challenge Recovery for Property Price Monitoring

How to Solve Cloudflare Challenge for Property Price Monitoring

Logo of CapSolver

Adélia Cruz

How to use CapSolver

28-Aug-2026

TL;DR

  • Use official price indexes, public transaction datasets, licensed listing feeds, and partner APIs before authorized page collection.
  • Separate asking price, sold price, rent, valuation estimate, and price-per-area; never compare unlike measures.
  • When a supported Cloudflare Challenge interrupts an approved page fallback, use AntiCloudflareTask with a static or sticky proxy and consistent Chrome user agent.
  • Keep cf_clearance, proxy credentials, and raw page content in short-lived runtime storage, not analytics or model context.
  • Alert only on confirmed, comparable changes and retain source, timestamp, parser version, and evidence hash for audit.

Introduction

Property price monitoring is reliable only when every observation has a clear meaning and provenance. A listing's asking price is not a recorded sale, an automated valuation is not a transaction, and a market index cannot be treated as a property-level quote. The strongest pipeline starts with official datasets and licensed feeds, normalizes property identity and price type, and uses authorized public pages only for defined gaps. If a supported Cloudflare Challenge replaces an expected listing page, CapSolver can provide a controlled recovery step, but the workflow must preserve its proxy, Chrome user agent, cookie scope, and source policy. This guide covers the data-source hierarchy, observation schema, comparability rules, challenge detection, AntiCloudflareTask, short-lived session recovery, price-change confirmation, quality controls, and responsible use.

Define the Price Question

A property monitoring job should specify the exact signal it needs. Examples include:

  • Did the asking price for a known active listing change?
  • Did a recorded sale appear for a parcel or title identifier?
  • Did rent for a defined unit type change?
  • Did a market-level index move after a new release?
  • Did a property's status change from active to pending or sold?

Do not collapse these questions into one generic price field.

python Copy
monitoring_job = {
    "canonical_property_id": "prop_83f12",
    "market": "US-CA-San-Francisco",
    "source_priority": [
        "official_transaction_dataset",
        "licensed_listing_feed",
        "partner_api",
        "authorized_public_page",
    ],
    "price_types": ["ASKING_SALE", "RECORDED_SALE"],
    "alert_rules": {
        "asking_change_percent": 3.0,
        "status_changes": ["ACTIVE_TO_PENDING", "PENDING_TO_SOLD"],
    },
}

The CapSolver web-scraping blog covers related data-pipeline patterns, and the CapSolver web-scraping FAQ provides operational guidance for authorized public data collection.

Use a Source Hierarchy

Official and licensed data should be the primary layer. The U.S. Federal Housing Finance Agency's House Price Index is a public collection of repeat-sales indexes covering multiple geographic levels. It is valuable for market benchmarks, but it is not a property-level listing feed.

For recorded transaction data, use the relevant land registry, recorder, or licensed provider. The U.K. government's Price Paid Data is an example of an official transaction dataset. Licensed multiple-listing or portal APIs may provide current asking-price and status observations under their own contracts.

Source Price meaning Recommended role Main limitation
Official price index Market trend Benchmark and anomaly context Not property-level
Public transaction record Recorded sale Verification and history Publication delay varies
Licensed listing feed Current asking price and status Primary active-listing source Contract and coverage limits
Partner API Defined commercial fields Structured operational source Provider-specific semantics
Authorized public page Buyer-facing validation Gap coverage and QA Layout and traffic validation

Browser collection should be a fallback for a documented data gap, not a replacement for an available licensed feed.

Build a Comparable Property Identity

Property identity is difficult because addresses change format, units can be omitted, and one building can contain many parcels.

python Copy
from dataclasses import dataclass

@dataclass(frozen=True)
class PropertyIdentity:
    canonical_property_id: str
    country: str
    administrative_area: str
    locality: str
    postal_code: str
    street_number: str
    street_name: str
    unit: str | None
    parcel_id: str | None
    latitude: float | None
    longitude: float | None

Match records conservatively. Parcel or title identifiers are stronger than normalized addresses; exact unit identifiers are stronger than building-level coordinates. Route ambiguous matches to review.

The CapSolver Python web-data guide offers implementation context for structured extraction pipelines.

Store Price Type and Evidence

python Copy
from dataclasses import dataclass, field
from datetime import datetime, timezone

@dataclass
class PriceObservation:
    canonical_property_id: str
    source: str
    source_record_id: str | None
    source_url: str | None
    price_type: str
    amount: float
    currency: str
    area_value: float | None
    area_unit: str | None
    status: str | None
    market: str
    evidence_hash: str
    parser_version: str
    observed_at: str = field(
        default_factory=lambda: datetime.now(timezone.utc).isoformat()
    )

Use a controlled price-type vocabulary:

python Copy
PRICE_TYPES = {
    "ASKING_SALE",
    "RECORDED_SALE",
    "ASKING_RENT_MONTHLY",
    "VALUATION_ESTIMATE",
    "PRICE_PER_AREA",
    "MARKET_INDEX",
}

Never compare ASKING_SALE directly with RECORDED_SALE or VALUATION_ESTIMATE. Preserve currency and area units.

Detect a Cloudflare Challenge Before Parsing

A challenge can return an interstitial instead of the expected property page. Do not interpret missing price selectors as delisting or a zero value.

python Copy
async def classify_property_page(page, expected_selector: str) -> str:
    if await page.locator(expected_selector).count():
        return "PROPERTY_PAGE"

    title = (await page.title()).strip().lower()
    html = (await page.content()).lower()

    if "just a moment" in title:
        return "CLOUDFLARE_CHALLENGE"
    if "challenge-platform" in html or "cf-chl-" in html:
        return "CLOUDFLARE_CHALLENGE"
    if "listing no longer available" in html:
        return "LISTING_UNAVAILABLE"
    return "UNKNOWN_PAGE"

Maintain source-specific fixtures. A generic marker is a routing hint, not proof that every target can or should be recovered.

The CapSolver Cloudflare product page describes the supported service, and the CapSolver Cloudflare blog contains additional implementation guidance.

Use the Official Cloudflare Challenge Task

CapSolver's Cloudflare Challenge documentation defines AntiCloudflareTask.

Field Required Property-monitoring use
type Yes AntiCloudflareTask
websiteURL Yes Exact approved listing or public record page
proxy Yes Static or sticky proxy used for the request session
userAgent Optional Same supported Chrome user agent
html Conditional Fresh challenge HTML from the same sticky session

The solution can contain cf_clearance, token, and user agent. These are short-lived runtime values, not property data.

Cloudflare's challenge documentation explains the role of challenge mechanisms. Recovery capability does not change source permissions or contractual limits.

Resolve Source Policy Before Creating a Task

python Copy
import os
from urllib.parse import urlparse

SOURCE_POLICY = {
    "approved-listings.example": {
        "purpose": "asking-price-validation",
        "proxy_profile": "property_us_west",
        "max_checks_per_hour": 2,
    },
}

PROXY_VAULT = {
    "property_us_west": os.environ["PROPERTY_PROXY_US_WEST"],
}


def approved_source(url: str) -> dict:
    host = urlparse(url).hostname
    policy = SOURCE_POLICY.get(host)
    if not policy:
        raise PermissionError("Property source is not approved")
    return policy

The target URL should come from a registered source record, not model or user-provided free text in a production scheduler.

Create AntiCloudflareTask

python Copy
import capsolver

capsolver.api_key = os.environ["CAPSOLVER_API_KEY"]


def solve_property_page_challenge(
    url: str,
    chrome_user_agent: str,
    fresh_html: str = "",
) -> dict:
    policy = approved_source(url)
    proxy = PROXY_VAULT[policy["proxy_profile"]]

    task = {
        "type": "AntiCloudflareTask",
        "websiteURL": url,
        "proxy": proxy,
        "userAgent": chrome_user_agent,
    }
    if fresh_html:
        task["html"] = fresh_html

    solution = capsolver.solve(task)
    cookies = solution.get("cookies") or {}
    clearance = cookies.get("cf_clearance") or solution.get("token")
    if not clearance:
        raise RuntimeError("Cloudflare Challenge solution is incomplete")

    return {
        "cookies": cookies,
        "user_agent": solution.get("userAgent") or chrome_user_agent,
        "proxy_profile": policy["proxy_profile"],
    }

Use a static or sticky proxy and preserve it for initial request, task creation, and page recovery. Do not put proxy credentials in logs.

Resume with the Same Request Identity

The official CapSolver documentation requires a TLS-capable request client and consistent session details. Keep that implementation inside a tested source adapter.

python Copy
async def recover_property_document(
    source_adapter,
    url: str,
    challenge_html: str,
    user_agent: str,
) -> str:
    solution = solve_property_page_challenge(
        url=url,
        chrome_user_agent=user_agent,
        fresh_html=challenge_html,
    )

    response = await source_adapter.fetch_with_session(
        url=url,
        proxy_profile=solution["proxy_profile"],
        user_agent=solution["user_agent"],
        cookies=solution["cookies"],
    )

    if response.status_code != 200:
        raise RuntimeError("Recovered request did not return HTTP 200")
    if "challenge-platform" in response.text.lower():
        raise RuntimeError("Challenge remained after one recovery attempt")
    return response.text

fetch_with_session is an application-owned adapter that uses the approved TLS profile. It should not expose cookies, proxy values, or headers to analytics.

Parse the Price with a Source Adapter

Public pages vary. Use one adapter per approved source with explicit selectors and semantic checks.

python Copy
from decimal import Decimal, InvalidOperation

class SourceAdapter:
    source_name = "approved-listings"
    parser_version = "2026-08-28.1"

    def parse_asking_price(self, document) -> dict:
        raw = document.select_one('[data-testid="asking-price"]')
        if raw is None:
            raise ValueError("Asking-price field not found")

        currency = raw.get("data-currency")
        amount_text = raw.get("data-amount")
        try:
            amount = Decimal(amount_text)
        except (InvalidOperation, TypeError):
            raise ValueError("Invalid asking-price amount")

        if amount <= 0 or not currency:
            raise ValueError("Incomplete asking-price observation")

        return {
            "price_type": "ASKING_SALE",
            "amount": float(amount),
            "currency": currency,
        }

Do not treat a marketing estimate, monthly mortgage illustration, or “from” price as the asking price.

Normalize Comparable Observations

python Copy
from decimal import Decimal


def comparable(left: PriceObservation, right: PriceObservation) -> bool:
    return all([
        left.canonical_property_id == right.canonical_property_id,
        left.price_type == right.price_type,
        left.currency == right.currency,
        left.market == right.market,
    ])


def percentage_change(previous: float, current: float) -> float:
    if previous <= 0:
        raise ValueError("Previous price must be positive")
    return float(
        (Decimal(str(current)) - Decimal(str(previous)))
        / Decimal(str(previous))
        * 100
    )

If area changes, a unit is added, or a listing is relisted under a different property identity, require review rather than reporting a false price movement.

Confirm Price Changes

A single observation can be noisy. Use repeat confirmation or source corroboration.

python Copy
def confirmed_price_change(
    observations: list[PriceObservation],
    threshold_percent: float,
) -> dict | None:
    if len(observations) < 3:
        return None

    baseline, first_new, second_new = observations[-3:]
    if not comparable(baseline, first_new):
        return None
    if not comparable(first_new, second_new):
        return None
    if first_new.amount != second_new.amount:
        return None

    change = percentage_change(baseline.amount, second_new.amount)
    if abs(change) < threshold_percent:
        return None

    return {
        "event": "CONFIRMED_PRICE_CHANGE",
        "previous": baseline.amount,
        "current": second_new.amount,
        "currency": second_new.currency,
        "change_percent": round(change, 2),
    }

For transaction datasets, use the source's publication timestamp and revision policy instead of browser repeat confirmation.

Keep Infrastructure and Market Signals Separate

Event Meaning Destination
PRICE_CHANGE_CONFIRMED Comparable price observation changed Analyst or customer alert
STATUS_CHANGED Active, pending, sold, or removed state changed Listing workflow
CHALLENGE_ENCOUNTERED Source presented a supported challenge Automation dashboard
RECOVERY_FAILED Recovery did not return expected page Operator review
PARSER_UNKNOWN Page returned but price semantics were unclear Data-quality queue
SOURCE_STALE Official feed or page has not refreshed Source health monitor

Never convert a challenge, timeout, or parser error into a price of zero or a delisted status.

The CapSolver errors FAQ provides diagnostic categories, and the CapSolver automation blog covers related recovery patterns.

Bonus Code: Use code WEBS at CapSolver Dashboard to get an extra 5% bonus on every recharge.

Production Controls

Control Recommended implementation
Source entitlement Contract or written approval tied to purpose
Source priority Official/partner data before page fallback
Target scope Host and path allowlist
Proxy Static or sticky server-side profile
User agent Same supported Chrome identity through recovery
Challenge retry One attempt, then review
Price semantics Explicit price type, currency, unit, and status
Evidence Source ID, timestamp, parser version, and hash
Alerts Confirmed comparable changes only
Retention Short-lived cookies; governed property observations

Use the CapSolver products page to review supported categories and the CapSolver CAPTCHA-solving FAQ for task-flow guidance.

Responsible Use

Monitor only data you are authorized to access. Follow listing-feed contracts, registry licenses, source terms, rate limits, privacy laws, and fair-housing requirements. Do not collect private account data, owner contact details, tenant information, application records, or restricted valuation reports without a valid legal basis. Keep challenge recovery scoped to an approved public or partner workflow. Technical capability does not grant permission.

Conclusion

Reliable property price monitoring depends on comparable observations and clear provenance. Official indexes and transaction datasets provide the baseline; licensed feeds provide current listing detail; authorized pages fill defined gaps. When a supported Cloudflare Challenge interrupts that fallback, use CapSolver with a static or sticky proxy, consistent Chrome user agent, short-lived cookies, one recovery attempt, and explicit page verification.

Start an approved property-data workflow with CapSolver, validate it against a controlled source, and add price-type, evidence, and confirmation rules before scaling alerts.

FAQ

Is the FHFA HPI a property-level price feed?

No. It is a market index built from repeat transactions and published at several geographic levels. Use it as a benchmark, not as an individual listing price.

Which CapSolver task handles Cloudflare Challenge?

Use the documented AntiCloudflareTask with the exact target URL and a static or sticky proxy. A consistent supported Chrome user agent and fresh challenge HTML may also be required.

Can asking price and recorded sale price be compared directly?

They should be stored as different price types. Any comparison must explain that one is a seller's offer and the other is a completed transaction.

Should a missing price after a challenge be treated as a delisting?

No. Record an infrastructure or parser error and verify the page state. A missing selector is not evidence that the listing was removed.

How long should cf_clearance be retained?

Keep it only for the short-lived approved request session. Do not store it in property analytics, model context, long-term logs, or customer alerts.

Compliance Disclaimer: The information provided on this blog is for informational purposes only. CapSolver is committed to compliance with all applicable laws and regulations. The use of the CapSolver network for illegal, fraudulent, or abusive activities is strictly prohibited and will be investigated. Our captcha-solving solutions enhance user experience while ensuring 100% compliance in helping solve captcha difficulties during public data crawling. We encourage responsible use of our services. For more information, please visit our Terms of Service and Privacy Policy.

More