# ----------------------------------------------------------------------------- # Lou-Ka — Agrégateur de logements à louer (province de Québec) # Auteur : Simon-Pierre Boucher — contact@spboucher.ai # connectors/sherplex.py : connecteur Sherplex immobilier # (appartementssherbrooke.com) — appartements neufs à louer en Estrie : # Weedon, Coaticook, Magog, Saint-François-Xavier-de-Brompton et # Saint-Denis-de-Brompton. Squarespace rendu serveur : pages de secteur → # pages de projet avec sections texte « Prix » (« 4 ½ : 1370$ à 1400$ », # « Meublé à partir de : 1450$ ») et « Disponibilité ». Granularité : # typologie par projet (pas d'unités individuelles). # ----------------------------------------------------------------------------- from __future__ import annotations import re from bs4 import BeautifulSoup from .base import BaseConnector from ..schema import Listing, normalize_unit_type BASE = "https://www.appartementssherbrooke.com" SECTORS = [ ("appartements-a-louer-weedon", "Weedon"), ("coaticook", "Coaticook"), ("magog", "Magog"), ("saint-francois-xavier-de-brompton", "Saint-François-Xavier-de-Brompton"), ("saintdenis-de-brompton", "Saint-Denis-de-Brompton"), ] # « 4 ½ : 1370$ à 1400$ » TYPE_PRICE_RE = re.compile(r"^(\d)\s*(?:½|1/2)\s*[::]\s*" r"(\d[\d\s ]{2,6})\$(?:\s*à\s*" r"(\d[\d\s ]{2,6})\$)?", re.I) # « Meublé à partir de : 1450$ » (Auberge Magog) FURN_PRICE_RE = re.compile(r"^((?:semi-)?meublé)\s*à\s*partir\s*de\s*[::]?\s*" r"(\d[\d\s ]{2,6})\$", re.I) TITLE_TYPES_RE = re.compile(r"(\d)\s*½") IMG_RE = re.compile(r'https://images\.squarespace-cdn\.com/content/' r'[^"\s\\)]+\.(?:jpe?g|png|webp)', re.I) def _num(s: str) -> float | None: try: return float(re.sub(r"[\s ]", "", s)) except (TypeError, ValueError): return None class SherplexConnector(BaseConnector): source_id = "sherplex" request_delay = 0.6 def fetch(self) -> list[Listing]: listings: list[Listing] = [] for sector, city in SECTORS: try: html = self.get(f"{BASE}/{sector}").text except Exception: continue paths = sorted(set(re.findall( r'href="(/%s/[a-z0-9-]+)"' % re.escape(sector), html))) for path in paths: try: listings += self._project(path, city) except Exception: continue # dédoublonnage par external_id uniq: dict[str, Listing] = {} for l in listings: uniq.setdefault(l.external_id, l) return list(uniq.values()) def _project(self, path: str, city: str) -> list[Listing]: url = BASE + path html = self.get(url).text soup = BeautifulSoup(html, "html.parser") lines = [l.strip() for l in soup.get_text("\n", strip=True).split("\n")] name, title_txt = "", "" mt = re.search(r"