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/jardins_anna.py : connecteur Les Jardins d'Anna (lesjardinsdanna.com)5# Projet domiciliaire de Nicolet (Centre-du-Québec), à 15 minutes de6# Trois-Rivières — logements 4½ et 5½ de style condo. WordPress rendu7# serveur : la page /location/ présente les typologies en sections <h3>8# (« Logements 4 ½ », « … avec verrière », « … avec verrière et garage »,9# « Logements 5 ½ ») suivies de « À partir de 1 250$ par mois, nos …10# incluent : » + liste d'inclusions. Granularité = typologie (le site ne11# publie pas d'inventaire par unité). external_id = typologie en slug.12# -----------------------------------------------------------------------------13from __future__ import annotations1415import re1617from bs4 import BeautifulSoup1819from ..schema import Listing, normalize_unit_type, strip_accents20from .base import BaseConnector2122BASE = "https://lesjardinsdanna.com"23PAGE_URL = f"{BASE}/location/"2425PRICE_RE = re.compile(r"À\s+partir\s+de\s+([\d\s ]{3,7})\$\s*par\s*mois", re.I)26UNIT_RE = re.compile(r"(\d)\s*(?:½|1/2)")27IMG_RE = re.compile(28 r"https?://lesjardinsdanna\.com/wp-content/uploads/[^\"'\s)]+"29 r"\.(?:jpg|jpeg|png|webp)", re.I)303132def _slug(text: str) -> str:33 s = strip_accents(text.lower())34 return re.sub(r"[^a-z0-9]+", "-", s).strip("-")353637def _num(txt: str) -> float | None:38 n = re.sub(r"[\s ]", "", txt or "")39 try:40 return float(n)41 except ValueError:42 return None434445class JardinsAnnaConnector(BaseConnector):46 source_id = "jardins_anna"47 request_delay = 0.84849 def fetch(self) -> list[Listing]:50 html = self.get(PAGE_URL).text51 soup = BeautifulSoup(html, "html.parser")5253 images = [u for u in dict.fromkeys(IMG_RE.findall(html))54 if not re.search(r"logo|icon|-\d+x\d+\.", u, re.I)][:15]5556 listings: list[Listing] = []57 for h3 in soup.find_all("h3"):58 label = re.sub(r"\s+", " ", h3.get_text(" ", strip=True))59 if not re.match(r"Logements?\s", label, re.I):60 continue61 # texte de la section : frères suivants jusqu'au prochain h362 price = None63 price_label = ""64 amenities: list[str] = []65 node = h366 while True:67 node = node.find_next_sibling()68 if node is None or node.name == "h3":69 break70 text = re.sub(r"\s+", " ", node.get_text(" ", strip=True))71 if not price:72 pm = PRICE_RE.search(text)73 if pm:74 price = _num(pm.group(1))75 price_label = pm.group(0)76 for li in node.find_all("li"):77 item = re.sub(r"\s+", " ", li.get_text(" ", strip=True))78 if item and item not in amenities and len(item) < 80:79 amenities.append(item)80 if price is None:81 continue82 unit_type = ""83 um = UNIT_RE.search(label)84 if um:85 unit_type = normalize_unit_type(f"{um.group(1)} ½")86 listings.append(Listing(87 source=self.source_id,88 external_id=_slug(label),89 url=PAGE_URL,90 title=f"{label} — Les Jardins d'Anna",91 address="Les Jardins d'Anna, Nicolet",92 sector="",93 city="Nicolet",94 unit_type=unit_type,95 price=price,96 price_label=price_label,97 description=f"{label} de style condo aux Jardins d'Anna, "98 "Nicolet (à 15 minutes de Trois-Rivières).",99 amenities=amenities,100 images=list(images),101 ))102 return listings103