# ----------------------------------------------------------------------------- # 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é =
…
ROW_SPLIT_RE = re.compile(r'
(Emplacement|Unité|Pi2|Pièces|Modèle|Loyer)

' r'

([^<]*)

', 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(" finalize availability = "Disponible" dm = DATE_TXT_RE.search(chunk) if dm and dm.group(1).strip(): availability = f"Disponible : {dm.group(1).strip()}" images = [f"{BASE}/{p}" for p in dict.fromkeys(PLAN_RE.findall(chunk))] listings.append(Listing( source=self.source_id, external_id=unit, url=LIST_URL, title=f"Le Sofia — unité {unit}", address=ADDRESS, city=CITY, unit_type=unit_type, bedrooms=bedrooms, price=price, price_label=f"{int(price)} $ /mois" if price else "", availability=availability, area_sqft=area, description=f"Unité {unit} au Sofia ({ADDRESS}) — immeuble " "locatif du Groupe Lacombe, structure de béton, " "ascenseur, climatisation, garage et borne de " "recharge.", images=images, )) return listings