SPB Git forge

spb/lou-ka

Public

Lou·Ka — tous les logements à louer du Québec, un seul endroit.

232commits 1branches 0releases
172.9 MBsize
maindefault branch
2 days agolast push
HTML 98.9% Python 0.6%
3.6 KB · 97 lines python
Raw Blame History
1# -----------------------------------------------------------------------------2# Lou-Ka — Agrégateur de logements à louer (province de Québec)3# Auteur : Simon-Pierre Boucher — contact@spboucher.ai4# connectors/port_de_mer.py : connecteur Port de Mer (portdemer.ca)5#   Complexe locatif unique de 389 unités au 99-101 Place Charles-LeMoyne,6#   Longueuil — directement en face du métro Longueuil. Site une-page7#   (CMS Prog Expert) rendu serveur : description, caractéristiques,8#   fourchette de prix « À partir de : 1515 $ - 2040 $ ». Aucun inventaire9#   par unité publié (sitemap-Room vide) → une annonce par immeuble,10#   comme brochu.py.11# -----------------------------------------------------------------------------12from __future__ import annotations1314import re1516from bs4 import BeautifulSoup1718from ..schema import Listing19from .base import BaseConnector2021BASE = "https://portdemer.ca"22PAGE_URL = f"{BASE}/fr/accueil"2324ADDRESS = "99-101, Place Charles-LeMoyne, Longueuil"25PRICE_RE = re.compile(r"[ÀA]\s*partir\s*de\s*:?\s*([\d\s ,]+)\s*\$"26                      r"(?:\s*[-–]\s*([\d\s ,]+)\s*\$)?", re.I)27IMG_RE = re.compile(r"(?:https?:)?//portdemer\.ca/mod/file/"28                    r"(?:Block|SubBlock)Image/[^\"'()\s]+\.(?:jpe?g|png|webp)"29                    r"[^\"'()\s]*", re.I)303132def _num(txt: str) -> float | None:33    try:34        return float(txt.replace(" ", "").replace(" ", "").replace(",", ""))35    except (AttributeError, ValueError):36        return None373839class PortDeMerConnector(BaseConnector):40    source_id = "port_de_mer"41    request_delay = 0.64243    def fetch(self) -> list[Listing]:44        try:45            html = self.get(PAGE_URL).text46        except Exception:47            return []48        soup = BeautifulSoup(html, "html.parser")49        text = soup.get_text("\n", strip=True)5051        price = None52        price_label = ""53        m = PRICE_RE.search(text)54        if m:55            price = _num(m.group(1))56            price_label = re.sub(r"\s+", " ", m.group(0)).strip()5758        # description : paragraphes substantiels de la section d'intro59        paras = [p.get_text(" ", strip=True) for p in soup.find_all("p")60                 if len(p.get_text(strip=True)) > 80]61        description = re.sub(r"\s+", " ", " ".join(paras[:4])).strip()[:1200]6263        # caractéristiques : les <li> des blocs sw-text (listes à puces)64        amenities: list[str] = []65        for li in soup.select("div.sw-text li"):66            t = re.sub(r"\s+", " ", li.get_text(" ", strip=True))67            if 0 < len(t) <= 60 and t not in amenities:68                amenities.append(t)6970        images = [("https:" + u if u.startswith("//") else u).split("?")[0]71                  for u in dict.fromkeys(IMG_RE.findall(html))][:20]7273        contact: dict = {}74        m = re.search(r'href="mailto:([^"?]+)"', html)75        if m:76            contact["email"] = m.group(1).strip().lower()77        m = re.search(r'href="tel:\+?1?(\d{3})(\d{3})(\d{4})"', html)78        if m:79            contact["phone"] = f"{m.group(1)}-{m.group(2)}-{m.group(3)}"8081        return [Listing(82            source=self.source_id,83            external_id="port-de-mer-longueuil",84            url=PAGE_URL,85            title="Port de Mer — appartements face au métro Longueuil",86            address=ADDRESS,87            city="Longueuil",88            price=price,89            price_label=price_label,90            availability="Informations et locations : "91                         "communiquer avec l'immeuble",92            description=description,93            amenities=amenities[:20],94            details={"contact": contact} if contact else {},95            images=images,96        )]97