UK Planning Intelligence Scraper and CRM
FastAPI · Supabase · Idox Scraping · Proxy Rotation · Edge Functions

The Challenge
JSESSIONID session-based pagination. Standard HTTP scraping returned empty pages because the portal requires an active session with valid CSRF tokens extracted from hidden form fields. The portal rate-limits aggressively, returning 429 responses that block the IP for extended periods. The HTML structure changes without notice, so the scraper would silently produce corrupt data when table layouts or CSS class names shifted. Duplicate records were a constant issue because the same planning application appears across multiple search result pages with slightly different formatting. The client needed every application within the Chiltern Beechwoods SAC Zone of Influence captured and scored, but the zone boundary is a geographic radius, not a simple postcode prefix list. A postcode-based filter missed applications at zone edges where postcodes straddle the boundary.The Approach
JSESSIONID cookie, then parses hidden form fields for CSRF tokens. It spoofs Chrome 145 on macOS with full Sec-Fetch and sec-ch-ua headers. Twenty residential proxies from Webshare rotate every seven requests with startup randomization and five-minute per-IP cooldown tracking on 429 responses. When rotating, the system clears cookies, re-initializes the session on the new IP, and extracts fresh CSRF tokens. The HTML parser uses BeautifulSoup with lxml to extract application details, agent contacts, postcodes via regex, and dwelling counts through three regex strategies in priority order. The catchment filter runs four steps on every application: geocode the postcode via postcodes.io, calculate Haversine distance from the SANG site and SAC boundary, check dwelling count thresholds by zone, filter by application type whitelist, and scan against seventy-six noise keywords. The lead scorer assigns points based on dwelling count, zone proximity, keyword matches, and application stage. Qualified applications are upserted into Supabase PostgreSQL keyed on reference number, preserving existing status and dwelling data on re-scrape. Supabase Edge Functions written in TypeScript on the Deno runtime handle confirmation and follow-up emails by posting to the FastAPI backend SMTP relay because Deno cannot open raw TCP sockets. HTML sanity checks validate page structure before parsing and upload raw snapshots to Supabase Storage on failure. The system deploys across three platforms: Vercel for the static React SPA frontend, Render for the FastAPI Docker container, and Supabase for database, auth, cron, edge functions, and storage.System Architecture
Deployment Topology
Deployment Topology
Three-platform architecture: Vercel, Render, and Supabase. Click a node to view its configuration details.
Interactive Demo
Walk through the system design and data flow for this project step by step.
How the Planning Scraper Works
Step 1Idox Portal Session Initialization
The scraper begins by establishing a valid session with the Buckinghamshire Council Idox planning portal. It sends a GET request to the advanced search page to receive a JSESSIONID cookie, then parses the HTML form to extract hidden fields containing CSRF tokens. These tokens are stored and included in every subsequent POST request. The request headers spoof Chrome 145 on macOS with full Sec-Fetch and sec-ch-ua headers to pass bot detection. If the session initialization fails on the current proxy IP, the system rotates to the next proxy and retries up to three times.
The Idox portal requires both a valid session cookie and CSRF tokens from hidden form fields for every search request. Without extracting these on each new proxy IP, the portal returns empty results. This is why simple HTTP scraping fails and why session re-initialization is mandatory on every proxy rotation.
Overview
pg_cron for daily scheduling. The validation confirmed zero missed applications across three consecutive runs.Business Impact
Technical Highlights
- Idox portal scraper with JSESSIONID session management, CSRF token extraction, and browser fingerprint spoofing
- 20 residential proxy rotation with round-robin cycling every 7 requests, startup randomization, and 5-minute per-IP cooldown on 429 responses
- Structure-aware page classification into schedule, definition, preamble, and operative types for legal document parsing
- 4-step catchment filter: Haversine distance from SANG/SAC boundary, dwelling count threshold, application type whitelist, 76-keyword noise filter
- Lead scoring engine with base 25 points, zone proximity bonus, dwelling count multiplier, keyword hits, and noise penalties
- Supabase Edge Functions for automated confirmation and follow-up emails via FastAPI SMTP relay
- HTML sanity checks with HTMLStructureChangedError, raw HTML snapshot uploads to Supabase Storage, and alert emails on portal markup changes
- Idempotent upserts keyed on planning reference number with status and dwelling data preservation on re-scrape
- Historical backfill with stateful 2-day backward chunking via backfill_tracking table
- pg_cron daily trigger at 07:00 UTC calling the FastAPI backend on Render
Want results like this for your infrastructure?
I specialize in taking complex AI pipelines and cloud setups from concept to high-availability production. Let's discuss how to optimize your workloads, secure your environment, and reduce cloud costs.