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/immeubles_bois.py : connecteur Les Immeubles Bois (immeublesbois.ca5# — Rimouski, Bas-Saint-Laurent ; 320+ logements, 26 immeubles, 1½–5½).6# WordPress + Elementor rendu serveur : la page /residentiel/ liste un7# loop-item par immeuble (adresse en h6 + photo aérienne + lien fiche).8# Les fiches d'immeuble ne publient RIEN de plus (titre + « Demander une9# visite » + vignettes d'AUTRES immeubles) : elles ne sont pas visitées —10# 1 seule requête par sync. Granularité IMMEUBLE assumée (précédent girs /11# immeubles_guillot) : aucun prix, aucune unité, aucune disponibilité12# publiés -> price=None, availability vide, rien d'inventé. Tout le parc13# affiché est à Rimouski (coordonnées du siège : 40 rue Saint-Germain Est).14# external_id stable : slug de la fiche (/157-159-rue-hudon/).15# robots.txt : Allow / pour les agents génériques (blocage ciblé de bots16# d'entraînement IA ; Content-Signal search=yes, use=reference — usage17# agrégateur/référence avec lien retour cohérent, précédent place_florimay).18# -----------------------------------------------------------------------------19from __future__ import annotations2021import re2223from bs4 import BeautifulSoup2425from ..schema import Listing26from .base import BaseConnector2728BASE = "https://immeublesbois.ca"29LIST_URL = f"{BASE}/residentiel/"303132class ImmeublesBoisConnector(BaseConnector):33 source_id = "immeubles_bois"34 request_delay = 0.63536 def fetch(self) -> list[Listing]:37 soup = BeautifulSoup(self.get(LIST_URL).text, "html.parser")3839 listings: dict[str, Listing] = {}40 for item in soup.select(41 '[data-elementor-type="loop-item"].category-residentiel'):42 try:43 self._parse_item(item, listings)44 except Exception:45 continue46 return list(listings.values())4748 # -- loop-item Elementor (un immeuble) ----------------------------------------49 def _parse_item(self, item, listings: dict[str, Listing]) -> None:50 link = item.select_one("h6 a[href]")51 if link is None:52 return53 url = link["href"].strip()54 title = re.sub(r"\s+", " ", link.get_text(" ", strip=True))55 m = re.search(r"immeublesbois\.ca/([^/?#]+)", url)56 if not m or not title:57 return58 ext_id = m.group(1)59 if ext_id in listings:60 return6162 images: list[str] = []63 img = item.select_one("img[src]")64 if img and img["src"].startswith("http") \65 and "logo" not in img["src"].lower():66 images.append(img["src"])6768 listings[ext_id] = Listing(69 source=self.source_id,70 external_id=ext_id,71 url=url,72 title=title,73 address=f"{title}, Rimouski",74 city="Rimouski", # tout le parc affiché est à Rimouski75 unit_type="", # typologies non publiées par immeuble76 price=None, # aucun loyer publié sur le site77 price_label="",78 availability="", # disponibilités hors ligne (téléphone)79 images=images,80 )81