SPB Git

spb/immo-ka Public

Immo-Ka — agrégateur des propriétés à vendre au Québec (73 connecteurs, ~40 000 annonces, React+FastAPI)

Python 66.4% TypeScript 19.9% CSS 13.2% HTML 0.5%
3.6 KB · 91 lines python
Raw Blame History
1# -----------------------------------------------------------------------------2# Immo-Ka — Agrégateur de maisons à vendre (province de Québec)3# Auteur : Simon-Pierre Boucher — contact@spboucher.ai4# connectors/via_capitale_agences.py : Via Capitale par SOUS-AGENCES (robuste)5#6#   Le portail central viacapitalevendu.com est derrière Cloudflare (scrape7#   fragile, 1 page). Les sous-agences Via Capitale (Élite, Sélect, du Mont-Royal,8#   Accès…) tournent chacune sur la plateforme source.immo (plugin WordPress),9#   dont l'API publique (api-v1.source.immo) est stable et paginée — même10#   mécanique que Vendirect (voir sourceimmo.py). On génère une classe par11#   sous-agence depuis data/via_capitale_agencies.json ; le n° Centris12#   (ref_number) sert de clé de déduplication (voir web.py, sources « %_ag_% »).13#14#   La config publique de chaque site est à15#   /wp-content/uploads/_sourceimmo/_configs.json (account_id/api_key/app_id +16#   default_view). Le registre est régénérable par scripts/refresh_via_capitale.17# -----------------------------------------------------------------------------18from __future__ import annotations1920import json21import re22from pathlib import Path2324from .sourceimmo import SourceImmoConnector25from ..schema import PropertyListing2627REGISTRY = Path(__file__).resolve().parent.parent.parent / "data" / "via_capitale_agencies.json"282930class ViaCapitaleAgenceConnector(SourceImmoConnector):31    """Sous-agence Via Capitale sur source.immo. Les sous-classes fournissent32    account_id / api_key / app_id / view_id / site_url / agency_name."""3334    agency_name = "Via Capitale"3536    def fetch(self) -> list[PropertyListing]:37        # auto-guérison : recharger les identifiants depuis la config publique38        # du site (résilient si source.immo fait tourner ses UUID).39        try:40            cfg = self.get(41                f"{self.site_url.rstrip('/')}/wp-content/uploads/_sourceimmo/_configs.json"42            ).json()43            self.account_id = cfg.get("account_id", self.account_id)44            self.api_key = cfg.get("api_key", self.api_key)45            self.app_id = cfg.get("app_id", self.app_id)46            self.view_id = cfg.get("default_view", self.view_id)47        except Exception:48            pass                      # on garde les valeurs du registre49        return super().fetch()5051    def _to_listing(self, it: dict) -> PropertyListing | None:52        lst = super()._to_listing(it)53        if lst is not None:54            lst.broker_name = self.agency_name55            # URL lisible vers la fiche de la sous-agence quand le slug est dispo56            ref = it.get("ref_number") or it.get("id")57            if ref and self.site_url:58                lst.url = f"{self.site_url.rstrip('/')}/propriete/{ref}"59        return lst606162def _load() -> list[dict]:63    try:64        return json.loads(REGISTRY.read_text(encoding="utf-8"))65    except Exception:66        return []676869def _slug(banner: str) -> str:70    return "via_ag_" + re.sub(r"[^a-z0-9]+", "_", banner.lower()).strip("_")717273for _ag in _load():74    if not all(_ag.get(k) for k in ("domain", "account", "api", "app", "view")):75        continue76    _sid = _slug(_ag["banner"])77    _name = "Via Capitale " + _ag["banner"].replace("-", " ").title()78    globals()[_sid.upper()] = type(79        "".join(p.title() for p in _sid.split("_")),80        (ViaCapitaleAgenceConnector,),81        {82            "source_id": _sid,83            "account_id": _ag["account"],84            "api_key": _ag["api"],85            "app_id": _ag["app"],86            "view_id": _ag["view"],87            "site_url": f"https://{_ag['domain']}",88            "agency_name": _name,89        },90    )91