# ----------------------------------------------------------------------------- # Lou-Ka — Agrégateur de logements à louer (province de Québec) # Auteur : Simon-Pierre Boucher — contact@spboucher.ai # connectors/atlas_immo.py : connecteur Atlas société immobilière # (atlasimmo.ca — Beauce : Saint-Georges, Saint-Victor, Notre-Dame-des-Pins, # ~50-100 logements 2½–6½). CMS custom « webchef », pages .php statiques # servies en HTTP (pas de TLS). Page /logements-a-louer-en-beauce.php : toutes # les unités disponibles sont rendues serveur (cartes `div.resultat` — nom # d'immeuble, rue + ville, description « 4X 4 et demi moderne », drapeaux # Disponible/Chauffé/Éclairé avec icônes ok/cancel, photo) ; le filtre # ville/grandeur/prix est purement client (JS). Quand aucun logement n'est # libre, seule la mention « Aucun appartement n'est disponible… » s'affiche # et le connecteur retourne [] — situation normale (parc beauceron plein). # Prix jamais affiché sur les cartes -> None (rien d'inventé) ; granularité # immeuble assumée (précédent immeubles_guillot). Les fiches # /logement-a-louer/-.php existent mais leur structure n'a jamais # été observée (aucune archive) : pas d'appel détail pour l'instant. # robots.txt : Disallow répertoires internes (/webchef, /inc…) seulement. # ----------------------------------------------------------------------------- from __future__ import annotations import re from bs4 import BeautifulSoup from ..schema import Listing, normalize_unit_type from .base import BaseConnector BASE = "http://www.atlasimmo.ca" LIST_URL = f"{BASE}/logements-a-louer-en-beauce.php" # type d'unité dans la description d'immeuble (« 4X 4 et demi moderne ») _TYPE_RE = re.compile(r"(\d)\s*(?:½|1/2|et\s+demi)", re.I) class AtlasImmoConnector(BaseConnector): source_id = "atlas_immo" request_delay = 0.6 def fetch(self) -> list[Listing]: html = self.get(LIST_URL).text soup = BeautifulSoup(html, "html.parser") listings: dict[str, Listing] = {} for card in soup.select("div.resultat"): try: self._parse_card(card, listings) except Exception: continue return list(listings.values()) # -- carte (div.resultat, id bloc-) ----------------------------------------- def _parse_card(self, card, listings: dict[str, Listing]) -> None: link = card.select_one('a[href*="/logement-a-louer/"]') if not link: return url = link["href"] if url.startswith("/"): url = BASE + url m = re.search(r"/logement-a-louer/([^/?#]+?)(?:\.php)?$", url) if not m: return ext_id = m.group(1) if not ext_id or ext_id in listings: return title_el = card.select_one("h2") title = title_el.get_text(strip=True) if title_el else "" # adresse :
  • Rue Ambroise
    Saint-Victor, ---
  • address, city = "", "" li = card.select_one(".description ul li") if li: strong = li.select_one("strong") city = strong.get_text(strip=True) if strong else "" street = li.get_text("\n", strip=True).split("\n")[0].strip() street = street.rstrip(",") address = f"{street}, {city}".strip(", ") if city else street # description libre de l'immeuble (« 4X 4 et demi moderne ») desc_el = card.select_one(".description p:not(.aligneDroit)") description = desc_el.get_text(" ", strip=True) if desc_el else "" m_type = _TYPE_RE.search(description) unit_type = normalize_unit_type(f"{m_type.group(1)} ½") if m_type else "" # drapeaux : Disponible — icon-cancel = non availability = "" amenities: list[str] = [] for span in card.select(".options span"): label = span.get_text(" ", strip=True) icon = span.select_one("i") ok = bool(icon and "icon-ok" in (icon.get("class") or [])) if re.search(r"disponible", label, re.I): if ok: availability = label elif ok and label: amenities.append(label) images: list[str] = [] img = card.select_one("img[src]") if img: u = img["src"] if u.startswith("/"): u = BASE + u if u.startswith("http"): images.append(u) listings[ext_id] = Listing( source=self.source_id, external_id=ext_id, url=url, title=title, address=address, city=city, unit_type=unit_type, price=None, # jamais affiché sur les cartes price_label="", availability=availability, description=description, amenities=amenities, images=images, )