# ----------------------------------------------------------------------------- # Lou-Ka — Agrégateur de logements à louer (province de Québec) # Auteur : Simon-Pierre Boucher — contact@spboucher.ai # connectors/immeubles_bois.py : connecteur Les Immeubles Bois (immeublesbois.ca # — Rimouski, Bas-Saint-Laurent ; 320+ logements, 26 immeubles, 1½–5½). # WordPress + Elementor rendu serveur : la page /residentiel/ liste un # loop-item par immeuble (adresse en h6 + photo aérienne + lien fiche). # Les fiches d'immeuble ne publient RIEN de plus (titre + « Demander une # visite » + vignettes d'AUTRES immeubles) : elles ne sont pas visitées — # 1 seule requête par sync. Granularité IMMEUBLE assumée (précédent girs / # immeubles_guillot) : aucun prix, aucune unité, aucune disponibilité # publiés -> price=None, availability vide, rien d'inventé. Tout le parc # affiché est à Rimouski (coordonnées du siège : 40 rue Saint-Germain Est). # external_id stable : slug de la fiche (/157-159-rue-hudon/). # robots.txt : Allow / pour les agents génériques (blocage ciblé de bots # d'entraînement IA ; Content-Signal search=yes, use=reference — usage # agrégateur/référence avec lien retour cohérent, précédent place_florimay). # ----------------------------------------------------------------------------- from __future__ import annotations import re from bs4 import BeautifulSoup from ..schema import Listing from .base import BaseConnector BASE = "https://immeublesbois.ca" LIST_URL = f"{BASE}/residentiel/" class ImmeublesBoisConnector(BaseConnector): source_id = "immeubles_bois" request_delay = 0.6 def fetch(self) -> list[Listing]: soup = BeautifulSoup(self.get(LIST_URL).text, "html.parser") listings: dict[str, Listing] = {} for item in soup.select( '[data-elementor-type="loop-item"].category-residentiel'): try: self._parse_item(item, listings) except Exception: continue return list(listings.values()) # -- loop-item Elementor (un immeuble) ---------------------------------------- def _parse_item(self, item, listings: dict[str, Listing]) -> None: link = item.select_one("h6 a[href]") if link is None: return url = link["href"].strip() title = re.sub(r"\s+", " ", link.get_text(" ", strip=True)) m = re.search(r"immeublesbois\.ca/([^/?#]+)", url) if not m or not title: return ext_id = m.group(1) if ext_id in listings: return images: list[str] = [] img = item.select_one("img[src]") if img and img["src"].startswith("http") \ and "logo" not in img["src"].lower(): images.append(img["src"]) listings[ext_id] = Listing( source=self.source_id, external_id=ext_id, url=url, title=title, address=f"{title}, Rimouski", city="Rimouski", # tout le parc affiché est à Rimouski unit_type="", # typologies non publiées par immeuble price=None, # aucun loyer publié sur le site price_label="", availability="", # disponibilités hors ligne (téléphone) images=images, )