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.7 KB · 96 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/miner.py : connecteur Le Miner (leminer.ca)5#   Unités locatives neuves au 354, rue Principale à Granby (reconversion du6#   site Miner). Webflow statique rendu serveur : la page /unites affiche7#   trois cartes de typologie (« Unités d'une chambre / à partir de8#   1 470 $/mois »). Granularité : typologie (pas d'unités individuelles).9# -----------------------------------------------------------------------------10from __future__ import annotations1112import re1314from bs4 import BeautifulSoup1516from .base import BaseConnector17from ..schema import Listing1819BASE = "https://leminer.ca"20LIST_URL = f"{BASE}/unites"2122ADDRESS = "354, rue Principale, Granby"23CITY = "Granby"2425# « Unités d'une chambre » / « Unités de deux chambres avec deux salles de bain »26CARD_RE = re.compile(r"^Unités?\s+(?:d'une|de\s+(?:une|deux|trois))\s+chambres?"27                     r"(?:\s+avec\s+(?:une|deux)\s+salles?\s+de\s+bain)?$", re.I)28PRICE_RE = re.compile(r"à\s*partir\s*de\s*([\d\s  ]{4,9})\$\s*/\s*mois", re.I)29IMG_RE = re.compile(r'(?:src|href)="(images/[^"]+\.(?:jpe?g|png|webp))"', re.I)3031WORD_NUM = {"une": 1, "deux": 2, "trois": 3}323334class MinerConnector(BaseConnector):35    source_id = "miner"36    request_delay = 0.63738    def fetch(self) -> list[Listing]:39        listings: list[Listing] = []40        try:41            r = self.get(LIST_URL)42            r.encoding = "utf-8"      # le serveur n'annonce pas le charset43            html = r.text44        except Exception:45            return listings46        soup = BeautifulSoup(html, "html.parser")47        lines = soup.get_text("\n", strip=True).split("\n")4849        images = [f"{BASE}/{u}" for u in dict.fromkeys(IMG_RE.findall(html))50                  if not re.search(r"favicon|webclip|facebook|logo|icon|"51                                   r"vector", u, re.I)][:15]5253        for i, line in enumerate(lines):54            raw = line.strip()55            if not CARD_RE.match(raw):56                continue57            m = PRICE_RE.search(" ".join(lines[i + 1:i + 3]))58            if not m:59                continue60            try:61                price = float(m.group(1).replace(" ", "")62                              .replace(" ", "").replace(" ", ""))63            except ValueError:64                continue6566            bedrooms = None67            mb = re.search(r"(?:d')?(une|deux|trois)\s+chambre", raw, re.I)68            if mb:69                bedrooms = WORD_NUM.get(mb.group(1).lower())70            bathrooms = None71            ms = re.search(r"avec\s+(une|deux)\s+salles?\s+de\s+bain", raw, re.I)72            if ms:73                bathrooms = WORD_NUM.get(ms.group(1).lower())7475            ext_id = f"granby-{bedrooms or 'x'}cc" + (76                f"-{bathrooms}sdb" if bathrooms else "")77            if any(l.external_id == ext_id for l in listings):78                continue79            listings.append(Listing(80                source=self.source_id,81                external_id=ext_id,82                url=LIST_URL,83                title=f"{raw} — Le Miner",84                address=ADDRESS,85                city=CITY,86                bedrooms=bedrooms,87                bathrooms=bathrooms,88                price=price,89                price_label=f"À partir de {m.group(1).strip()} $/mois",90                description="Unités locatives neuves du projet Le Miner à "91                            "Granby : chambres spacieuses, espaces réfléchis "92                            "et lumineux, au cœur du centre-ville.",93                images=images,94            ))95        return listings96