# ----------------------------------------------------------------------------- # Lou-Ka — Agrégateur de logements à louer (province de Québec) # Auteur : Simon-Pierre Boucher — contact@spboucher.ai # connectors/lafrance_mathieu.py : connecteur Lafrance & Mathieu # (lafrance-mathieu.com — Québec, Lévis, Val-Bélair, Beauport, etc.) # Page liste /louer-appartement-quebec (+ filtres ?boroughs=N), une annonce # par immeuble. La page détail /logement/ expose : adresse complète avec # code postal, lat/lng (script JSON building-coordinates), commodités et # caractéristiques (p.attribute-description : chat accepté, balcon, entrées # laveuse-sécheuse/lave-vaisselle…), unités individuelles (numéro, étage, # disponibilité, prix), année de construction, téléphone de l'immeuble. # Fiches visitées via self.detail() (cache BD, max max_details/sync). # ----------------------------------------------------------------------------- from __future__ import annotations import hashlib import json import re from bs4 import BeautifulSoup from ..schema import Listing, infer_city, normalize_unit_type, parse_price from .base import BaseConnector BASE = "https://lafrance-mathieu.com" LIST_URL = f"{BASE}/louer-appartement-quebec" _IMG_RE = re.compile( r'https://gilm-site-vitrine-production\.s3\.amazonaws\.com/' r'media/real_estate/[^"\s\)]+\.(?:jpg|jpeg|png|webp)') _POSTAL_RE = re.compile(r"^[A-Z]\d[A-Z]\s?\d[A-Z]\d$") _PHONE_RE = re.compile(r"\b\d{3}[-. ]\d{3}[-. ]\d{4}\b") class _DetailBudget(Exception): """Budget de fiches détail atteint pour cette synchronisation.""" class LafranceMathieuConnector(BaseConnector): source_id = "lafrance_mathieu" max_details = 150 # nouvelles fiches détail max par synchronisation # -- fiche immeuble --------------------------------------------------------- def _fetch_detail(self, url: str) -> dict: """Page /logement/ -> payload JSON-sérialisable (cache BD).""" html = self.get(url).text soup = BeautifulSoup(html, "html.parser") payload: dict = {} # photos de l'immeuble (bucket S3) payload["images"] = list(dict.fromkeys(_IMG_RE.findall(html))) # lat/lng structurés :