# ----------------------------------------------------------------------------- # Lou-Ka — Agrégateur de logements à louer (province de Québec) # Auteur : Simon-Pierre Boucher — contact@spboucher.ai # connectors/primo.py : connecteur Primo Immobilier (primoimmobilier.ca — # condos locatifs de luxe en Montérégie : Lumicité Condominiums à # Saint-Mathieu-de-Beloeil et Quartier Trinité à Saint-Basile-le-Grand). # WordPress rendu serveur mais SANS inventaire d'unités : chaque page # projet affiche un seul prix plancher « À partir de 2075$/mois » (les # typologies 3½/4½/5½ n'apparaissent que dans le formulaire, sans prix). # Granularité = PROJET (une annonce par projet, prix « à partir de »). # external_id = slug de la page projet — stable. # ----------------------------------------------------------------------------- from __future__ import annotations import html as _html import re from ..schema import Listing from .base import BaseConnector BASE = "https://primoimmobilier.ca" # (slug page, nom du projet, ville) PROJECTS: tuple[tuple[str, str, str], ...] = ( ("saint-mathieu-de-beloeil", "Lumicité Condominiums", "Saint-Mathieu-de-Beloeil"), ("saint-basile-le-grand", "Quartier Trinité", "Saint-Basile-le-Grand"), ) H1_RE = re.compile(r"]*>([\s\S]*?)", re.I) FROM_RE = re.compile(r"À\s+partir\s+de\s+([\d\s ]{3,9})\$\s*/\s*mois", re.I) SCRIPT_RE = re.compile(r"<(script|style)[\s\S]*?", re.I) TAG_RE = re.compile(r"<[^>]+>") IMG_RE = re.compile( r'https://primoimmobilier\.ca/wp-content/[^"\s)]+\.(?:jpe?g|png|webp)', re.I) class PrimoConnector(BaseConnector): source_id = "primo" request_delay = 0.8 def fetch(self) -> list[Listing]: listings: list[Listing] = [] for slug, name, city in PROJECTS: url = f"{BASE}/{slug}/" try: html = self.get(url).text except Exception: continue flat = re.sub(r"[\s ]+", " ", TAG_RE.sub(" ", _html.unescape( SCRIPT_RE.sub(" ", html)))) pm = FROM_RE.search(flat) price = None if pm: try: price = float(re.sub(r"[\s ]", "", pm.group(1))) except ValueError: price = None if price is None or not (500 <= price <= 8000): continue # pas de prix publié = pas d'annonce hm = H1_RE.search(html) title = (re.sub(r"\s+", " ", _html.unescape( TAG_RE.sub(" ", hm.group(1)))).strip() if hm else f"Condos locatifs {name}") images = [u for u in dict.fromkeys(IMG_RE.findall(html)) if not re.search(r"logo|icon|favicon|banners", u, re.I) ][:10] listings.append(Listing( source=self.source_id, external_id=slug, url=url, title=title, address="", # adresse civique non publiée city=city, price=price, price_label=f"à partir de {int(price)} $/mois", description=f"{name} à {city} — condos locatifs de luxe de " "Primo Immobilier (typologies 3½ à 5½). " "Ascenseur, stationnement et rangement " "intérieurs, plafonds de 9 pieds, comptoirs de " "quartz, accessible aux personnes à mobilité " "réduite.", images=images, )) return listings