# ----------------------------------------------------------------------------- # Lou-Ka — Agrégateur de logements à louer (province de Québec) # Auteur : Simon-Pierre Boucher — contact@spboucher.ai # connectors/jardins_anna.py : connecteur Les Jardins d'Anna (lesjardinsdanna.com) # Projet domiciliaire de Nicolet (Centre-du-Québec), à 15 minutes de # Trois-Rivières — logements 4½ et 5½ de style condo. WordPress rendu # serveur : la page /location/ présente les typologies en sections

# (« Logements 4 ½ », « … avec verrière », « … avec verrière et garage », # « Logements 5 ½ ») suivies de « À partir de 1 250$ par mois, nos … # incluent : » + liste d'inclusions. Granularité = typologie (le site ne # publie pas d'inventaire par unité). external_id = typologie en slug. # ----------------------------------------------------------------------------- from __future__ import annotations import re from bs4 import BeautifulSoup from ..schema import Listing, normalize_unit_type, strip_accents from .base import BaseConnector BASE = "https://lesjardinsdanna.com" PAGE_URL = f"{BASE}/location/" PRICE_RE = re.compile(r"À\s+partir\s+de\s+([\d\s ]{3,7})\$\s*par\s*mois", re.I) UNIT_RE = re.compile(r"(\d)\s*(?:½|1/2)") IMG_RE = re.compile( r"https?://lesjardinsdanna\.com/wp-content/uploads/[^\"'\s)]+" r"\.(?:jpg|jpeg|png|webp)", re.I) def _slug(text: str) -> str: s = strip_accents(text.lower()) return re.sub(r"[^a-z0-9]+", "-", s).strip("-") def _num(txt: str) -> float | None: n = re.sub(r"[\s ]", "", txt or "") try: return float(n) except ValueError: return None class JardinsAnnaConnector(BaseConnector): source_id = "jardins_anna" request_delay = 0.8 def fetch(self) -> list[Listing]: html = self.get(PAGE_URL).text soup = BeautifulSoup(html, "html.parser") images = [u for u in dict.fromkeys(IMG_RE.findall(html)) if not re.search(r"logo|icon|-\d+x\d+\.", u, re.I)][:15] listings: list[Listing] = [] for h3 in soup.find_all("h3"): label = re.sub(r"\s+", " ", h3.get_text(" ", strip=True)) if not re.match(r"Logements?\s", label, re.I): continue # texte de la section : frères suivants jusqu'au prochain h3 price = None price_label = "" amenities: list[str] = [] node = h3 while True: node = node.find_next_sibling() if node is None or node.name == "h3": break text = re.sub(r"\s+", " ", node.get_text(" ", strip=True)) if not price: pm = PRICE_RE.search(text) if pm: price = _num(pm.group(1)) price_label = pm.group(0) for li in node.find_all("li"): item = re.sub(r"\s+", " ", li.get_text(" ", strip=True)) if item and item not in amenities and len(item) < 80: amenities.append(item) if price is None: continue unit_type = "" um = UNIT_RE.search(label) if um: unit_type = normalize_unit_type(f"{um.group(1)} ½") listings.append(Listing( source=self.source_id, external_id=_slug(label), url=PAGE_URL, title=f"{label} — Les Jardins d'Anna", address="Les Jardins d'Anna, Nicolet", sector="", city="Nicolet", unit_type=unit_type, price=price, price_label=price_label, description=f"{label} de style condo aux Jardins d'Anna, " "Nicolet (à 15 minutes de Trois-Rivières).", amenities=amenities, images=list(images), )) return listings