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/primo.py : connecteur Primo Immobilier (primoimmobilier.ca —5# condos locatifs de luxe en Montérégie : Lumicité Condominiums à6# Saint-Mathieu-de-Beloeil et Quartier Trinité à Saint-Basile-le-Grand).7# WordPress rendu serveur mais SANS inventaire d'unités : chaque page8# projet affiche un seul prix plancher « À partir de 2075$/mois » (les9# typologies 3½/4½/5½ n'apparaissent que dans le formulaire, sans prix).10# Granularité = PROJET (une annonce par projet, prix « à partir de »).11# external_id = slug de la page projet — stable.12# -----------------------------------------------------------------------------13from __future__ import annotations1415import html as _html16import re1718from ..schema import Listing19from .base import BaseConnector2021BASE = "https://primoimmobilier.ca"2223# (slug page, nom du projet, ville)24PROJECTS: tuple[tuple[str, str, str], ...] = (25 ("saint-mathieu-de-beloeil", "Lumicité Condominiums",26 "Saint-Mathieu-de-Beloeil"),27 ("saint-basile-le-grand", "Quartier Trinité", "Saint-Basile-le-Grand"),28)2930H1_RE = re.compile(r"<h1[^>]*>([\s\S]*?)</h1>", re.I)31FROM_RE = re.compile(r"À\s+partir\s+de\s+([\d\s ]{3,9})\$\s*/\s*mois", re.I)32SCRIPT_RE = re.compile(r"<(script|style)[\s\S]*?</\1>", re.I)33TAG_RE = re.compile(r"<[^>]+>")34IMG_RE = re.compile(35 r'https://primoimmobilier\.ca/wp-content/[^"\s)]+\.(?:jpe?g|png|webp)',36 re.I)373839class PrimoConnector(BaseConnector):40 source_id = "primo"41 request_delay = 0.84243 def fetch(self) -> list[Listing]:44 listings: list[Listing] = []45 for slug, name, city in PROJECTS:46 url = f"{BASE}/{slug}/"47 try:48 html = self.get(url).text49 except Exception:50 continue51 flat = re.sub(r"[\s ]+", " ",52 TAG_RE.sub(" ", _html.unescape(53 SCRIPT_RE.sub(" ", html))))54 pm = FROM_RE.search(flat)55 price = None56 if pm:57 try:58 price = float(re.sub(r"[\s ]", "", pm.group(1)))59 except ValueError:60 price = None61 if price is None or not (500 <= price <= 8000):62 continue # pas de prix publié = pas d'annonce6364 hm = H1_RE.search(html)65 title = (re.sub(r"\s+", " ", _html.unescape(66 TAG_RE.sub(" ", hm.group(1)))).strip()67 if hm else f"Condos locatifs {name}")6869 images = [u for u in dict.fromkeys(IMG_RE.findall(html))70 if not re.search(r"logo|icon|favicon|banners", u, re.I)71 ][:10]7273 listings.append(Listing(74 source=self.source_id,75 external_id=slug,76 url=url,77 title=title,78 address="", # adresse civique non publiée79 city=city,80 price=price,81 price_label=f"à partir de {int(price)} $/mois",82 description=f"{name} à {city} — condos locatifs de luxe de "83 "Primo Immobilier (typologies 3½ à 5½). "84 "Ascenseur, stationnement et rangement "85 "intérieurs, plafonds de 9 pieds, comptoirs de "86 "quartz, accessible aux personnes à mobilité "87 "réduite.",88 images=images,89 ))90 return listings91