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.9 KB · 119 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/saro.py : connecteur Les Constructions Saro (constructionsaro.com)5#   Immeubles locatifs haut de gamme à Saint-Hubert (Longueuil, Rive-Sud),6#   clientèle 50 ans et plus, secteur du parc de la Cité. Site ASP.NET7#   (ProducNet) une seule page rendue serveur : sections par modèle8#   (« 3 ½ Haut de gamme — 850 pi² », 4 ½, 5 ½, 7 ½ bi-génération) avec9#   diaporamas d'images par typologie. Aucun prix ni disponibilité publiés.10#   Granularité : une annonce par typologie/modèle.11# -----------------------------------------------------------------------------12from __future__ import annotations1314import re1516from bs4 import BeautifulSoup1718from ..schema import Listing, normalize_unit_type19from .base import BaseConnector2021BASE = "https://constructionsaro.com"22PAGE_URL = f"{BASE}/Default.aspx"2324CITY = "Longueuil"25SECTOR = "Saint-Hubert"2627# « 3 ½ Haut de gamme » suivi de « 850 pi2 … » (texte aplati de la section)28MODEL_RE = re.compile(29    r"(\d)\s*½\s*Haut de gamme\s*(?:Bi-g[ée]n[ée]ration[\w\s]{0,20}?)?"30    r"([\d\s]{3,6})\s*pi", re.I)31DIAPO_IMG_RE = re.compile(32    r'href="(Images/Diaporama([A-Za-z0-9]+)/[^"]+\.(?:jpe?g|png))"', re.I)33PHONE_RE = re.compile(r"(\d{3})[.\-](\d{3})[.\-](\d{4})")3435# suffixe du dossier diaporama par typologie (3 -> Diaporama3etDemi…)36DIAPO_OF = {"3": "3etDemi", "4": "4etDemi", "5": "5etDemi", "7": "7etDemi"}373839class SaroConnector(BaseConnector):40    source_id = "saro"41    request_delay = 0.64243    def fetch(self) -> list[Listing]:44        listings: list[Listing] = []45        try:46            html = self.get(PAGE_URL).text47        except Exception:48            return listings49        soup = BeautifulSoup(html, "html.parser")50        for tag in soup(["script", "style", "noscript"]):51            tag.decompose()52        text = re.sub(r"\s+", " ", soup.get_text(" ", strip=True))5354        # commodités : liste « star » de l'immeuble (éléments h5 concaténés)55        amenities: list[str] = []56        for kw, label in [57            (r"aire ouverte avec très grande salle de bain",58             "Condos à aire ouverte, très grande salle de bain"),59            (r"walk-in", "Garde-robe walk-in"),60            (r"insonorisation sup[ée]rieur", "Insonorisation supérieure"),61            (r"Cam[ée]ra de surveillance", "Caméra de surveillance et entrée contrôlée"),62            (r"Plancher en lattes", "Plancher en lattes et céramique"),63            (r"Air climatis[ée]", "Air climatisé"),64            (r"Salle de lavage ind[ée]pendante", "Salle de lavage indépendante"),65            (r"rangement int[ée]rieure?", "Rangement intérieur"),66            (r"Cuisine avec ilo?t", "Cuisine avec îlot"),67            (r"Porte patio", "Porte patio"),68            (r"Grand balcon terrasse", "Grand balcon terrasse"),69            (r"ascenseur", "Ascenseur"),70            (r"50 ans et plus", "Pour les 50 ans et plus"),71        ]:72            if re.search(kw, text, re.I):73                amenities.append(label)7475        contact: dict = {}76        m = PHONE_RE.search(text)77        if m:78            contact["phone"] = f"{m.group(1)}-{m.group(2)}-{m.group(3)}"7980        # images par diaporama de typologie81        imgs_by_type: dict[str, list[str]] = {}82        for path, diapo in DIAPO_IMG_RE.findall(html):83            imgs_by_type.setdefault(diapo, []).append(f"{BASE}/{path}")8485        seen: set[str] = set()86        for n, area in MODEL_RE.findall(text):87            if n in seen:88                continue89            seen.add(n)90            ut = normalize_unit_type(f"{n}½")91            try:92                sqft = float(area.replace(" ", ""))93            except ValueError:94                sqft = None95            bigen = (n == "7")96            desc = ("Immeuble locatif neuf haut de gamme des Constructions "97                    "Saro à Saint-Hubert (Longueuil), secteur du parc de la "98                    "Cité, pour les 50 ans et plus. Construction supérieure "99                    "avec plancher de béton et ascenseur.")100            if bigen:101                desc = "Modèle bi-génération sur deux étages. " + desc102            listings.append(Listing(103                source=self.source_id,104                external_id=f"saro-{n}.5",105                url=PAGE_URL,106                title=f"{n} ½ Haut de gamme — Constructions Saro"107                      + (" (bi-génération)" if bigen else ""),108                address="",109                sector=SECTOR,110                city=CITY,111                unit_type=ut,112                area_sqft=sqft,113                description=desc,114                amenities=list(amenities),115                details={"contact": dict(contact)} if contact else {},116                images=imgs_by_type.get(DIAPO_OF.get(n, ""), [])[:15],117            ))118        return listings119