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.2 KB · 107 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/signature_bois_franc.py : Signature Bois-Franc — condos locatifs5#   (location.signatureboisfranc.com) au 2020, rue Lucien-Thimens, quartier6#   Bois-Franc, Saint-Laurent (Montréal).7#   Techno : WordPress + FacetWP, mais la liste /unites-disponibles/ est8#   RENDUE CÔTÉ SERVEUR (toutes les cartes .unit-row dans le HTML initial,9#   pas de pagination) — simple parse regex des champs par carte.10#   Granularité : UNITÉ (numéro, pièces, étage, pi², « à partir de » $,11#   statut). On écarte « Unité louée » et « Réservée ».12# -----------------------------------------------------------------------------13from __future__ import annotations1415import re1617from .base import BaseConnector18from ..schema import Listing1920LIST_URL = "https://location.signatureboisfranc.com/unites-disponibles/"21ADDRESS = "2020, rue Lucien-Thimens"22CITY = "Montréal"23SECTOR = "Saint-Laurent"2425_ROW_SPLIT_RE = re.compile(r'<div class="unit-row">')26_FIELD_RE = {27    "number": re.compile(r'unit-number__text">\s*#?([^<]+?)\s*<'),28    "rooms": re.compile(r'unit-room__text">\s*([^<]+?)\s*<'),29    "floor": re.compile(r'unit-floor__text">\s*([^<]+?)\s*<'),30    "area": re.compile(r'unit-area__text">\s*([^<]+?)\s*<'),31    "price": re.compile(r'unit-price__text">\s*([^<]+?)\s*<'),32    "status": re.compile(r'unit-availability__text">\s*([^<]*?)\s*<'),33}34_PLAN_RE = re.compile(r'unit-plan[^>]*>.*?href="([^"]+\.pdf)"', re.S)35_AREA_NUM_RE = re.compile(r'([\d\s ,.]+)\s*pi')36_PRICE_NUM_RE = re.compile(r'([\d\s  ]{3,})\s*\$')37# statuts qui excluent l'unité de l'inventaire38_GONE_RE = re.compile(r'lou[ée]e?|r[ée]serv[ée]e?', re.I)394041class SignatureBoisFrancConnector(BaseConnector):42    """Signature Bois-Franc (Saint-Laurent, Montréal) — unités disponibles."""4344    source_id = "signature_bois_franc"45    request_delay = 0.846    use_detail_cache = False        # une seule page, tout est dans la liste4748    def fetch(self) -> list[Listing]:49        page = re.sub(r"\s+", " ", self.get(LIST_URL).text)50        out: list[Listing] = []51        seen: set[str] = set()52        for block in _ROW_SPLIT_RE.split(page)[1:]:53            f = {k: (rx.search(block).group(1).strip()54                     if rx.search(block) else "")55                 for k, rx in _FIELD_RE.items()}56            num = f["number"].lstrip("#").strip()57            if not num:58                continue59            status = f["status"]60            if _GONE_RE.search(status):61                continue                        # louée / réservée6263            ext_id = f"unite-{num}"             # un seul immeuble : nº stable64            if ext_id in seen:65                continue66            seen.add(ext_id)6768            area = None69            m = _AREA_NUM_RE.search(f["area"])70            if m:71                try:72                    area = float(re.sub(r"[\s  ,]", "", m.group(1)))73                except ValueError:74                    pass75            price = None76            m = _PRICE_NUM_RE.search(f["price"].replace("$", " $"))77            if m:78                try:79                    price = float(re.sub(r"[\s  ]", "", m.group(1)))80                except ValueError:81                    pass8283            details: dict = {}84            if f["floor"]:85                details["floor"] = f["floor"]86            plan = _PLAN_RE.search(block)87            if plan:88                details["floor_plan"] = plan.group(1)8990            out.append(Listing(91                source=self.source_id,92                external_id=ext_id,93                url=LIST_URL,                   # pas de page par unité94                title=f"Signature Bois-Franc — Unité {num}",95                address=ADDRESS,96                sector=SECTOR,97                city=CITY,98                unit_type=f["rooms"],99                price=price,100                price_label=(f"À partir de {price:.0f} $ /mois"101                             if price else ""),102                availability=status or "Disponible",103                area_sqft=area,104                details=details,105            ))106        return out107