spb/lou-ka Public
Lou·Ka — tous les logements à louer du Québec, un seul endroit.
HTML 99.7%
1# -----------------------------------------------------------------------------2# Lou-Ka — Agrégateur de logements à louer (province de Québec)3# Auteur : Simon-Pierre Boucher — contact@spboucher.ai4# connectors/alias_appartements.py : connecteur Alias Appartements5# (aliasappartements.com — rue des Mouettes, Jonquière, ville de Saguenay ;6# Groupe Boudreault). Projet locatif neuf UNIQUE de 80 unités 4½ livré en7# 4 phases (2023-2025). WordPress + Elementor + JetElements rendu serveur,8# page projet unique SANS fiches d'unités : le site publie le nombre9# d'unités (« 80 unités »), le type (« 4 1/2 »), le loyer plancher10# (« À partir de 1575$ »), l'adresse (h3 « Rue des Mouettes, Jonquière »),11# les caractéristiques (carrousel JetElements) et des photos — disponibilités12# par formulaire/contact. -> 1 annonce « catalogue » à granularité PROJET13# (précédent girs/immeubles_guillot), availability vide, rien d'inventé ;14# price via price_label (« à partir de », plus bas loyer).15# external_id stable : rue-des-mouettes.16# robots.txt Yoast ouvert (Disallow vide), sitemap public.17# -----------------------------------------------------------------------------18from __future__ import annotations1920import re2122from bs4 import BeautifulSoup2324from ..schema import Listing, normalize_unit_type25from .base import BaseConnector2627BASE = "https://www.aliasappartements.com"2829_UNITS_RE = re.compile(r"^\d+\s+unités$", re.I)30_TYPE_RE = re.compile(r"^([1-6])\s*(?:½|1/2)\s*$")31_PRICE_RE = re.compile(r"^à\s+partir\s+de\s+[\d\s]+\$", re.I)32_SKIP_IMG = re.compile(r"\.svg$|logo|icon|Group-\d+", re.I)333435class AliasAppartementsConnector(BaseConnector):36 source_id = "alias_appartements"37 request_delay = 0.638 max_images = 103940 def fetch(self) -> list[Listing]:41 soup = BeautifulSoup(self.get(BASE + "/").text, "html.parser")4243 units_txt, unit_type, price_label, address = "", "", "", ""44 for h in soup.select(".elementor-heading-title"):45 t = re.sub(r"\s+", " ", h.get_text(" ", strip=True))46 if not t:47 continue48 if _UNITS_RE.match(t):49 units_txt = t50 elif _TYPE_RE.match(t):51 unit_type = normalize_unit_type(t)52 elif _PRICE_RE.match(t) and not price_label:53 price_label = t54 elif h.name == "h3" and re.search(r"(?i)mouettes", t):55 address = t5657 if not address and not units_txt:58 return [] # page méconnaissable : ne rien émettre5960 # caractéristiques : carrousel JetElements61 amenities: list[str] = []62 if units_txt:63 amenities.append(units_txt)64 for h5 in soup.select("h5.jet-carousel__item-title"):65 t = re.sub(r"\s+", " ", h5.get_text(" ", strip=True))66 if t and t not in amenities:67 amenities.append(t)6869 # paragraphes descriptifs (hors pied de page)70 paras: list[str] = []71 for p in soup.select(".elementor-widget-text-editor p"):72 t = re.sub(r"\s+", " ", p.get_text(" ", strip=True))73 if len(t) >= 100 and "©" not in t and len(paras) < 5:74 paras.append(t)75 description = "\n".join(paras)[:1600]7677 images: list[str] = []78 for img in soup.select("img[src*='/wp-content/uploads/']"):79 u = img["src"]80 if (u.startswith("http") and not _SKIP_IMG.search(u)81 and u not in images):82 images.append(u)83 if len(images) >= self.max_images:84 break8586 return [Listing(87 source=self.source_id,88 external_id="rue-des-mouettes",89 url=BASE + "/",90 title=f"Alias Appartements — {address or 'Rue des Mouettes, Jonquière'}",91 address=address or "Rue des Mouettes, Jonquière",92 sector="Jonquière",93 city="Saguenay",94 unit_type=unit_type,95 price=None, # déduit de price_label (« à partir de »)96 price_label=price_label,97 availability="", # disponibilités par formulaire/contact98 description=description,99 amenities=amenities[:30],100 images=images,101 )]102