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/fitz.py : connecteur Le Fitz (lefitz.ca) — condos locatifs,5# 3 tours au 77, route du Président-Kennedy à Lévis. WordPress + plugin6# maison « imedia-building-plans » : admin-ajax.php?action=get_unit_list&7# building_id=<term> renvoie le tableau complet des unités (no, type,8# étage, chambres, sdb, superficie, PRIX, disponibilité) ; les <tr>9# class="unit disabled" sont louées, class="unit" disponibles.10# Granularité : unité, avec prix.11# -----------------------------------------------------------------------------12from __future__ import annotations1314import re1516from bs4 import BeautifulSoup1718from ..schema import Listing, normalize_unit_type, parse_price19from .base import BaseConnector2021BASE = "https://lefitz.ca"22AJAX = f"{BASE}/wp-admin/admin-ajax.php"2324# tours (term_id -> nom) : data-term-id des boutons de /plan-unites/25BUILDINGS = [("44", "Tour 1"), ("14", "Tour 2"), ("10", "Tour 3")]2627ADDRESS = "77, route du Président-Kennedy, Lévis"28AMENITIES = ["Chauffé et éclairé", "Électroménagers inclus",29 "Internet haute vitesse inclus", "Gym et salle de yoga",30 "Piscine intérieure", "Terrasses et jardin communautaire"]313233class FitzConnector(BaseConnector):34 source_id = "fitz"35 request_delay = 0.63637 def fetch(self) -> list[Listing]:38 listings: list[Listing] = []39 seen: set[str] = set()40 for term_id, tower in BUILDINGS:41 try:42 html = self.get(AJAX, params={43 "action": "get_unit_list",44 "building_id": term_id}).text45 except Exception:46 continue47 soup = BeautifulSoup(html, "html.parser")48 for row in soup.select("tr.unit"):49 try:50 classes = row.get("class") or []51 if "disabled" in classes:52 continue # unité louée53 unit_no = (row.get("data-unit") or "").strip()54 if not unit_no or unit_no in seen:55 continue56 seen.add(unit_no)5758 def cell(name: str) -> str:59 el = row.select_one(f"td.unit__{name}")60 return el.get_text(" ", strip=True) if el else ""6162 unit_type = normalize_unit_type(cell("type"))63 price_label = cell("price")64 area = None65 m = re.search(r"([\d\s]+)\s*p\.?\s*c\.?", cell("area"))66 if m:67 area = float(m.group(1).replace(" ", "")68 .replace(" ", "").replace(" ", ""))69 bedrooms = bathrooms = None70 if cell("rooms").isdigit():71 bedrooms = float(cell("rooms"))72 if cell("bathrooms").replace(".", "").isdigit():73 bathrooms = float(cell("bathrooms"))7475 plan_img = row.get("data-image-desktop") or ""76 details: dict = {"floor": cell("floor") or77 row.get("data-floor", "")}78 if row.get("data-file"):79 details["floor_plan"] = row["data-file"]80 if row.get("data-balcony-area"):81 details["balcony_area"] = row["data-balcony-area"]8283 listings.append(Listing(84 source=self.source_id,85 external_id=unit_no,86 url=row.get("data-permalink")87 or f"{BASE}/plan-unites/",88 title=f"Unité {unit_no} — Le Fitz ({tower})",89 address=ADDRESS,90 city="Lévis",91 unit_type=unit_type,92 bedrooms=bedrooms,93 bathrooms=bathrooms,94 price=parse_price(price_label),95 price_label=price_label,96 availability=cell("availability"),97 area_sqft=area,98 description=f"Unité {unit_no} ({unit_type}), "99 f"étage {details['floor']}, {tower} du "100 "Fitz — condos locatifs avec vue à "101 "Lévis. Chauffage, éclairage, électro-"102 "ménagers et Internet inclus.",103 amenities=list(AMENITIES),104 details=details,105 images=[plan_img] if plan_img.startswith("http")106 else [],107 ))108 except Exception:109 continue110 return listings111