DEV Community

Gio Rich
Gio Rich

Posted on

Scraping Czech property auctions (portaldrazeb.cz) into JSON with Python

I built this actor; it's a paid tool on Apify with a free trial credit.

Portál dražeb (portaldrazeb.cz) is where Czech court executors publish their auctions: flats, family houses, land, cars and more, usually with a minimum bid of two-thirds of the appraised value. It's the largest source of execution auctions in the country, but the site has no export, no RSS and no public data API. If you want to know "which flats in Prague go under the hammer this month and at what discount", you click through a Vue app page by page.

I'm an 18-year-old engineering student, and I built Portál dražeb Scraper to turn the portal into clean JSON. This post shows how to call it, what the data looks like, and one small project: a daily alert for auctions priced well below their appraisal.

How it works, briefly

The listing pages load their data from the site's own JSON endpoint, with a CSRF token issued with the page. The actor loads each list once for a token and cookie, pages through the JSON in blocks of 200, and normalises every auction: prices, the RÚIAN address, region, coordinates, photos and document links. No browser, no login. The full upcoming list (786 auctions when I tested) takes about a minute.

Python example

pip install apify-client
Enter fullscreen mode Exit fullscreen mode
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")

run = client.actor("rel8ble/portal-drazeb-scraper").call(run_input={
    "listings": ["upcoming"],
    "categories": ["flat"],
    "locations": ["Praha"],
})

for a in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(a["startAt"][:10], a["minimumBid"], a["estimatedPrice"], a["title"], a["url"])
Enter fullscreen mode Exit fullscreen mode

Node example

npm install apify-client
Enter fullscreen mode Exit fullscreen mode
import { ApifyClient } from "apify-client";

const client = new ApifyClient({ token: "<YOUR_APIFY_TOKEN>" });

const run = await client.actor("rel8ble/portal-drazeb-scraper").call({
  listings: ["upcoming"],
  categories: ["vehicles"],
  maxPrice: 100000,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const a of items) console.log(`${a.title}: from ${a.minimumBid} CZK on ${a.startAt.slice(0, 10)} (${a.city})`);
Enter fullscreen mode Exit fullscreen mode

What comes back

One item per auction. This is a real record from a test run, trimmed:

{
  "listing": "upcoming",
  "auctionId": "poJ7z",
  "auctionNumber": "098EX01347/21-292",
  "url": "https://www.portaldrazeb.cz/drazba/098ex01347-21-292-poj7z",
  "title": "Byt v Praze - Krč o výměře 76,6 m2",
  "categoryEn": "Flat / apartment",
  "electronic": true,
  "startAt": "2026-10-06T08:00:00.000+00:00",
  "currency": "CZK",
  "estimatedPrice": 9150000,
  "minimumBid": 6100000,
  "minimumBidPctOfEstimate": 66.7,
  "bidIncrement": 15000,
  "cautionDeposit": 1300000,
  "region": "Hlavní město Praha",
  "city": "Praha",
  "address": "Za dvorem 1862/3, Krč, Praha",
  "latitude": 50.0183617,
  "longitude": 14.4627307,
  "images": ["https://www.portaldrazeb.cz/upload/auction-image/32vlN"],
  "documents": [
    { "type": "auction_decree", "typeLabel": "Auction decree (dražební vyhláška)", "url": "https://www.portaldrazeb.cz/upload/auction-document/NxZnn" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

In a run over all 818 upcoming and finished auctions, the region, district and city were filled for 97-98%, GPS for 96% and photos for 81%. The street address was filled for only 34%, because many executors publish only the municipality. For finished auctions, currentPrice is the final price. One flat in Prague's Kamýk district had an appraisal of 8,625,000 CZK, a minimum bid of 5,750,000 and sold for 7,050,000.

A privacy note: the portal's JSON also carries a "responsible person" block, and uploaded document file names sometimes contain the debtor's name. The actor drops both and keeps only the executor office's official contact details.

Use case: a daily alert for deep-discount auctions

The goal: every morning, list flats and houses whose minimum bid is below 70% of the appraisal and that start within the next 30 days, and skip the ones already seen.

import json, pathlib
from datetime import date, timedelta
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
seen_file = pathlib.Path("seen.json")
seen = set(json.loads(seen_file.read_text())) if seen_file.exists() else set()

run = client.actor("rel8ble/portal-drazeb-scraper").call(run_input={
    "listings": ["upcoming"],
    "categories": ["flat", "house"],
    "startFrom": date.today().isoformat(),
    "startTo": (date.today() + timedelta(days=30)).isoformat(),
})

new = []
for a in client.dataset(run["defaultDatasetId"]).iterate_items():
    pct = a.get("minimumBidPctOfEstimate")
    if pct is not None and pct < 70 and a["auctionId"] not in seen:
        new.append(a)
        seen.add(a["auctionId"])

for a in sorted(new, key=lambda a: a["minimumBidPctOfEstimate"]):
    print(f'{a["minimumBidPctOfEstimate"]}% | {a["minimumBid"]:,} CZK | {a["city"]} | {a["title"]} | {a["url"]}')

seen_file.write_text(json.dumps(sorted(seen)))
Enter fullscreen mode Exit fullscreen mode

Most real-estate auctions start at exactly two-thirds of the appraisal: 345 of the 541 I looked at sat at 66.7%, and another 105 were below that. So the 70 threshold catches nearly every first-round auction. Change it to 60 to see only the deeper discounts. Run it with cron or Task Scheduler, or skip the script: set up a Schedule in Apify Console and send the dataset to Slack or Google Sheets through an integration.

What it costs

Pricing is $3.00 per 1,000 results, and one result is one auction saved.

  • Every upcoming auction in Czechia (~800) = about $2.40
  • The daily alert above (a few dozen matching flats and houses) = a few cents per run
  • Filters are applied before saving, so you only pay for matches

Limits

These come straight from my testing:

  • Only portaldrazeb.cz. Voluntary and insolvency auctions published on other portals aren't included.
  • Descriptions stay in Czech. Categories are translated to English.
  • Street address for about a third of auctions; region, city and GPS for nearly all.
  • The "finished" list is short. The portal only shows recently ended auctions (32 when I tested), so schedule runs if you want a price history.
  • It's slow-ish on the portal side: fetching all ~800 upcoming auctions took about a minute, almost all of it waiting on the site.

If something breaks or a field is wrong, the Issues tab on the actor page reaches me directly.

Portál dražeb Scraper on Apify


This article was drafted with AI and published by me.

Top comments (0)