SPB Git forge

spb/lou-ka

Public

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

232commits 1branches 0releases
172.9 MBsize
maindefault branch
2 days agolast push
HTML 98.9% Python 0.6%
3.8 KB · 97 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/solaris.py : connecteur Solaris Boisbriand (solarisboisbriand.com)5#   Condos locatifs neufs au 1030, rue des Francs-Bourgeois, Boisbriand6#   (Faubourg Boisbriand). Site Wix mais rendu SERVEUR : la page /plans contient7#   dans son HTML statique les cartes « n ½ (x chambres) … à partir de NNNN$/mois »8#   (texte riche wixui, entités &frac12; / &agrave;). Aucune liste d'unités —9#   granularité : TYPOLOGIE (3 fiches : 3½ / 4½ / 5½, prix « à partir de »).10# -----------------------------------------------------------------------------11from __future__ import annotations1213import html as _html14import re1516from ..schema import Listing17from .base import BaseConnector1819BASE = "https://www.solarisboisbriand.com"20PLANS_URL = f"{BASE}/plans"2122ADDRESS = "1030, rue des Francs-Bourgeois, Boisbriand"23CITY = "Boisbriand"2425# « 3 ½ (1 chambre) … à partir de 1800$/mois » (texte décodé, balises retirées)26CARD_RE = re.compile(27    r"(\d)\s*½\s*\((\d)\s*chambres?\)"          # typologie (n ½ (x chambres))28    r".{0,400}?à\s*partir\s*de\s*"               # libellé29    r"(\d[\d\s,]*)\s*\$\s*/\s*mois", re.S)30IMG_RE = re.compile(31    r"https://static\.wixstatic\.com/media/[^\"'\s\\)]+"32    r"\.(?:jpe?g|png|webp)", re.I)33SKIP_IMG_RE = re.compile(r"logo|favicon|icon|blur|~mv2\.png", re.I)343536class SolarisConnector(BaseConnector):37    source_id = "solaris"38    request_delay = 0.639    max_images = 104041    def _images(self) -> list[str]:42        """Photos du projet (page d'accueil Wix, media wixstatic)."""43        try:44            html = self.get(f"{BASE}/").text45        except Exception:46            return []47        out: list[str] = []48        for u in dict.fromkeys(IMG_RE.findall(html)):49            u = u.split("/v1/")[0]              # version pleine taille50            if not SKIP_IMG_RE.search(u) and u not in out:51                out.append(u)52        return out[: self.max_images]5354    def fetch(self) -> list[Listing]:55        raw = self.get(PLANS_URL).text56        # entités (&frac12;, &agrave;…) puis balises -> texte plat57        text = _html.unescape(raw)58        text = re.sub(r"<script.*?</script>", " ", text, flags=re.S)59        text = re.sub(r"<style.*?</style>", " ", text, flags=re.S)60        text = re.sub(r"<[^>]+>", " ", text)61        text = re.sub(r"\s+", " ", text)6263        images = self._images()64        listings: list[Listing] = []65        seen: set[str] = set()66        for n, beds, amount in CARD_RE.findall(text):67            unit_type = f"{n}½"68            if unit_type in seen:69                continue70            seen.add(unit_type)71            amount = amount.strip().replace(" ", "").replace(",", "")72            try:73                price = float(amount)74            except ValueError:75                continue76            if not 300 <= price <= 20000:77                continue78            listings.append(Listing(79                source=self.source_id,80                external_id=f"solaris-{n}.5",81                url=PLANS_URL,82                title=f"Solaris Boisbriand — {unit_type}"83                      f" ({beds} chambre{'s' if beds != '1' else ''})",84                address=ADDRESS,85                city=CITY,86                unit_type=unit_type,87                bedrooms=float(beds),88                price=price,89                price_label=f"à partir de {int(price)}$/mois",90                availability="Disponible",91                description=f"Condo locatif neuf {unit_type}"92                            f" ({beds} chambre(s)) au Faubourg Boisbriand"93                            " — prix « à partir de », selon l'unité.",94                images=list(images),95            ))96        return listings97