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 · 101 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/evol.py : connecteur EVOL Saint-Jean (evolstjean.com)5#   Complexe locatif neuf au 270, rue de la Cabinetterie à6#   Saint-Jean-sur-Richelieu (par Maître Carré). Site PHP statique rendu7#   serveur : la page /les-unites.php affiche le prix plancher par typologie8#   (« 3 ½ à partir de 1 575$ », « 2 ½ COMPLET »…) et les inclusions.9#   Granularité : une annonce par typologie ; les typologies « COMPLET »10#   (tout loué) sont ignorées.11# -----------------------------------------------------------------------------12from __future__ import annotations1314import re1516from bs4 import BeautifulSoup1718from ..schema import Listing, normalize_unit_type, parse_price19from .base import BaseConnector2021BASE = "https://evolstjean.com"22PAGE_URL = f"{BASE}/les-unites.php"2324ADDRESS = "270, rue de la Cabinetterie, Saint-Jean-sur-Richelieu"25CITY = "Saint-Jean-sur-Richelieu"2627# blocs typologie : « 3 ½ » suivi de « à partir de 1 575$ » ou « COMPLET »28TYPE_BLOCK_RE = re.compile(29    r"(\d)\s*½\s*(COMPLET|à partir de\s*[\d\s,]{3,9}\s*\$)", re.I)30IMG_RE = re.compile(r'(?:src|href)="((?:/|images/)[^"]+\.(?:jpe?g|png|webp))"', re.I)31PHONE_RE = re.compile(r"(\d{3})\s(\d{3})-(\d{4})")323334class EvolConnector(BaseConnector):35    source_id = "evol"36    request_delay = 0.63738    def fetch(self) -> list[Listing]:39        listings: list[Listing] = []40        try:41            html = self.get(PAGE_URL).text42        except Exception:43            return listings44        soup = BeautifulSoup(html, "html.parser")45        for tag in soup(["script", "style", "noscript"]):46            tag.decompose()47        text = re.sub(r"\s+", " ", soup.get_text(" ", strip=True))4849        # inclusions (liste de la section « Inclusions »)50        amenities: list[str] = []51        for kw, label in [52            (r"Cinq [ée]lectrom[ée]nagers|5 [ée]lectrom[ée]nagers",53             "5 électroménagers (frigo, cuisinière, lave-vaisselle, laveuse, sécheuse)"),54            (r"Eau chaude incluse", "Eau chaude incluse"),55            (r"Climatisation int[ée]gr[ée]e", "Climatisation"),56            (r"c[âa]blodistribution et Internet incluses",57             "Câble et Internet inclus"),58            (r"Comptoirs en quartz", "Comptoirs en quartz"),59            (r"Balcons?-terrasses? priv[ée]s", "Balcon-terrasse privé"),60            (r"[Aa]scenseur", "Ascenseur"),61            (r"[Ss]tationnement int[ée]rieur", "Stationnement intérieur ($)"),62        ]:63            if re.search(kw, text):64                amenities.append(label)6566        # photos du site (galerie de l'immeuble)67        images = [f"{BASE}/{u.lstrip('/')}"68                  for u in dict.fromkeys(IMG_RE.findall(html))69                  if not re.search(r"logo|favicon|icon|plan", u, re.I)][:15]7071        contact: dict = {}72        m = PHONE_RE.search(text)73        if m:74            contact["phone"] = f"{m.group(1)}-{m.group(2)}-{m.group(3)}"7576        for n, label in TYPE_BLOCK_RE.findall(text):77            if "COMPLET" in label.upper():78                continue        # typologie entièrement louée en ce moment79            ut = normalize_unit_type(f"{n}½")80            price_label = re.sub(r"\s+", " ", label).strip()81            listings.append(Listing(82                source=self.source_id,83                external_id=f"evol-{n}.5",84                url=PAGE_URL,85                title=f"EVOL Saint-Jean — {ut}",86                address=ADDRESS,87                city=CITY,88                unit_type=ut,89                price=parse_price(price_label),90                price_label=price_label,91                description=("Complexe locatif neuf EVOL au cœur de "92                             "Saint-Jean-sur-Richelieu : unités lumineuses "93                             "aux finitions modernes (comptoirs en quartz, "94                             "dosseret céramique), balcon-terrasse privé, "95                             "généreuse fenestration."),96                amenities=list(amenities),97                details={"contact": dict(contact)} if contact else {},98                images=images,99            ))100        return listings101