# ----------------------------------------------------------------------------- # Lou-Ka — Agrégateur de logements à louer (province de Québec) # Auteur : Simon-Pierre Boucher — contact@spboucher.ai # connectors/inovario.py : connecteur Inovario (inovario.com) # Promoteur-gestionnaire de Châteauguay — deux projets locatifs : rue # St-Denis et Quartier du Centre Signature. WordPress/Elementor rendu # serveur : le sitemap Yoast (/sitemap_index.xml → page-sitemap.xml) liste # une page par modèle d'appartement (chateauguay--[-modele-…]). # Pas de prix ni d'unités individuelles publiés. Granularité : modèle # (typologie). # ----------------------------------------------------------------------------- from __future__ import annotations import re from bs4 import BeautifulSoup from .base import BaseConnector from ..schema import Listing, normalize_unit_type BASE = "https://inovario.com" SITEMAP = f"{BASE}/page-sitemap.xml" CITY = "Châteauguay" LOC_RE = re.compile(r"([^<]+)") MODEL_RE = re.compile(r"^https://inovario\.com/(chateauguay-[a-z0-9-]+)/?$") TYPE_RE = re.compile(r"(\d)-(?:1-2|et-demi)") SQFT_RE = re.compile(r"(\d{3,4})-pc") IMG_RE = re.compile(r'https://inovario\.com/wp-content/uploads/' r'[^"\s\\)]+\.(?:jpe?g|png|webp)', re.I) class InovarioConnector(BaseConnector): source_id = "inovario" request_delay = 0.6 def fetch(self) -> list[Listing]: listings: list[Listing] = [] try: xml = self.get(SITEMAP).text except Exception: return listings for url in LOC_RE.findall(xml): m = MODEL_RE.match(url.strip()) if not m: continue slug = m.group(1) mt = TYPE_RE.search(slug) if not mt: continue # pages de projet/hub, pas des modèles unit_type = normalize_unit_type(f"{mt.group(1)}½") if "st-denis" in slug: project = "Rue St-Denis" address = "rue Saint-Denis, Châteauguay" else: project = "Quartier du Centre Signature" address = "rue Principale, Châteauguay" sqft = None ms = SQFT_RE.search(slug) if ms: sqft = float(ms.group(1)) title, amenities, images = "", [], [] try: html = self.get(url).text soup = BeautifulSoup(html, "html.parser") h1 = soup.find("h1") if h1: title = h1.get_text(" ", strip=True) # « CARACTÉRISTIQUES DE NOS APPARTEMENTS » : liste à puces for li in soup.select("ul li"): t = li.get_text(" ", strip=True) if 3 < len(t) < 90 and not li.find("a"): amenities.append(t) images = [u for u in dict.fromkeys(IMG_RE.findall(html)) if not re.search(r"logo|icon|favicon", u, re.I)][:15] except Exception: pass variant = "" mv = re.search(r"(modele-[\w-]+|coin|centre|den)$", slug) if mv: variant = mv.group(1).replace("modele-", "modèle ") listings.append(Listing( source=self.source_id, external_id=slug, url=url, title=title or f"{unit_type} — {project}, Châteauguay", address=address, city=CITY, unit_type=unit_type, area_sqft=sqft, availability="", details={"project": project, **({"variant": variant} if variant else {})}, description=f"Appartement {unit_type} de style condo du " f"projet {project} à Châteauguay : planchers de " f"béton insonorisés, aires ouvertes et grande " f"fenestration. Prix sur demande.", amenities=list(dict.fromkeys(amenities))[:15], images=images, )) return listings