spb/auto-ka Public
Python 81.9%
TypeScript 12.4%
CSS 5.5%
1# -----------------------------------------------------------------------------2# Auto-Ka — Agrégateur de voitures usagées à vendre (province de Québec)3# Auteur : Simon-Pierre Boucher — contact@spboucher.ai4# connectors/gatsby_dealers.py : connecteurs des concessionnaires de marque5# sur la plateforme Gatsby « autoroot.io » (inventaire EvalAuto — Groupe6# Beaucage : Kia Granby/Drummondville, Mazda Sherbrooke, Mitsubishi et7# Nissan Drummondville, Nissan Victoriaville).8#9# Même plateforme que megacentredeliquidation (voir megacentre.py) : chaque10# véhicule expose /page-data/auto-usage/<slug>/page-data.json -> `usedCar`11# complet. MAIS contrairement au Méga Centre, le sitemap de ces sites ne12# référence pas les pages véhicule individuelles — seulement les pages13# catégorie. L'énumération passe donc par l'API AWS AppSync que le site14# utilise lui-même côté client : le endpoint GraphQL et la clé x-api-key15# sont publiés dans siteParameters du page-data de la page liste16# (/page-data/auto-usage/page-data.json). La requête17# getUsedCarSearchByRelevanceMultiTable, filtrée par nameDealer, renvoie18# 30 items/page + nextFilter (curseur) -> tous les slugs.19#20# Le reste (cache hebdo des page-data détail, plafond AUTOKA_MAX_DETAILS,21# usedCar -> Vehicle) est hérité de MegaCentreDeLiquidation.22# AUTOKA_MAX_PAGES (env) limite la pagination AppSync pour les tests.23# -----------------------------------------------------------------------------24from __future__ import annotations2526import json27import os28import time2930from .megacentre import MegaCentreDeLiquidation3132_QUERY_NAME = "getUsedCarSearchByRelevanceMultiTable"3334# champs minimaux : on ne veut que les slugs (le détail vient du page-data)35_ITEM_FIELDS = "{ nextFilter items { id slug nameDealer } }"363738class GatsbyEvalAutoConnector(MegaCentreDeLiquidation):39 """Base des sites Gatsby autoroot/EvalAuto sans sitemap véhicule."""4041 source_id = "" # base non enregistrée par le registre42 base_url = ""43 dealer_name = ""44 city = ""45 request_delay = 1.046 max_details = 30047 max_aws_pages = 60 # garde-fou dur (60 x 30 = 1 800 véh.)4849 # -- POST GraphQL poli ----------------------------------------------------50 def _post_json(self, url: str, payload: dict, headers: dict) -> dict:51 wait = self.request_delay - (time.time() - self._last_request)52 if wait > 0:53 time.sleep(wait)54 resp = self.session.post(url, json=payload, headers=headers,55 timeout=self.timeout)56 self._last_request = time.time()57 resp.raise_for_status()58 return resp.json()5960 @staticmethod61 def _gql(dealer: str, next_item: str | None) -> str:62 nxt = f', usedCarNextItem:"{next_item}"' if next_item else ""63 return f"""64 {{65 {_QUERY_NAME}(order: "DESC",66 searchValue: "",67 maxPrice: 1000000,68 minPrice: 0,69 maxYear: 2035,70 minYear: 1900,71 brand: [],72 model: [],73 nameDealer: {json.dumps([dealer])},74 extColor: [],75 drivetrain: [],76 passengers: [],77 engine: [],78 bodytype: [],79 transmission: [],80 certified: [true,false],81 discount: [true,false],82 prestige: [true,false]83 {nxt})84 {_ITEM_FIELDS}85 }}86 """8788 # -- énumération des slugs via la page liste + AppSync ---------------------89 def _vehicle_slugs(self) -> list[str]:90 data = self.get(91 f"{self.base_url}/page-data/auto-usage/page-data.json").json()92 result = data.get("result") or {}93 params = (result.get("data") or {}).get("siteParameters") or {}94 context = result.get("pageContext") or {}9596 # les 30 premiers véhicules sont pré-rendus dans le pageContext97 slugs = {str(c.get("slug")) for c in context.get("allUsedCar") or []98 if c.get("slug")}99100 endpoint = str(params.get("awsEndpoint") or "")101 token = str(params.get("awsToken") or "")102 dealer = str(params.get("awsDealerNameFilter")103 or context.get("nameDealer") or self.dealer_name)104 if not endpoint or not token: # repli : pré-rendu seulement105 return sorted(slugs)106107 cap = min(int(os.environ.get("AUTOKA_MAX_PAGES", self.max_aws_pages)),108 self.max_aws_pages)109 headers = {"x-api-key": token, "Content-Type": "application/json"}110 next_item: str | None = None111 for _ in range(cap):112 try:113 resp = self._post_json(endpoint,114 {"query": self._gql(dealer, next_item)},115 headers)116 except Exception:117 break # AppSync HS -> pré-rendu118 payload = (resp.get("data") or {}).get(_QUERY_NAME) or {}119 items = payload.get("items") or []120 for item in items:121 slug = str(item.get("slug") or "")122 if slug:123 slugs.add(slug)124 next_item = payload.get("nextFilter")125 if not items or not next_item:126 break127 return sorted(slugs)128129 # -- usedCar -> Vehicle (ville de repli) -----------------------------------130 def _to_vehicle(self, slug, d):131 veh = super()._to_vehicle(slug, d)132 if veh is not None and not veh.city:133 veh.city = self.city134 return veh135136137# ---------------------------------------------------------------------------138# Concessionnaires (1 sous-classe = 1 source) — Groupe Beaucage139# ---------------------------------------------------------------------------140141class KiaGranby(GatsbyEvalAutoConnector):142 source_id = "kiagranby"143 base_url = "https://www.kiagranby.com"144 dealer_name = "Kia de Granby"145 city = "Granby"146147148class KiaDrummondville(GatsbyEvalAutoConnector):149 source_id = "kiadrummondville"150 base_url = "https://www.kiadrummondville.com"151 dealer_name = "Kia Drummondville"152 city = "Drummondville"153154155class MazdaDeSherbrooke(GatsbyEvalAutoConnector):156 source_id = "mazdadesherbrooke"157 base_url = "https://www.mazdadesherbrooke.com"158 dealer_name = "Mazda de Sherbrooke"159 city = "Sherbrooke"160161162class MitsubishiDrummondville(GatsbyEvalAutoConnector):163 source_id = "mitsubishidrummondville"164 base_url = "https://www.mitsubishidrummondville.com"165 dealer_name = "Mitsubishi Drummondville"166 city = "Drummondville"167168169class NissanDrummondville(GatsbyEvalAutoConnector):170 source_id = "nissandrummondville"171 base_url = "https://www.nissandrummondville.com"172 dealer_name = "Nissan Drummondville"173 city = "Drummondville"174175176class NissanVictoriaville(GatsbyEvalAutoConnector):177 source_id = "nissanvictoriaville"178 base_url = "https://www.nissanvictoriaville.com"179 dealer_name = "Nissan de Victoriaville"180 city = "Victoriaville"181