# ----------------------------------------------------------------------------- # Lou-Ka — Agrégateur de logements à louer (province de Québec) # Auteur : Simon-Pierre Boucher — contact@spboucher.ai # connectors/vestric.py : connecteur Vestric — Condos locatifs (vestric.ca) # Projet unique à Candiac (Montérégie). WordPress (thème Vortex « vy_ ») # rendu serveur : la page /les-unites liste les unités disponibles # (li.vy_units_listing_item : no d'unité, modèle, étage, type, superficie, # disponibilité + lien /units/unite-). Granularité : unité. Prix non # publiés (formulaire de contact avec fourchettes de budget seulement). # ----------------------------------------------------------------------------- from __future__ import annotations import re from bs4 import BeautifulSoup from .base import BaseConnector from ..schema import Listing, normalize_unit_type BASE = "https://vestric.ca" LIST_URL = f"{BASE}/les-unites" CITY = "Candiac" IMG_RE = re.compile(r'https://vestric\.ca/app/uploads/' r'[^"\s\\)]+\.(?:jpe?g|png|webp)', re.I) PDF_RE = re.compile(r'https://vestric\.ca/app/uploads/[^"\s\\)]+\.pdf', re.I) SQFT_RE = re.compile(r"(\d{3,4})\s*pi", re.I) class VestricConnector(BaseConnector): source_id = "vestric" 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("li.vy_units_listing_item"): try: def cell(cls: str) -> str: el = item.select_one(f"div.vy_units_listing_{cls} span") return el.get_text(" ", strip=True) if el else "" unit_no = cell("unit") if not unit_no: continue model = cell("model").replace("Modèle", "").strip() floor = cell("floor") unit_type = normalize_unit_type(cell("type")) sqft = None m = SQFT_RE.search(cell("square")) if m: sqft = float(m.group(1)) availability = cell("availability") a = item.select_one('a[href*="/units/"]') url = (a.get("href", "").split("#")[0] if a else f"{BASE}/units/unite-{unit_no}") details: dict = {} if model: details["model"] = model if floor: details["floor"] = floor # page détail (cache BD) : photos de l'unité key = f"{unit_type}|{sqft}|{availability}|{model}" d = self.detail(f"unite-{unit_no}", key, lambda url=url: self._fetch_detail(url)) listings.append(Listing( source=self.source_id, external_id=f"unite-{unit_no}", url=url, title=f"Unité {unit_no} ({unit_type}) — Vestric", address="200, avenue des Chênes, Candiac", city=CITY, unit_type=unit_type, availability=availability, area_sqft=sqft, details={**details, **({"plan_pdf": d["plan_pdf"]} if d.get("plan_pdf") else {})}, images=d.get("images") or [], )) except Exception: continue return listings def _fetch_detail(self, url: str) -> dict: out: dict = {} try: html = self.get(url).text except Exception: return out out["images"] = [u for u in dict.fromkeys(IMG_RE.findall(html)) if not re.search(r"logo|icon|favicon|og-image|" r"decoration|bloc-vestric|" r"-\d{2,3}x\d{2,3}\.", u, re.I)][:15] pdfs = PDF_RE.findall(html) if pdfs: out["plan_pdf"] = pdfs[0] return out