DEV Community

Cover image for I Built a Tool That Scrapes Facebook Groups and Uses a Fine-Tuned LLM to Parse Flat Listings
Minotaur
Minotaur

Posted on

I Built a Tool That Scrapes Facebook Groups and Uses a Fine-Tuned LLM to Parse Flat Listings

Finding a flat in Hyderabad through Facebook groups is painful. Hundreds of posts daily, zero structure - just walls of text with phone numbers, random pricing formats, and "DM for details." I built a tool to fix this.

The Problem

Indian cities have dozens of Facebook groups like "Flats Without Brokers Hyderabad" where landlords post listings. But:

  • No consistent format (rent might be "15k" or "₹15,000" or "fifteen thousand")
  • Buried in comments and unrelated posts
  • No way to filter by BHK, location, budget, or furnishing
  • You have to scroll endlessly, every single day

What I Built

A pipeline that:

  1. Scrapes Facebook groups using Playwright (headless browser, cookie-based auth)
  2. Parses unstructured text into structured data using a cascade of regex → fine-tuned LLM → keyword enrichment
  3. Stores listings in PostgreSQL (Supabase) with deduplication
  4. Serves a searchable web UI (Flask) with filters for rent, BHK, location, furnishing
  5. Runs on a schedule via GitHub Actions (scraping) + Render (web app)

Here's what the end result looks like - 304 listings, all parsed and filterable:

dashboard

Architecture

Facebook Groups (16 groups)
        │
        ▼
┌─────────────────┐
│  Playwright      │  Cookie auth, scroll, expand "See more"
│  Scraper         │  Extract raw post text
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  Cascade Parser  │
│                  │
│  1. Regex first  │  Fast extraction: rent, BHK, sqft, phone
│  2. LLM fallback │  Fine-tuned Gemma 3 1B (LoRA) for ambiguous fields
│  3. Enrichment   │  Keyword-based: amenities, parking, facing
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  PostgreSQL      │  Supabase (prod) / SQLite (dev)
│  + Dedup         │  Skip already-seen listings
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  Flask Web UI    │  Filter, search, wishlist
│  on Render       │  Mobile responsive
└─────────────────┘
Enter fullscreen mode Exit fullscreen mode

The Interesting Part: Cascade Parsing

The naive approach would be: throw every post at an LLM and get JSON back. But that's:

  • Slow (API call per listing)
  • Expensive at scale
  • Overkill for fields regex handles perfectly (rent, BHK, phone numbers)

Instead, I built a cascade:

Layer 1: Regex

Handles the easy stuff instantly:

# Rent: matches "15k", "₹15,000", "15000/-", "rent 15000"
match = re.search(r'(?:rent|₹|rs\.?)\s*(\d[\d,]*)', text, re.I)

# Phone: Indian mobile numbers with Unicode digit handling
normalized = text.translate(str.maketrans('𝟬𝟭𝟮𝟯𝟰𝟱𝟲𝟳𝟴𝟵', '0123456789'))
match = re.search(r'(?:\+91[\s-]?)?([6-9]\d{9})', normalized)
Enter fullscreen mode Exit fullscreen mode

Layer 2: Fine-Tuned LLM

For ambiguous fields (furnished status, gated community, available dates), I fine-tuned Gemma 3 1B using LoRA:

  • Training data: 200+ manually labeled listings from the actual Facebook groups
  • Finetuned on Apple Silicon (MPS) using HuggingFace transformers + PEFT
  • Runs locally via MLX for inference - zero API costs
  • Only called when regex returns null for these specific fields

Layer 3: Keyword Enrichment

Post-processing that catches what both regex and LLM miss:

  • Amenities detection (lift, gym, security, power backup)
  • Parking type inference
  • Floor/facing extraction from context

The result: ~85% of fields are filled by regex (fast, free), ~10% by the fine-tuned model, and ~5% by enrichment. Average parse time per listing: under 200ms.

You can filter by fully furnished, gated community, no broker, budget - and it actually works:

filters

And expanding a listing shows the raw Facebook post alongside the parsed structured data:

details

Fine-Tuning Details

# LoRA config for Gemma 3 1B
lora_config = LoraConfig(
    r=8,
    lora_alpha=16,
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
    lora_dropout=0.05,
)
Enter fullscreen mode Exit fullscreen mode

Training data format (chat-style JSONL):

{"messages": [
  {"role": "system", "content": "Extract flat rental info from Facebook posts. Return JSON only."},
  {"role": "user", "content": "2BHK semi furnished flat in Gachibowli..."},
  {"role": "assistant", "content": "{\"bhk\": 2, \"furnished\": \"semi\", \"location\": \"Gachibowli\", ...}"}
]}
Enter fullscreen mode Exit fullscreen mode

6 epochs, batch size 1, lr=2e-5. Trained in ~20 minutes on M-series MacBook.

Deployment

  • Scraper: Runs locally (Facebook blocks cloud IPs). Could move to a residential proxy setup.
  • Web app: Deployed on Render (free tier), auto-deploys from main
  • Database: Supabase PostgreSQL (free tier, SSL connection)
  • CI: GitHub Actions checks linting and deployment

Results

  • 557 listings scraped from 16 Hyderabad Facebook groups
  • Searchable by rent range, BHK, location, furnishing status
  • Wishlist feature to save favorites
  • Found my current flat through it

Tech Stack

Component Technology
Scraping Python, Playwright (async)
Parsing Regex + fine-tuned Gemma 3 1B (LoRA/MLX)
Database PostgreSQL (Supabase) / SQLite fallback
Web UI Flask, Jinja2, mobile-responsive CSS
Deployment Render (web), GitHub Actions (CI)
Auth Cookie-based Facebook session

What I'd Do Differently

  1. Add notifications - alert when a listing matching your criteria appears
  2. Better dedup - some listings are reposted with slight text changes
  3. Map view - plot listings on a Hyderabad map by location
  4. Multi-city - architecture supports it, just need more groups

Source Code

GitHub: github.com/MinotaurG/fb-flat-finder


If you're apartment hunting in an Indian city and drowning in Facebook group posts, feel free to fork this and point it at your city's groups. PRs welcome.

Top comments (0)