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%
4.1 KB · 107 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/vestric.py : connecteur Vestric — Condos locatifs (vestric.ca)5#   Projet unique à Candiac (Montérégie). WordPress (thème Vortex « vy_ »)6#   rendu serveur : la page /les-unites liste les unités disponibles7#   (li.vy_units_listing_item : no d'unité, modèle, étage, type, superficie,8#   disponibilité + lien /units/unite-<no>). Granularité : unité. Prix non9#   publiés (formulaire de contact avec fourchettes de budget seulement).10# -----------------------------------------------------------------------------11from __future__ import annotations1213import re1415from bs4 import BeautifulSoup1617from .base import BaseConnector18from ..schema import Listing, normalize_unit_type1920BASE = "https://vestric.ca"21LIST_URL = f"{BASE}/les-unites"2223CITY = "Candiac"2425IMG_RE = re.compile(r'https://vestric\.ca/app/uploads/'26                    r'[^"\s\\)]+\.(?:jpe?g|png|webp)', re.I)27PDF_RE = re.compile(r'https://vestric\.ca/app/uploads/[^"\s\\)]+\.pdf', re.I)28SQFT_RE = re.compile(r"(\d{3,4})\s*pi", re.I)293031class VestricConnector(BaseConnector):32    source_id = "vestric"33    request_delay = 0.63435    def fetch(self) -> list[Listing]:36        listings: list[Listing] = []37        try:38            html = self.get(LIST_URL).text39        except Exception:40            return listings41        soup = BeautifulSoup(html, "html.parser")4243        for item in soup.select("li.vy_units_listing_item"):44            try:45                def cell(cls: str) -> str:46                    el = item.select_one(f"div.vy_units_listing_{cls} span")47                    return el.get_text(" ", strip=True) if el else ""4849                unit_no = cell("unit")50                if not unit_no:51                    continue52                model = cell("model").replace("Modèle", "").strip()53                floor = cell("floor")54                unit_type = normalize_unit_type(cell("type"))55                sqft = None56                m = SQFT_RE.search(cell("square"))57                if m:58                    sqft = float(m.group(1))59                availability = cell("availability")6061                a = item.select_one('a[href*="/units/"]')62                url = (a.get("href", "").split("#")[0] if a63                       else f"{BASE}/units/unite-{unit_no}")6465                details: dict = {}66                if model:67                    details["model"] = model68                if floor:69                    details["floor"] = floor7071                # page détail (cache BD) : photos de l'unité72                key = f"{unit_type}|{sqft}|{availability}|{model}"73                d = self.detail(f"unite-{unit_no}", key,74                                lambda url=url: self._fetch_detail(url))7576                listings.append(Listing(77                    source=self.source_id,78                    external_id=f"unite-{unit_no}",79                    url=url,80                    title=f"Unité {unit_no} ({unit_type}) — Vestric",81                    address="200, avenue des Chênes, Candiac",82                    city=CITY,83                    unit_type=unit_type,84                    availability=availability,85                    area_sqft=sqft,86                    details={**details, **({"plan_pdf": d["plan_pdf"]} if d.get("plan_pdf") else {})},87                    images=d.get("images") or [],88                ))89            except Exception:90                continue91        return listings9293    def _fetch_detail(self, url: str) -> dict:94        out: dict = {}95        try:96            html = self.get(url).text97        except Exception:98            return out99        out["images"] = [u for u in dict.fromkeys(IMG_RE.findall(html))100                         if not re.search(r"logo|icon|favicon|og-image|"101                                          r"decoration|bloc-vestric|"102                                          r"-\d{2,3}x\d{2,3}\.", u, re.I)][:15]103        pdfs = PDF_RE.findall(html)104        if pdfs:105            out["plan_pdf"] = pdfs[0]106        return out107