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.4 KB · 116 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/grandeur_natura.py : connecteur Grandeur Natura5#   (grandeurnatura.ca, Groupe Mathieux) — 6-plex locatifs neufs au 1199,6#   rue Marcel-de la Sablonnière, Terrebonne (Urbanova), J0N 1H0.7#   WordPress/Bootstrap rendu serveur : la page d'accueil décrit chaque8#   typologie dans un bloc « Unité locative - Appartement n ½ » (SDB,9#   chambres, garage, carrousel de photos par unité, plan PDF commun).10#   Les blocs « Maisons de prestige » sont des maisons à VENDRE : exclus.11#   Aucun prix publié (jamais inventé). Granularité TYPOLOGIE (3½/4½/5½).12# -----------------------------------------------------------------------------13from __future__ import annotations1415import re1617from bs4 import BeautifulSoup1819from ..schema import Listing20from .base import BaseConnector2122BASE = "https://grandeurnatura.ca"23ADDRESS = "1199, rue Marcel-de la Sablonnière"24CITY = "Terrebonne"25SECTOR = "Urbanova"2627UNIT_H3_RE = re.compile(r"^Unité locative\s*[–-]\s*Appartement\s*([2-6])\s*½")28SDB_RE = re.compile(r"(\d)\s*SDB", re.I)29BEDS_RE = re.compile(r"(\d)\s*chambres?\b", re.I)30GARAGE_RE = re.compile(r"(Garage[^.\n|]{0,40})", re.I)31IMG_OK_RE = re.compile(r"\.(?:jpe?g|png|webp)$", re.I)323334class GrandeurNaturaConnector(BaseConnector):35    source_id = "grandeur_natura"36    request_delay = 0.83738    def fetch(self) -> list[Listing]:39        listings: list[Listing] = []40        try:41            html = self.get(BASE + "/").text42        except Exception:43            return listings44        soup = BeautifulSoup(html, "html.parser")45        for h3 in soup.select("h3"):46            title = h3.get_text(" ", strip=True)47            m = UNIT_H3_RE.match(title)48            if not m:49                continue                       # « Maisons de prestige » = vente50            n = m.group(1)51            unit_type = f"{n}½"52            row = h3.find_parent("div", class_="row") or h3.parent53            text = row.get_text(" | ", strip=True)5455            baths = beds = None56            bm = SDB_RE.search(text)57            if bm:58                baths = float(bm.group(1))59            cm = BEDS_RE.search(text)60            if cm:61                beds = float(cm.group(1))62            amenities: list[str] = []63            gm = GARAGE_RE.search(text)64            if gm:65                amenities.append(gm.group(1).strip())6667            # phrase descriptive du bloc (le premier paragraphe substantiel)68            desc = ""69            for p in row.select("p"):70                t = p.get_text(" ", strip=True)71                if len(t) > 40:72                    desc = t73                    break7475            # photos du carrousel de l'unité (variantes @2x/redimensionnées76            # dédupliquées ; logos exclus)77            images: list[str] = []78            for img in row.select("img"):79                src = img.get("src") or img.get("data-src") or ""80                if src.startswith("/"):81                    src = BASE + src82                if not src.startswith("http") or not IMG_OK_RE.search(src):83                    continue84                if re.search(r"logo|icon|@2x|-\d+x\d+\.|-scaled", src, re.I):85                    continue86                if src not in images:87                    images.append(src)8889            details: dict = {"building": "Grandeur Natura"}90            plan = row.select_one('a[href$=".pdf"]')91            if plan is not None:92                href = plan.get("href") or ""93                details["plan_pdf"] = (BASE + href) if href.startswith("/") \94                    else href9596            listings.append(Listing(97                source=self.source_id,98                external_id=f"unite-{n}.5",    # typologie : stable99                url=BASE + "/",100                title=f"Grandeur Natura — {unit_type}",101                address=ADDRESS,102                sector=SECTOR,103                city=CITY,104                unit_type=unit_type,105                bedrooms=beds,106                bathrooms=baths,107                price=None,108                price_label="",109                availability="Sur demande",110                description=desc[:2000],111                amenities=amenities,112                details=details,113                images=images[:15],114            ))115        return listings116