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/st_charles_granby.py : connecteur Le St-Charles (stcharlesgranby.com)5# Condos locatifs modernes au 41, rue Saint-Charles Nord à Granby (studios,6# 3½, 4½, 5½). WordPress/Elementor rendu serveur : la page d'accueil affiche7# une carte par typologie (« 3 1/2 / À partir de 900$ / mois* »).8# Granularité : typologie (pas d'unités individuelles publiées).9# -----------------------------------------------------------------------------10from __future__ import annotations1112import re1314from bs4 import BeautifulSoup1516from .base import BaseConnector17from ..schema import Listing, normalize_unit_type1819BASE = "https://www.stcharlesgranby.com"2021ADDRESS = "41, rue Saint-Charles Nord, Granby"22CITY = "Granby"2324TYPE_RE = re.compile(r"^(studio|\d\s*(?:1/2|½))$", re.I)25PRICE_RE = re.compile(r"À\s*partir\s*de\s*(\d[\d\s ]{2,6})\$\s*/\s*mois",26 re.I)27IMG_RE = re.compile(r'https://www\.stcharlesgranby\.com/wp-content/uploads/'28 r'[^"\s\\)]+\.(?:jpe?g|png|webp)', re.I)293031class StCharlesGranbyConnector(BaseConnector):32 source_id = "st_charles_granby"33 request_delay = 0.63435 def fetch(self) -> list[Listing]:36 listings: list[Listing] = []37 try:38 html = self.get(BASE + "/").text39 except Exception:40 return listings41 soup = BeautifulSoup(html, "html.parser")42 lines = soup.get_text("\n", strip=True).split("\n")4344 images = [u for u in dict.fromkeys(IMG_RE.findall(html))45 if not re.search(r"logo|icon|favicon|plan|"46 r"-\d{2,4}x\d{2,4}\.", u, re.I)][:15]4748 # cartes « Studio / À partir de 900$ / mois* » (le menu répète les49 # typologies sans prix : on n'accepte que type suivi d'un prix)50 for i, line in enumerate(lines):51 raw = line.strip()52 if not TYPE_RE.match(raw):53 continue54 nxt = lines[i + 1].strip() if i + 1 < len(lines) else ""55 if TYPE_RE.match(nxt):56 continue # menu de navigation (types à la suite)57 m = PRICE_RE.search(nxt)58 if not m:59 continue60 unit_type = ("Studio" if raw.lower() == "studio"61 else normalize_unit_type(raw))62 try:63 price = float(m.group(1).replace(" ", "").replace(" ", ""))64 except ValueError:65 continue66 ext_id = ("granby-studio" if unit_type == "Studio" else67 f"granby-{unit_type.replace('½', '.5')}")68 if any(l.external_id == ext_id for l in listings):69 continue70 listings.append(Listing(71 source=self.source_id,72 external_id=ext_id,73 url=BASE + "/",74 title=f"{unit_type} — Le St-Charles",75 address=ADDRESS,76 city=CITY,77 unit_type=unit_type,78 price=price,79 price_label=f"À partir de {m.group(1).strip()}$ / mois",80 description="Condos locatifs modernes, lumineux et bien "81 "pensés au cœur de Granby (studios, 3½, 4½ "82 "et 5½), à distance de marche des services.",83 images=images,84 ))85 return listings86