SPB Git

spb/lou-ka Public

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

HTML 99.7%
4.7 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/sig.py : connecteur Société d'investissement Gilbert (immo-sig.ca)5#   Résidences étudiantes en colocation à Québec (Université Laval, Cégep6#   Sainte-Foy) : Le Sommet, Le Pavillon, La FAC, L'UNI. La page7#   /residences-etudiantes/ (Elementor, rendue serveur) affiche pour chaque8#   résidence le prix « à partir de » par chambre, l'adresse et une photo.9#   Granularité = résidence (aucune unité détaillée publiée). Les fiches10#   détaillées vivent sur residences-etudiantes.com, protégé par une11#   vérification anti-bot reCAPTCHA -> immo-sig.ca est la source primaire ;12#   le lien « DÉCOUVRIR » sert d'URL d'annonce pour l'utilisateur.13# -----------------------------------------------------------------------------14from __future__ import annotations1516import re1718from bs4 import BeautifulSoup1920from ..schema import Listing, strip_accents21from .base import BaseConnector2223BASE = "https://immo-sig.ca"24INDEX_URL = f"{BASE}/residences-etudiantes/"2526# variantes responsives WordPress (-300x200.jpg) à écarter27_VARIANT_IMG = re.compile(r"-\d{2,4}x\d{2,4}\.(?:jpe?g|png|webp)$", re.I)282930def _slug(name: str) -> str:31    s = strip_accents((name or "").strip().lower())32    return re.sub(r"[^a-z0-9]+", "-", s).strip("-")333435class SigConnector(BaseConnector):36    source_id = "sig"37    request_delay = 0.63839    def fetch(self) -> list[Listing]:40        html = self.get(INDEX_URL).text41        soup = BeautifulSoup(html, "html.parser")4243        listings: list[Listing] = []44        seen: set[str] = set()45        # une colonne Elementor par résidence : photo + <h3> nom + texte46        # « COLOCATION ÉTUDIANTE / Unités de N chambres à partir de X$/mois /47        # adresse » + bouton DÉCOUVRIR48        for col in soup.select(".elementor-column"):49            h3 = col.select_one("h3.elementor-heading-title")50            text_w = col.select_one(".elementor-widget-text-editor")51            if not h3 or not text_w:52                continue53            name = re.sub(r"\s+", " ", h3.get_text(" ", strip=True))54            sid = _slug(name)55            if not name or sid in seen:56                continue5758            paras = [re.sub(r"[ \t]+", " ", p.get_text("\n", strip=True))59                     for p in text_w.find_all("p")]60            tagline = paras[0] if paras else ""61            body = paras[1] if len(paras) > 1 else ""62            if "à partir de" not in body.lower():63                continue                      # pas un bloc résidence64            seen.add(sid)6566            # « Unités de 3 chambres à partir de 650$/mois\n2333 Chapdelaine »67            lines = [l.strip() for l in body.split("\n") if l.strip()]68            price_line = lines[0] if lines else ""69            address = lines[1] if len(lines) > 1 else ""7071            img = col.select_one("img[src]")72            images = []73            if img:74                src = (img.get("src") or "").strip()75                if src.startswith("http") and not _VARIANT_IMG.search(src):76                    images.append(src)7778            link = col.select_one("a.elementor-button[href]")79            url = (link["href"] if link and80                   str(link["href"]).startswith("http") else INDEX_URL)8182            # « meublées » n'est indiqué que pour L'UNI83            furnished = True if "meublé" in price_line.lower() else None84            m = re.search(r"unités?\s+de\s+([\d\s aà-]+chambres?)",85                          price_line, re.I)86            desc = " — ".join(x for x in [87                tagline.capitalize(), price_line,88                "Fiche détaillée : " + url if url != INDEX_URL else ""] if x)8990            details = {"residence": name}91            listings.append(Listing(92                source=self.source_id,93                external_id=sid,94                url=url,95                title=f"{name} — Colocation étudiante",96                address=address,97                sector="",                    # non précisé à la source98                city="Québec",99                unit_type="Chambre",          # colocation : location à la chambre100                price_label=price_line,       # « ... à partir de 545$/mois »101                availability="",              # aucune disponibilité affichée102                furnished=furnished,103                description=desc[:600],104                amenities=[t for t in [105                    "Colocation étudiante",106                    f"Unités de {m.group(1).strip()}" if m else ""] if t],107                details=details,108                images=images,109            ))110        return listings111