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.3 KB · 83 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/kw_distinction.py : Keller Williams Distinction (Gatineau/Outaouais)5#   Site ASP.NET (plateforme tonikwebstudio) : la grille de résultats et les6#   fiches détail sont rendues via AJAX/UpdatePanel (prix et photos absents du7#   HTML statique, et non rendus de façon fiable même par Firecrawl). En revanche8#   le sitemap.xml énumère TOUTES les propriétés, et l'URL encode ville, type,9#   nombre de chambres, rue et numéro Centris — de quoi bâtir une fiche solide10#   au niveau liste. Le numéro Centris sert de clé de déduplication.11# -----------------------------------------------------------------------------12from __future__ import annotations1314import re1516from .base import BaseConnector17from ..schema import PropertyListing1819BASE = "https://www.kwdistinction.com"20SITEMAP = f"{BASE}/sitemap.xml"21# /maisons-a-vendre-{ville}/{type}/{maison-N-chambres}/{rue}/{no-centris}22_URL_RE = re.compile(23    r'/maisons?-a-vendre-([a-z0-9-]+)/([a-z0-9-]+)/(?:maison-(\d+)-chambres?|[a-z0-9-]+)'24    r'/([a-z0-9-]+)/(\d{6,})', re.I)2526_TYPE_MAP = {27    "maison-de-plain-pied": "Maison", "maison-a-etages": "Maison",28    "maison-a-paliers": "Maison", "maison-a-paliers-multiples": "Maison",29    "maison-mobile": "Maison mobile", "appartement": "Condo", "condo": "Condo",30    "duplex": "Duplex", "triplex": "Triplex", "quadruplex": "Multiplex",31    "quintuplex": "Multiplex", "terrain": "Terrain", "fermette": "Fermette/Agricole",32    "chalet": "Chalet", "commercial": "Commercial", "commerciale": "Commercial",33}343536def _deslug(s: str) -> str:37    return re.sub(r"\s+", " ", s.replace("--", " ").replace("-", " ")).strip().title()383940def _split_city(slug: str) -> tuple[str, str]:41    """« aylmer--gatineau- » -> (Gatineau, Aylmer) ; « cayamant » -> (Cayamant, '')."""42    slug = slug.strip("-")43    fixes = {"Gatineau": "Gatineau", "Quebec": "Québec", "Montreal": "Montréal"}44    if "--" in slug:45        sector, city = slug.split("--", 1)46        city = _deslug(city)47        return fixes.get(city, city), _deslug(sector)48    city = _deslug(slug)49    return fixes.get(city, city), ""505152class KwDistinctionConnector(BaseConnector):53    source_id = "kw_distinction"54    request_delay = 0.55556    def fetch(self) -> list[PropertyListing]:57        try:58            xml = self.get(SITEMAP).text59        except Exception:60            return []61        by_id: dict[str, PropertyListing] = {}62        for m in _URL_RE.finditer(xml):63            ville, ptype, beds, rue, centris = m.groups()64            if centris in by_id:65                continue66            city, sector = _split_city(ville)67            path = m.group(0)68            by_id[centris] = PropertyListing(69                source=self.source_id,70                external_id=centris,71                url=f"{BASE}{path}",72                title=_deslug(rue),73                address=_deslug(rue),74                city=city,75                sector=sector,76                region="Outaouais",77                property_type=_TYPE_MAP.get(ptype.lower(), ""),78                bedrooms=int(beds) if beds else None,79                mls=centris,80                broker_name="Keller Williams Distinction",81            )82        return list(by_id.values())83