# ----------------------------------------------------------------------------- # Lou-Ka — Agrégateur de logements à louer (province de Québec) # Auteur : Simon-Pierre Boucher — contact@spboucher.ai # connectors/place_barrage.py : connecteur Place du Barrage (placedubarrage.com) # Immeuble locatif au 2, rue Vertu à Windsor (Estrie), géré par Sphère # immobilier. Site Wix rendu serveur : la page /appartements liste 24 cartes # (répéteur Wix « wixui-repeater__item ») — « Appartement 101-A », courte # description, prix (« 1475$ ») ou « LOUÉ », lien vers la fiche # /appartements/appartement--. ⚠️ Le du gabarit Wix # mentionne un autre projet (Le Connaught) : ignorer. On dérive l'URL de la # fiche du numéro affiché sur la carte (les hrefs peuvent être désalignés). # Granularité : unité ; prix affichés, typologies non publiées. # ----------------------------------------------------------------------------- from __future__ import annotations import re from bs4 import BeautifulSoup from .base import BaseConnector from ..schema import Listing BASE = "https://www.placedubarrage.com" LIST_URL = f"{BASE}/appartements" ADDRESS = "2, rue Vertu, Windsor" CITY = "Windsor" UNIT_RE = re.compile(r"^Appartement\s+(\d{3})-([A-F])$", re.I) PRICE_RE = re.compile(r"^(\d{3,4})\s*\$$") IMG_RE = re.compile(r'https://static\.wixstatic\.com/media/' r'[^"\s\\)]+\.(?:jpe?g|png|webp)[^"\s\\)]*', re.I) FLOOR_BY_DIGIT = {"1": "Étage 1", "2": "Étage 2", "3": "Étage 3"} class PlaceBarrageConnector(BaseConnector): source_id = "place_barrage" request_delay = 0.6 def fetch(self) -> list[Listing]: listings: list[Listing] = [] try: html = self.get(LIST_URL).text except Exception: return listings soup = BeautifulSoup(html, "html.parser") for item in soup.select("div.wixui-repeater__item"): try: text_lines = [t.strip() for t in item.get_text("\n", strip=True).split("\n")] unit_no, letter = "", "" for t in text_lines: m = UNIT_RE.match(t) if m: unit_no, letter = m.group(1), m.group(2).upper() break if not unit_no: continue if any(re.search(r"^lou[ée]$", t, re.I) for t in text_lines): continue # déjà loué price = None for t in text_lines: mp = PRICE_RE.match(t) if mp: price = float(mp.group(1)) break if price is None: continue # ni prix ni LOUÉ : statut inconnu description = next( (t for t in text_lines if t.lower().startswith("appartement situé")), "") images = [u.split("/v1/")[0] for u in dict.fromkeys(IMG_RE.findall(str(item)))][:15] # ⚠️ hrefs Wix désalignés (la carte 302-E pointe vers la fiche # 302-a…) et seules les fiches -a/-b/-f existent : on ne garde # la fiche que si sa lettre correspond à celle de la carte, # sinon l'URL de l'annonce est la page liste. url = LIST_URL a = item.select_one('a[href*="/appartements/appartement-"]') if a and a.get("href", "").rstrip("/").endswith( f"-{unit_no}-{letter.lower()}"): url = a["href"].rstrip("/") ext_id = f"{unit_no}-{letter}".lower() if any(l.external_id == ext_id for l in listings): continue listings.append(Listing( source=self.source_id, external_id=ext_id, url=url, title=f"Appartement {unit_no}-{letter} — " f"Place du Barrage", address=ADDRESS, city=CITY, price=price, availability="Disponible", description=description, details={"floor": FLOOR_BY_DIGIT.get(unit_no[0], "")}, images=images, )) except Exception: continue return listings