Lou·Ka — tous les logements à louer du Québec, un seul endroit.
HTML 98.9%
Python 0.6%
1# -----------------------------------------------------------------------------2# Lou-Ka — Agrégateur de logements à louer (province de Québec)3# Auteur : Simon-Pierre Boucher — contact@spboucher.ai4# connectors/westwalk.py : connecteur Westwalk DDO (westwalk.ca)5# Immeuble neuf au 12, rue Davignon, Dollard-des-Ormeaux (West-Island),6# à 10 min à pied du REM. WordPress/Elementor statique : la page7# /en/apartments-for-rent-dollard-des-ormeaux/ affiche un « image-box » par8# type d'unité (LOFT, 1-BEDROOM, 2-BEDROOMS, 3-BEDROOMS) avec soit un prix9# « Starting at $2,300 », soit « 100% rented » — seuls les types avec un10# prix réel sont annoncés (une annonce par type, id stable ww-ddo-<type>).11# Le site liste aussi Anjou (sans cartes) et Pointe-Claire (prix12# placeholders $X,XXX) : exclus. Commodités = titres h3 de la page.13# -----------------------------------------------------------------------------14from __future__ import annotations1516import re1718from bs4 import BeautifulSoup1920from ..schema import Listing, strip_accents21from .base import BaseConnector2223BASE = "https://westwalk.ca"24PAGE = f"{BASE}/en/apartments-for-rent-dollard-des-ormeaux/"25ADDRESS = "12, rue Davignon, Dollard-des-Ormeaux, QC H9B 0B6"2627# Libellé du type -> type d'unité QC28_TYPES = {29 "LOFT": "Loft",30 "1-BEDROOM": "3½",31 "2-BEDROOMS": "4½",32 "3-BEDROOMS": "5½",33}34_PRICE_RE = re.compile(r"\$\s*([\d][\d,\s]*\d|\d)")35_IMG_RE = re.compile(36 r'https://westwalk\.ca/wp-content/uploads/[^"\'\s]+?'37 r'\.(?:jpg|jpeg|png|webp)', re.I)38_SKIP_IMG = re.compile(r"logo|icon|favicon|bullet|map-|-\d{2,4}x\d{2,4}\.", re.I)39# h3 de la page qui ne sont PAS des commodités40_SKIP_H3 = re.compile(r"^(unit\s|follow|contact|westwalk)", re.I)414243class WestwalkConnector(BaseConnector):44 source_id = "westwalk"45 request_delay = 0.646 max_images = 204748 def fetch(self) -> list[Listing]:49 html = self.get(PAGE).text50 soup = BeautifulSoup(html, "html.parser")5152 # galerie (photos du projet, sans logos/miniatures)53 images: list[str] = []54 for u in _IMG_RE.findall(html):55 if _SKIP_IMG.search(u):56 continue57 if u not in images:58 images.append(u)59 images = images[: self.max_images]6061 # commodités : titres h3 courts de la page (Elevator, Gym, Pool…)62 amenities: list[str] = []63 for h in soup.find_all("h3"):64 t = h.get_text(" ", strip=True)65 if t and len(t) < 60 and not _SKIP_H3.match(t):66 if t not in amenities:67 amenities.append(t)68 amenities = amenities[:25]6970 # description : méta de la page71 desc = ""72 meta = soup.find("meta", attrs={"name": "description"})73 if meta and meta.get("content"):74 desc = meta["content"].strip()[:600]7576 listings: list[Listing] = []77 for box in soup.select(".elementor-image-box-content"):78 t_el = box.select_one(".elementor-image-box-title")79 d_el = box.select_one(".elementor-image-box-description")80 if not (t_el and d_el):81 continue82 label = t_el.get_text(" ", strip=True).upper()83 if label not in _TYPES:84 continue85 price_txt = d_el.get_text(" ", strip=True)86 pm = _PRICE_RE.search(price_txt)87 if not pm:88 continue # « 100% rented » ou placeholder : exclu89 try:90 price = float(re.sub(r"[^\d]", "", pm.group(1)))91 except ValueError:92 continue93 if not (100 <= price <= 20000):94 continue95 slug = re.sub(r"[^a-z0-9]+", "-",96 strip_accents(label.lower())).strip("-")97 listings.append(Listing(98 source=self.source_id,99 external_id=f"ddo-{slug}",100 url=PAGE,101 title=f"Westwalk DDO — {label.title()}",102 address=ADDRESS,103 sector="Dollard-des-Ormeaux",104 city="Dollard-des-Ormeaux",105 unit_type=_TYPES[label],106 price=price,107 price_label=f"À partir de {int(price)} $/mois",108 availability=price_txt,109 description=desc,110 amenities=list(amenities),111 images=list(images),112 ))113 return listings114