# ----------------------------------------------------------------------------- # Lou-Ka — Agrégateur de logements à louer (province de Québec) # Auteur : Simon-Pierre Boucher — contact@spboucher.ai # connectors/solaris.py : connecteur Solaris Boisbriand (solarisboisbriand.com) # Condos locatifs neufs au 1030, rue des Francs-Bourgeois, Boisbriand # (Faubourg Boisbriand). Site Wix mais rendu SERVEUR : la page /plans contient # dans son HTML statique les cartes « n ½ (x chambres) … à partir de NNNN$/mois » # (texte riche wixui, entités ½ / à). Aucune liste d'unités — # granularité : TYPOLOGIE (3 fiches : 3½ / 4½ / 5½, prix « à partir de »). # ----------------------------------------------------------------------------- from __future__ import annotations import html as _html import re from ..schema import Listing from .base import BaseConnector BASE = "https://www.solarisboisbriand.com" PLANS_URL = f"{BASE}/plans" ADDRESS = "1030, rue des Francs-Bourgeois, Boisbriand" CITY = "Boisbriand" # « 3 ½ (1 chambre) … à partir de 1800$/mois » (texte décodé, balises retirées) CARD_RE = re.compile( r"(\d)\s*½\s*\((\d)\s*chambres?\)" # typologie (n ½ (x chambres)) r".{0,400}?à\s*partir\s*de\s*" # libellé r"(\d[\d\s,]*)\s*\$\s*/\s*mois", re.S) IMG_RE = re.compile( r"https://static\.wixstatic\.com/media/[^\"'\s\\)]+" r"\.(?:jpe?g|png|webp)", re.I) SKIP_IMG_RE = re.compile(r"logo|favicon|icon|blur|~mv2\.png", re.I) class SolarisConnector(BaseConnector): source_id = "solaris" request_delay = 0.6 max_images = 10 def _images(self) -> list[str]: """Photos du projet (page d'accueil Wix, media wixstatic).""" try: html = self.get(f"{BASE}/").text except Exception: return [] out: list[str] = [] for u in dict.fromkeys(IMG_RE.findall(html)): u = u.split("/v1/")[0] # version pleine taille if not SKIP_IMG_RE.search(u) and u not in out: out.append(u) return out[: self.max_images] def fetch(self) -> list[Listing]: raw = self.get(PLANS_URL).text # entités (½, à…) puis balises -> texte plat text = _html.unescape(raw) text = re.sub(r"", " ", text, flags=re.S) text = re.sub(r"", " ", text, flags=re.S) text = re.sub(r"<[^>]+>", " ", text) text = re.sub(r"\s+", " ", text) images = self._images() listings: list[Listing] = [] seen: set[str] = set() for n, beds, amount in CARD_RE.findall(text): unit_type = f"{n}½" if unit_type in seen: continue seen.add(unit_type) amount = amount.strip().replace(" ", "").replace(",", "") try: price = float(amount) except ValueError: continue if not 300 <= price <= 20000: continue listings.append(Listing( source=self.source_id, external_id=f"solaris-{n}.5", url=PLANS_URL, title=f"Solaris Boisbriand — {unit_type}" f" ({beds} chambre{'s' if beds != '1' else ''})", address=ADDRESS, city=CITY, unit_type=unit_type, bedrooms=float(beds), price=price, price_label=f"à partir de {int(price)}$/mois", availability="Disponible", description=f"Condo locatif neuf {unit_type}" f" ({beds} chambre(s)) au Faubourg Boisbriand" " — prix « à partir de », selon l'unité.", images=list(images), )) return listings