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/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