# ----------------------------------------------------------------------------- # Lou-Ka — Agrégateur de logements à louer (province de Québec) # Auteur : Simon-Pierre Boucher — contact@spboucher.ai # connectors/sofia.py : connecteur Le Sofia — Groupe Lacombe # (sofiavarennes.com — 48 condos locatifs au 1845, rue du Souvenir, # Varennes, structure béton, face au fleuve). Site PHP/Perch CMS rendu # serveur : la page plans.php contient le tableau COMPLET des unités # (numéro, pi², chambres, modèle 3½/4½, plan JPG, loyer, disponibilité). # Seules les lignes « Date -> OUI » (badge Disponible) sont retenues — # les autres n'affichent ni loyer ni date. Granularité = UNITÉ. # external_id = numéro d'unité (stable, adresse unique). # ----------------------------------------------------------------------------- from __future__ import annotations import re from ..schema import Listing from .base import BaseConnector BASE = "https://sofiavarennes.com" LIST_URL = f"{BASE}/plans.php" ADDRESS = "1845, rue du Souvenir, Varennes" CITY = "Varennes" # une ligne d'unité =
([^<]*)
', re.I) AVAILABLE_RE = re.compile(r'>Date\s*OUI\b') DATE_TXT_RE = re.compile( r'Loyer[^<]*
\s*' r'([^<]*)
', re.I) PLAN_RE = re.compile(r'href="(perch/resources/[^"]+\.(?:jpe?g|png|webp))"', re.I) def _price(txt: str) -> float | None: m = re.search(r"(\d[\d\s ]{2,8})\$", txt or "") if not m: return None try: return float(re.sub(r"[\s ]", "", m.group(1))) except ValueError: return None class SofiaConnector(BaseConnector): source_id = "sofia" request_delay = 0.8 def fetch(self) -> list[Listing]: try: html = self.get(LIST_URL).text except Exception: return [] listings: list[Listing] = [] seen: set[str] = set() for chunk in ROW_SPLIT_RE.split(html)[1:]: chunk = chunk.split("