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 · 99 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/vita_stl.py : connecteur Appartements VITA (appartementsvita.ca)5#   2505, boulevard Marcel-Laurin, Saint-Laurent (Montréal) — deux phases,6#   1½ à 5½, à deux pas du REM Bois-Franc. WordPress/Elementor : le sélecteur7#   d'unités (plugin wp-floor-plans) ne retourne AUCUNE unité individuelle8#   (vérifié via admin-ajax sur les deux phases et tous les étages) — la page9#   d'accueil affiche plutôt une ligne par type : « 3 ½ à partir de 1650$ »10#   ou « 1 ½ : liste d'attente ». Une annonce par type avec prix réel (les11#   types en liste d'attente sont exclus), id stable vita-<type>.12# -----------------------------------------------------------------------------13from __future__ import annotations1415import re1617from bs4 import BeautifulSoup1819from ..schema import Listing20from .base import BaseConnector2122BASE = "https://appartementsvita.ca"23ADDRESS = "2505, boulevard Marcel-Laurin, Saint-Laurent, Montréal, QC H4R 0R7"2425_TYPE_RE = re.compile(r"^([1-6])\s*½\s*(.*)$")26_PRICE_RE = re.compile(r"à partir de\s*([\d\s,]+)\s*\$", re.I)27_IMG_RE = re.compile(28    r'https://appartementsvita\.ca/wp-content/uploads/[^"\'\s\\)]+?'29    r'\.(?:jpg|jpeg|png|webp)', re.I)30_SKIP_IMG = re.compile(r"logo|icon|favicon|-\d{2,4}x\d{2,4}\.", re.I)313233class VitaStlConnector(BaseConnector):34    source_id = "vita_stl"35    request_delay = 0.636    max_images = 203738    def fetch(self) -> list[Listing]:39        html = self.get(f"{BASE}/").text40        soup = BeautifulSoup(html, "html.parser")4142        # galerie du projet (accueil, sans logos/miniatures)43        images: list[str] = []44        for u in _IMG_RE.findall(html):45            if _SKIP_IMG.search(u):46                continue47            if u not in images:48                images.append(u)49        images = images[: self.max_images]5051        # description : méta de la page52        desc = ""53        meta = soup.find("meta", attrs={"name": "description"})54        if meta and meta.get("content"):55            desc = meta["content"].strip()[:600]56        if not desc:57            og = soup.find("meta", property="og:description")58            if og and og.get("content"):59                desc = og["content"].strip()[:600]6061        # une ligne « n ½ … » par type dans les listes à icônes Elementor62        listings: list[Listing] = []63        seen: set[str] = set()64        for span in soup.select(".elementor-icon-list-text"):65            t = span.get_text(" ", strip=True)66            m = _TYPE_RE.match(t)67            if not m:68                continue69            n, rest = m.group(1), m.group(2)70            unit_type = f"{n}½"71            if unit_type in seen:72                continue73            pm = _PRICE_RE.search(rest)74            if not pm:75                continue             # « liste d'attente » : exclu76            digits = re.sub(r"[^\d]", "", pm.group(1))77            if not digits:78                continue79            price = float(digits)80            if not (100 <= price <= 20000):81                continue82            seen.add(unit_type)83            listings.append(Listing(84                source=self.source_id,85                external_id=f"vita-{n}5",86                url=f"{BASE}/",87                title=f"Appartements VITA — {unit_type}",88                address=ADDRESS,89                sector="Saint-Laurent",90                city="Montréal",91                unit_type=unit_type,92                price=price,93                price_label=f"À partir de {int(price)} $/mois",94                availability=t,95                description=desc,96                images=list(images),97            ))98        return listings99