🏠 Finding underpriced London properties
I was flat-hunting in London and, like anyone who’s done this, found the process painful. Rightmove gives you thousands of listings but no way to tell which ones are actually good value. So I built a tool that scrapes Rightmove and uses statistical analysis to surface underpriced properties.
How it works
The core idea is simple: compare each listing’s price-per-square-foot against similar properties in the same area. Properties that are cheap relative to their peers are worth investigating.
The app groups properties by postcode sector, bedroom count, and bathroom count. For each group, it computes the mean and standard deviation of price-per-square-foot. Each property then gets a deviation percentage (how far it is from its group average) and a z-score (how many standard deviations from the mean).
The results are shown on an interactive Leaflet map with a heatmap overlay — “hot” areas literally mean good deals (negative price deviation). Markers are colour-coded on a diverging scale: blues for underpriced, oranges/reds for overpriced. There’s also a sortable table alongside the map.
The scraper
The Rightmove parser is probably the most robust part. It tries three data sources per listing page in priority order:
- JSON-LD structured data
- Embedded page JSON (Rightmove stores a lot in JavaScript objects on the page)
- HTML fallback with BeautifulSoup
It handles multiple URL formats (modern /properties/{id} and legacy patterns), deals with 403/404/410 responses gracefully, and automatically converts square metres to square feet.
Practical features
Beyond the analysis, the app has a few features that made it genuinely useful during my flat hunt:
- Bulk URL ingestion — paste multiple Rightmove URLs and the app scrapes them all, deduplicating by canonical URL
- Offer price tracking — it prefers an
offer_price(what you actually offered) over thelisted_price, which is useful for tracking negotiated prices - CRUD management page for manually adding off-market or privately sourced listings
- Minimum group size filter (default 3) to prevent unreliable stats from tiny samples
Stack
Flask, SQLite, BeautifulSoup for scraping, Leaflet.js with the leaflet.heat plugin for mapping. No npm, no bundler, no frontend framework — just vanilla JS. The whole backend is a single 440-line Python file.
Deployed on Render with optional HTTP Basic Auth for sharing with friends who were also flat-hunting.