SPB Git forge

spb/lou-ka

Public

Lou·Ka — tous les logements à louer du Québec, un seul endroit.

232commits 1branches 0releases
172.9 MBsize
maindefault branch
2 days agolast push
HTML 98.9% Python 0.6%
4.3 KB · 105 lines python
Raw Blame History
1# -----------------------------------------------------------------------------2# Lou-Ka — Agrégateur de logements à louer (province de Québec)3# Auteur : Simon-Pierre Boucher — contact@spboucher.ai4# connectors/in_via.py : connecteur IN VIA appartements urbains5#   (inviamirabel.com — CAP Immobilier, appartements locatifs neufs au6#   14385 et 14365 rue Roger-Thomas, secteur Saint-Janvier à Mirabel,7#   phases de 32 unités). WordPress/Elementor rendu SERVEUR : la page8#   /appartements/ annonce les typologies offertes en location (3½, 4½,9#   5½) et les phases avec leur adresse ; AUCUN prix ni disponibilité par10#   unité publiés (« renseignez-vous sur la disponibilité ») — rien11#   d'inventé. Granularité TYPOLOGIE (external_id = « invia-N.5 », stable).12#   Les phases « À venir » ne créent pas d'annonce distincte.13# -----------------------------------------------------------------------------14from __future__ import annotations1516import re1718from bs4 import BeautifulSoup1920from ..schema import Listing, normalize_unit_type21from .base import BaseConnector2223BASE = "https://www.inviamirabel.com"24PAGE_URL = f"{BASE}/appartements/"25ADDRESS = "14385, rue Roger-Thomas"26SECTOR = "Saint-Janvier"27CITY = "Mirabel"2829# « Louez un appartement 3 ½, 4 ½ ou 5 ½ »30OFFER_RE = re.compile(r"Louez un appartement((?:\s*\d\s*½[,\s]*(?:ou)?\s*)+)",31                      re.I)32TYPO_RE = re.compile(r"(\d)\s*½")33PHASE_RE = re.compile(r"Phase\s*(\d+)\s+((?:\d{4,5}\s+)?[\wÀ-ü' -]+?)"34                      r"(?=\s*(?:Plans intérieurs|Phase|$))")35IMG_RE = re.compile(r"https://www\.inviamirabel\.com/wp-content/uploads/"36                    r"[^\"\s\\]+?\.(?:jpe?g|png|webp)", re.I)37IMG_SIZE_RE = re.compile(r"-\d+x\d+(?=\.(?:jpe?g|png|webp))", re.I)383940class InViaConnector(BaseConnector):41    source_id = "in_via"42    request_delay = 0.84344    def fetch(self) -> list[Listing]:45        try:46            html = self.get(PAGE_URL).text47        except Exception:48            return []49        soup = BeautifulSoup(html, "html.parser")50        text = re.sub(r"\s+", " ", soup.get_text(" ", strip=True))5152        # typologies réellement offertes (« Louez un appartement 3 ½, 4 ½ ou 5 ½ »)53        m = OFFER_RE.search(text)54        typos = TYPO_RE.findall(m.group(1)) if m else TYPO_RE.findall(text[:600])55        typos = list(dict.fromkeys(typos))56        if not typos:57            return []5859        # phases livrées (adresse civique) — « Phase 3 À venir » ignorée60        phases: list[str] = []61        for pm in PHASE_RE.finditer(text):62            label = pm.group(2).strip()63            if re.match(r"^\d{4,5}\s", label):64                phases.append(f"Phase {pm.group(1)} : {label}")6566        # description : paragraphes de présentation du site67        desc_parts: list[str] = []68        for pat in (r"(Déménagez dans un appartement[^.]+\.)",69                    r"(Vivez dans un espace de vie neuf[^.]+\.)",70                    r"(Chaque unité inclut un stationnement[^.]+\.)"):71            mm = re.search(pat, text)72            if mm:73                desc_parts.append(mm.group(1).strip())74        blurb = " ".join(desc_parts)7576        images = []77        for u in dict.fromkeys(IMG_RE.findall(html)):78            u = IMG_SIZE_RE.sub("", u)79            if not re.search(r"logo|icon|favicon|plan", u, re.I) \80                    and u not in images:81                images.append(u)8283        amenities = ["Comptoirs de quartz", "Air climatisé",84                     "Stationnement intérieur inclus",85                     "Espace de rangement au garage"]86        listings: list[Listing] = []87        for t in typos:88            unit_type = normalize_unit_type(f"{t} 1/2")89            listings.append(Listing(90                source=self.source_id,91                external_id=f"invia-{t}.5",92                url=PAGE_URL,93                title=f"Appartement {unit_type} — IN VIA Mirabel",94                address=ADDRESS,95                sector=SECTOR,96                city=CITY,97                unit_type=unit_type,98                description=(blurb + (" " + " | ".join(phases)99                                      if phases else ""))[:1200],100                amenities=amenities,101                details={"phases": phases} if phases else {},102                images=images[:20],103            ))104        return listings105