Lou·Ka
Tous les logements à louer du Québec. Un seul endroit.
Agrégateur indépendant de logements locatifs — chaque annonce avec toutes ses photos, ses détails standardisés, et un lien direct vers l'annonce originale du gestionnaire. Toujours à jour, automatiquement.
Pourquoi Lou-Ka ?
Chercher un appartement au Québec, c'est ouvrir 70 sites web différents — chacun avec sa propre navigation, ses propres filtres, son propre format. Lou-Ka retourne le problème : un connecteur dédié par gestionnaire immobilier visite chaque site, normalise chaque annonce vers un schéma unique, et détecte les changements en continu.
Les sites d'agences n'offrent pas de webhooks. Lou-Ka reproduit l'équivalent : synchronisation périodique + hash de contenu → ajouts, mises à jour et retraits détectés automatiquement. Une annonce qui disparaît du site source disparaît de Lou-Ka.
L'architecture en 30 secondes
flowchart LR
subgraph Sources["74 gestionnaires immobiliers"]
S1["Logisco · Cogir · CAPREIT<br/>Immostar · DMA · Laberge<br/>Akelius · Devimco · Mondev<br/>… 68 connecteurs actifs"]
end
subgraph LouKa["Lou-Ka"]
C["Connecteurs<br/><i>1 adaptateur / site</i>"] --> N["Normalisation<br/><i>schéma Listing unique</i>"]
N --> D[("SQLite<br/>hash + diff")]
D --> A["API FastAPI<br/>/api/listings · /api/facets"]
A --> F["React 18 + Vite<br/>PWA mobile · thème clair"]
end
W["⏱ Watcher horaire<br/>(PM2)"] -.-> C
S1 --> C
F --> U["🔑 Locataire"]
| Couche | Rôle | Fichiers |
|---|---|---|
| Connecteurs | 1 module Python par gestionnaire : HTML rendu serveur, API JSON internes (Building Stack, RealVuu, Planpoint, Rentsync, source.immo, JetEngine…), ou Firecrawl pour les sites derrière Cloudflare | louka/connectors/*.py |
| Schéma | Listing standardisé : adresse, secteur, ville, type (3½…), prix, disponibilité, commodités, toutes les images |
louka/schema.py |
| Diff engine | Upsert par hash de contenu — nouvelle / modifiée / disparue (désactivée) | louka/db.py |
| API | Filtres ville / secteur / taille / prix / gestionnaire / recherche, facettes, stats, déclencheur de sync | louka/web.py |
| Frontend | Design « éditorial sharp » : Space Grotesk, ombres décalées, accent lime, ticker temps réel, bottom sheet mobile, galeries photos, PWA installable | frontend/ |
Démarrage rapide
git clone https://github.com/spboucher-ai/lou-ka.git && cd lou-ka
# Backend
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
# Frontend
cd frontend && npm install && npm run build && cd ..
# (optionnel) sites JavaScript/anti-bot
echo "FIRECRAWL_API_KEY=fc-votre-cle" > .env
# Ingestion puis service
.venv/bin/python run.py sync # toutes les sources (ou: run.py sync logisco msi)
.venv/bin/python run.py serve 8080 # → http://localhost:8080
.venv/bin/python run.py watch 60 # resynchronisation en boucle (minutes)Ajouter un gestionnaire (≈ 30 lignes)
L'enregistrement est auto-découvrant : déposez un module dans louka/connectors/,
c'est tout — aucun fichier partagé à modifier.
# louka/connectors/mon_agence.py
from ..schema import Listing, infer_city, normalize_unit_type, parse_price
from .base import BaseConnector
class MonAgenceConnector(BaseConnector):
source_id = "mon_agence"
def fetch(self) -> list[Listing]:
html = self.get("https://mon-agence.ca/logements").text # throttlé, poli
# ... parser les cartes, les fiches, les photos ...
return [Listing(
source=self.source_id, external_id="123",
url="https://mon-agence.ca/logement/123",
title="555, avenue Exemple", sector="Limoilou",
city=infer_city("Limoilou"), unit_type=normalize_unit_type("4 1/2"),
price=parse_price("1 250 $ / mois"), images=[...],
)]Puis : .venv/bin/python run.py sync mon_agence — et l'annonce apparaît sur le site,
avec sa fiche, sa galerie et son lien source. Ajoutez l'entrée correspondante dans
data/sources.json pour la page Sources.
API
| Endpoint | Description |
|---|---|
GET /api/listings?city=§or=&unit_type=&source=&price_min=&price_max=&q= |
Recherche filtrée, triée par prix |
GET /api/listings/{uid} |
Fiche complète (toutes les images, commodités, source) |
GET /api/facets |
Valeurs distinctes pour construire les filtres |
GET /api/sources |
Registre des 74 gestionnaires + compteurs + dernière sync |
GET /api/stats |
Totaux par région, loyer moyen, journal de synchronisation |
POST /api/sync |
Déclenche une synchronisation en arrière-plan |
Couverture
Ville de Québec & Lévis — Logisco, Cogir, Groupe Laberge, Immostar, DMA/Locago, Groupe Dallaire, Trudel, Immeubles Roussin, Immeubles Simard, MSI, Gestipro, Logisma, Lafrance & Mathieu, SIB, SDG, SGIQ, GIM Côté, Logisbourg, Bribourg, Paul-E. Richard, Headway, Contraste, Appartements Urbains, Picard, Brochu, GParadis, CAPREIT, Lokalia, Immoappart, OK Louer, et une douzaine de complexes (Huma, Le Clif, Terra, La Klé, Sentinelle, Rivero, Viridi, Quartier les Éléments…).
Grand Montréal — Akelius, InterRent, Boardwalk, Minto, MetCap, Realstar, Hazelview, Groupe Copley, Cromwell, Lynk/Olymbec, Trylon, Plan A, Lofts MTL, Axia, Mondev, Devimco, Collection Équinoxe (Batimo/EMD), Progim, Rentalys, UTILE, Werkliv, 1 Square Phillips, Firma, Le Domaine, Beaudoin, Denux, Gestion Montréal, Nid d'Amour, SHDM…
Chaque source non-connectable est documentée avec sa raison dans data/sources.json
(ex. : aucun prix affiché, inventaire vide, site placeholder).
Production
Déployé sous PM2 (3 processus) derrière ngrok :
lou-ka-web .venv/bin/python run.py serve 8095 # API + frontend
lou-ka-sync .venv/bin/python run.py watch 60 # resync horaire
lou-ka-ngrok ngrok http --url=www.lou-ka.com 8095 # tunnelPhilosophie d'exploitation : on ne pousse que le code — le serveur maintient ses données lui-même.
Principes
- Politesse — délai ≥ 0,5 s entre requêtes, garde-fous de crawl, User-Agent identifié.
- Fidélité — aucun prix inventé : si la source n'affiche pas de prix,
price = null. - Traçabilité — chaque fiche renvoie vers l'annonce originale du gestionnaire.
- Robustesse — un connecteur qui casse n'affecte jamais les autres (auto-découverte
tolérante, try/except par annonce, journal
sync_log).