Lou·Ka — tous les logements à louer du Québec, un seul endroit.
HTML 98.9%
Python 0.6%
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