SPB Git forge

spb/lou-ka

Public

Lou·Ka — tous les logements à louer du Québec, un seul endroit.

232commits 1branches 0releases
172.9 MBsize
maindefault branch
2 days agolast push
HTML 98.9% Python 0.6%
4.9 KB · 111 lines python
Raw Blame History
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