
Adélia Cruz
Neural Network Developer

AntiCloudflareTask with a static or sticky proxy and consistent Chrome user agent.cf_clearance, proxy credentials, and raw page content in short-lived runtime storage, not analytics or model context.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.
A property monitoring job should specify the exact signal it needs. Examples include:
Do not collapse these questions into one generic price field.
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.
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.
Property identity is difficult because addresses change format, units can be omitted, and one building can contain many parcels.
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.
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:
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.
A challenge can return an interstitial instead of the expected property page. Do not interpret missing price selectors as delisting or a zero value.
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.
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.
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.
AntiCloudflareTaskimport 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.
The official CapSolver documentation requires a TLS-capable request client and consistent session details. Keep that implementation inside a tested source adapter.
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.
Public pages vary. Use one adapter per approved source with explicit selectors and semantic checks.
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.
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.
A single observation can be noisy. Use repeat confirmation or source corroboration.
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.
| 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.
| 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.
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.
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.
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.
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.
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.
No. Record an infrastructure or parser error and verify the page state. A missing selector is not evidence that the listing was removed.
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.
Diagnose Cloudflare Challenge flows with AntiCloudflareTask, stable proxy and user agent identity, fresh HTML, clearance handling, validation, and safe errors.

Build reliable ecommerce inventory monitoring with API-first sourcing, Cloudflare Challenge recovery, session consistency, stock evidence, and safe alerts.
