# ----------------------------------------------------------------------------- # Lou-Ka — Agrégateur de logements à louer (province de Québec) # Auteur : Simon-Pierre Boucher — contact@spboucher.ai # connectors/condos18.py : connecteur Condos Locatifs 18 # (condoslocatifs18.ca — 1195, 18e Rue, Limoilou, Québec, à deux pas de # l'hôpital de l'Enfant-Jésus/CHU). One-pager maison rendu serveur : les # prix sont affichés PAR TYPE d'unité (« 3 1/2 … À partir de 1200$ par # mois ») dans des onglets #s312/#s412/#s512, chacun listant ses variantes # de plans (Emplacement / Description / Superficie). Une annonce par type # d'unité — la granularité du site — avec la fourchette de superficies des # variantes, les plans (lightbox) et les photos du modèle en galerie. # ----------------------------------------------------------------------------- from __future__ import annotations import re from bs4 import BeautifulSoup from ..schema import Listing, normalize_unit_type, parse_price from .base import BaseConnector BASE = "https://condoslocatifs18.ca" PAGE_URL = f"{BASE}/" ADDRESS = "1195, 18e Rue, Québec, G1J 0G5" SECTOR = "Limoilou" CITY = "Québec" LAT, LNG = 46.835710, -71.227340 # marqueur Google Maps de la page AREA_RE = re.compile(r"Superficie\s*:\s*([\d\s]+)\s*pi", re.I) FLOOR_RE = re.compile(r"Emplacement\s*:\s*([^\n<]+)", re.I) DESC_RE = re.compile(r"Description\s*:\s*([^\n<]+)", re.I) # photos du modèle / de l'immeuble (hors icônes, logos et plans) PHOTO_RE = re.compile(r'(?:src|href)="(/images/condos-locatifs-18/' r'(?:modele-condo-interieur-exterieur/big/[^"]+|' r"condo-a-louer/\d+[^\"]*|img-exterieur[^\"]*))\"", re.I) class Condos18Connector(BaseConnector): source_id = "condos18" request_delay = 0.6 def fetch(self) -> list[Listing]: listings: list[Listing] = [] html = self.get(PAGE_URL).text soup = BeautifulSoup(html, "html.parser") # photos communes de l'immeuble/du modèle photos = [BASE + u for u in dict.fromkeys(PHOTO_RE.findall(html)) if not re.search(r"_tn|tn\.png|map-", u, re.I)][:15] details_common: dict = {} m = re.search(r'href="mailto:([^"?]+)"', html) contact = {"email": m.group(1).strip().lower()} if m else {} m = re.search(r'href="tel:\+?1?[\s.]*(\d{3})[\s.]*(\d{3})[\s.]*(\d{4})"', html) if not m: m = re.search(r"\b(418)[\s.]+(\d{3})[\s.]+(\d{4})\b", soup.get_text(" ", strip=True)) if m: contact["phone"] = f"{m.group(1)}-{m.group(2)}-{m.group(3)}" if contact: details_common["contact"] = contact # onglets prix par type : bouton « 3 1/2 » (id="#s312") + « À partir # de 1200$ par mois », puis panneau