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%
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