All projects01 / 08

AnunțulCasei

Seven property portals, one deduplicated search for Bucharest and Ilfov, with alerts.

live2026anuntulcasei.ro

01live

  • portals crawled7
  • SEO landing pages977
  • automated tests1,300+
  • listings re-sectored in one pass3,071

01 / Problem

The same flat on four sites, at four prices

Looking for an apartment in Bucharest means opening several portals and spotting the same listing again under a different price and different photos. I built a free search for sale listings in Bucharest and Ilfov that reads seven portals and shows each property once.

AnunțulCasei home page on desktop: the headline 'all for-sale listings in one place' beside an example feed of new Bucharest listings with prices and source tags (STO, OLX, P24)

02 / How it works

One schema for seven sources

Each portal has its own crawler, polite and rate-limited, that turns the page or API into one listing format and keeps the raw payload next to it. PostgreSQL with PostGIS stores them, Meilisearch serves search, and a worker matches every new or cheaper listing against saved searches and sends a Telegram or push alert.

AnunțulCasei home page on desktop: live counters for active listings and new listings today, the seven tracked portals, and an example Telegram alert with price, area and links to the original listing

03 / The hard part

Deciding what counts as the same property

Deduplication compares location, area, rooms, floor, price and photo hashes, and I only accepted it after it reached 95% precision on a labelled set of pairs. Seller-picked sectors turned out to be wrong often, so I derived the sector from map polygons, ran the fix as a dry run first, and then corrected 3,071 stored listings. When one portal put a captcha in front of its pages I dropped it instead of working around it.

04 / Result

Public, tested and watched

The site is live at anuntulcasei.ro with open sign-up, 977 generated landing pages and a Lighthouse SEO score of 92. The API and crawlers have more than 1,300 automated tests, a load test stayed comfortable at 100 simulated users, and a nightly report and a Grafana dashboard tell me what the crawlers and visitors did.

AnunțulCasei home page on a phone: headline, call-to-action buttons and the feed of new listings stacked in one column

How it works

Seven crawlers in, one searchable record out

Each portal has its own polite crawler. Everything lands in one format, is matched against what is already known, and is served from one database and one search index.

The same flow as a list, in order.

  1. Crawl: OLX. Goes to Validation and dedup.
  2. Crawl: Storia. Goes to Validation and dedup.
  3. Crawl: Publi24. Goes to Validation and dedup.
  4. Crawl: Anunțul Telefonic. Goes to Validation and dedup.
  5. Crawl: homezz. Goes to Validation and dedup.
  6. Crawl: lajumate. Goes to Validation and dedup.
  7. Crawl: romimo. Goes to Validation and dedup.
  8. Normalise: Validation and dedup. One format, raw payload kept; duplicates matched. Goes to PostgreSQL + PostGIS, Meilisearch.
  9. Store: PostgreSQL + PostGIS. One record per property. Goes to API, Alert worker (new and cheaper listings).
  10. Store: Meilisearch. Search index. Goes to API.
  11. Serve: API. FastAPI. Goes to Web app.
  12. Serve: Alert worker. Matches saved searches, sends Telegram or push. End of the path.
  13. Use: Web app. Search, map and saved searches. End of the path.

Stack, by layer

Backend
FastAPISQLAlchemyAlembicPostgreSQL + PostGISRedisarq
Search
Meilisearchimgproxy
Frontend
Next.jsTypeScriptMapLibre
Data
Python crawlersPlaywright
Operations
Docker ComposeCloudflare TunnelGrafana

Live from AnunțulCaseiOffline, last known values

Active listings
~122k
Unique properties
~67k
Portals healthy
n/a