Vague 3 : grands portails — AutoTrader.ca/AutoHebdo, Otogo.ca, CarGurus.ca
- autotrader : plateforme AutoScout24 (__NEXT_DATA__ des SRP /cars/reg_qc), occasion QC triée récent d abord, fenêtre AUTOKA_AUTOTRADER_MAX (déf. 2 000), direct + repli Scrapfly ASP sans render_js sur 403 (0 crédit en régime normal) - otogo : API JSON publique /api-search/ (découverte via bundles Nuxt), inventaire complet ~23 700 annonces, VIN ≈99 % + GPS exact du vendeur, partition par tranches de prix (profondeur API 1 000/requête), AUTOKA_OTOGO_MAX (déf. 0 = tout), 0 crédit externe - cargurus : searchResults.action JSON via Scrapfly ASP (DataDome), VIN 100 % + deal rating/dealScore/expectedPrice dans details, cap AUTOKA_CARGURUS_MAX (déf. 500) ≈ 11 crédits/sync - normalize : nearest_city(lat, lng) — ville la plus proche depuis villes_gps.json, nom accentué restauré via sources.json (repli Otogo) - sources.json : 142 sources (3 portails, autorité dédup type=portail) - docs connecteurs régénérés (30 fiches) + historique Vague 3 Premier sync : 26 164 annonces ajoutées, dédup VIN 6 655 groupes / 8 438 doublons masqués (otogo→concessions directes : 5 847) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
22 changed files +956 −122
added
autoka/connectors/autotrader.py
+191 −0
@@ -0,0 +1,191 @@ | ||
| 1 | +# ----------------------------------------------------------------------------- | |
| 2 | +# Auto-Ka — Agrégateur de voitures usagées à vendre (province de Québec) | |
| 3 | +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai | |
| 4 | +# connectors/autotrader.py : PORTAIL — AutoTrader.ca / AutoHebdo (Québec) | |
| 5 | +# | |
| 6 | +# AutoTrader.ca tourne depuis 2025-2026 sur la plateforme AutoScout24 | |
| 7 | +# (Next.js) : chaque page de résultats (SRP) embarque les 20 annonces | |
| 8 | +# complètes en JSON dans <script id="__NEXT_DATA__"> — marque/modèle/ | |
| 9 | +# version, année, prix (brut + évaluation de prix), kilométrage, boîte, | |
| 10 | +# carburant, description, ville/code postal du vendeur, concessionnaire | |
| 11 | +# vs particulier, téléphone et photos CDN. Pas de VIN dans la liste ni | |
| 12 | +# sur la page détail (vérifié 2026-08-18) — la dédup VIN ne s'applique | |
| 13 | +# donc pas à cette source. | |
| 14 | +# | |
| 15 | +# URL : /cars/reg_qc?offer=U&sort=age&desc=1&page=N | |
| 16 | +# - reg_qc : province de Québec seulement | |
| 17 | +# - offer=U : occasion seulement (34 870 annonces au 2026-08-18) | |
| 18 | +# - sort=age&desc=1 : plus récentes d'abord — la fenêtre plafonnée | |
| 19 | +# couvre ainsi les nouveautés à chaque cycle | |
| 20 | +# - profondeur max : 200 pages × 20 = 4 000 annonces par requête | |
| 21 | +# | |
| 22 | +# Plafond : AUTOKA_AUTOTRADER_MAX (défaut 2 000) — fenêtre glissante des | |
| 23 | +# annonces les plus récentes ; les annonces sorties de la fenêtre passent | |
| 24 | +# inactives après le délai de grâce standard (MISS_GRACE), la source garde | |
| 25 | +# donc un volume ≈ plafond en base. | |
| 26 | +# | |
| 27 | +# Anti-bot : Akamai présent mais le HTML SSR se sert en direct (vérifié | |
| 28 | +# pages 1-50) ; repli automatique Scrapfly ASP SANS render_js sur 403/429 | |
| 29 | +# (~1 crédit/page en cas de blocage seulement — budget nul en régime normal). | |
| 30 | +# ----------------------------------------------------------------------------- | |
| 31 | +from __future__ import annotations | |
| 32 | + | |
| 33 | +import json | |
| 34 | +import os | |
| 35 | +import re | |
| 36 | + | |
| 37 | +import requests | |
| 38 | + | |
| 39 | +from ..schema import Vehicle | |
| 40 | +from .base import BaseConnector | |
| 41 | + | |
| 42 | +BASE = "https://www.autotrader.ca" | |
| 43 | +SRP = BASE + "/cars/reg_qc?offer=U&sort=age&desc=1&page={page}" | |
| 44 | + | |
| 45 | +PAGE_SIZE = 20 | |
| 46 | +MAX_PAGES_SITE = 200 # profondeur maximale exposée par la plateforme | |
| 47 | + | |
| 48 | +_NEXT_DATA_RE = re.compile( | |
| 49 | + r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>', re.S) | |
| 50 | + | |
| 51 | +# vignettes liste 250x188 -> photos 640x480 (servies par le CDN AutoScout24) | |
| 52 | +_IMG_SIZE_RE = re.compile(r"/\d+x\d+\.webp$") | |
| 53 | + | |
| 54 | +# libellés d'évaluation de prix AutoScout24 (tracking.priceLabel) | |
| 55 | +_PRICE_LABELS = {"top-price", "good-price", "fair-price", "expensive", | |
| 56 | + "too-expensive"} | |
| 57 | + | |
| 58 | + | |
| 59 | +def _max_listings() -> int: | |
| 60 | + try: | |
| 61 | + return max(PAGE_SIZE, int(os.environ.get("AUTOKA_AUTOTRADER_MAX", | |
| 62 | + "2000"))) | |
| 63 | + except ValueError: | |
| 64 | + return 2000 | |
| 65 | + | |
| 66 | + | |
| 67 | +class AutoTraderQuebec(BaseConnector): | |
| 68 | + """Portail AutoTrader.ca — annonces d'occasion du Québec, récentes d'abord.""" | |
| 69 | + | |
| 70 | + source_id = "autotrader" | |
| 71 | + request_delay = 1.5 # politesse : gros portail national | |
| 72 | + | |
| 73 | + def __init__(self) -> None: | |
| 74 | + super().__init__() | |
| 75 | + self.session.headers.update({ | |
| 76 | + "Accept": "text/html,application/xhtml+xml", | |
| 77 | + "Accept-Language": "fr-CA,fr;q=0.9,en;q=0.5", | |
| 78 | + }) | |
| 79 | + | |
| 80 | + # -- extraction ----------------------------------------------------------- | |
| 81 | + | |
| 82 | + def _fetch_page(self, page: int) -> tuple[list[dict], int]: | |
| 83 | + """Annonces embarquées + nombre total de pages d'une page SRP.""" | |
| 84 | + url = SRP.format(page=page) | |
| 85 | + try: | |
| 86 | + html = self.get(url).text | |
| 87 | + except requests.HTTPError as exc: # Akamai : repli ASP économe | |
| 88 | + status = exc.response.status_code if exc.response is not None else 0 | |
| 89 | + if status not in (403, 429): | |
| 90 | + raise | |
| 91 | + html = self.get_scrapfly(url, render_js=False, asp=True) | |
| 92 | + m = _NEXT_DATA_RE.search(html) | |
| 93 | + if not m: | |
| 94 | + raise RuntimeError(f"autotrader : __NEXT_DATA__ introuvable " | |
| 95 | + f"(page {page})") | |
| 96 | + pp = json.loads(m.group(1)).get("props", {}).get("pageProps", {}) | |
| 97 | + return (pp.get("listings") or []), int(pp.get("numberOfPages") or 0) | |
| 98 | + | |
| 99 | + def _to_vehicle(self, l: dict) -> Vehicle | None: | |
| 100 | + ext_id = str(l.get("id") or "") | |
| 101 | + veh_d = l.get("vehicle") or {} | |
| 102 | + if not ext_id or not l.get("url"): | |
| 103 | + return None | |
| 104 | + if (veh_d.get("articleType") or "Car") != "Car": | |
| 105 | + return None # voitures seulement (atype=C) | |
| 106 | + | |
| 107 | + price_d = l.get("price") or {} | |
| 108 | + price = price_d.get("priceRaw") | |
| 109 | + price = float(price) if price else None | |
| 110 | + | |
| 111 | + loc = l.get("location") or {} | |
| 112 | + if (loc.get("provinceCode") or "QC") != "QC": | |
| 113 | + return None # garde-fou : Québec seulement | |
| 114 | + | |
| 115 | + seller = l.get("seller") or {} | |
| 116 | + is_private = (seller.get("type") or "").lower() == "private" | |
| 117 | + dealer_name = (seller.get("companyName") or "").strip() | |
| 118 | + if is_private or not dealer_name: | |
| 119 | + dealer_name = "Particulier (AutoTrader)" if is_private else \ | |
| 120 | + "AutoTrader.ca" | |
| 121 | + | |
| 122 | + details: dict = {"forsaleby": "particulier" if is_private | |
| 123 | + else "concessionnaire"} | |
| 124 | + label = ((l.get("tracking") or {}).get("priceLabel") or "") | |
| 125 | + if label in _PRICE_LABELS: | |
| 126 | + details["autotrader_price_label"] = label | |
| 127 | + if price_d.get("priceEvaluation") is not None: | |
| 128 | + details["autotrader_price_evaluation"] = price_d["priceEvaluation"] | |
| 129 | + zip_code = (loc.get("zip") or "").strip() | |
| 130 | + if zip_code: | |
| 131 | + details["postal_code"] = zip_code | |
| 132 | + phones = seller.get("phones") or [] | |
| 133 | + if phones and phones[0].get("formattedNumber"): | |
| 134 | + details["dealer_phone"] = phones[0]["formattedNumber"] | |
| 135 | + | |
| 136 | + images = [_IMG_SIZE_RE.sub("/640x480.webp", u) | |
| 137 | + for u in (l.get("images") or []) if u] | |
| 138 | + | |
| 139 | + title_bits = [veh_d.get("make") or "", veh_d.get("model") or "", | |
| 140 | + str(veh_d.get("modelYear") or "")] | |
| 141 | + return Vehicle( | |
| 142 | + source=self.source_id, | |
| 143 | + external_id=ext_id, | |
| 144 | + url=l["url"], | |
| 145 | + kind="auto", | |
| 146 | + title=" ".join(b for b in title_bits if b), | |
| 147 | + make=veh_d.get("make") or "", | |
| 148 | + model=veh_d.get("model") or "", | |
| 149 | + trim=(veh_d.get("modelVersionInput") or "").strip(), | |
| 150 | + year=veh_d.get("modelYear"), | |
| 151 | + price=price, | |
| 152 | + price_label=(price_d.get("priceFormatted") or "").strip(), | |
| 153 | + mileage_label=veh_d.get("mileageInKm") or "", | |
| 154 | + transmission=veh_d.get("transmission") or "", | |
| 155 | + fuel=veh_d.get("fuel") or "", | |
| 156 | + engine=veh_d.get("motorTypeName") or "", | |
| 157 | + dealer_name=dealer_name, | |
| 158 | + city=(loc.get("city") or "").title(), | |
| 159 | + description=l.get("description") or "", | |
| 160 | + details=details, | |
| 161 | + images=images, | |
| 162 | + ) | |
| 163 | + | |
| 164 | + # -- contrat --------------------------------------------------------------- | |
| 165 | + | |
| 166 | + def fetch(self) -> list[Vehicle]: | |
| 167 | + cap = _max_listings() | |
| 168 | + vehicles: dict[str, Vehicle] = {} | |
| 169 | + listings, n_pages = self._fetch_page(1) | |
| 170 | + n_pages = min(n_pages or MAX_PAGES_SITE, MAX_PAGES_SITE, | |
| 171 | + -(-cap // PAGE_SIZE)) | |
| 172 | + for l in listings: | |
| 173 | + v = self._to_vehicle(l) | |
| 174 | + if v: | |
| 175 | + vehicles[v.external_id] = v | |
| 176 | + for page in range(2, n_pages + 1): | |
| 177 | + if len(vehicles) >= cap: | |
| 178 | + break | |
| 179 | + try: | |
| 180 | + listings, _ = self._fetch_page(page) | |
| 181 | + except Exception: | |
| 182 | + break # fin de pagination / page vide | |
| 183 | + new = 0 | |
| 184 | + for l in listings: | |
| 185 | + v = self._to_vehicle(l) | |
| 186 | + if v and v.external_id not in vehicles: | |
| 187 | + vehicles[v.external_id] = v | |
| 188 | + new += 1 | |
| 189 | + if new == 0: | |
| 190 | + break | |
| 191 | + return list(vehicles.values()) | |
added
autoka/connectors/cargurus.py
+172 −0
@@ -0,0 +1,172 @@ | ||
| 1 | +# ----------------------------------------------------------------------------- | |
| 2 | +# Auto-Ka — Agrégateur de voitures usagées à vendre (province de Québec) | |
| 3 | +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai | |
| 4 | +# connectors/cargurus.py : PORTAIL — CarGurus.ca (Québec, avec « deal rating ») | |
| 5 | +# | |
| 6 | +# CarGurus est protégé par DataDome (403 en direct, page « enable JS »). | |
| 7 | +# MAIS son API interne de liste répond en JSON pur à travers Scrapfly ASP | |
| 8 | +# SANS render_js — coût observé : 1 crédit/requête (proxy datacenter) : | |
| 9 | +# GET /Cars/searchResults.action?zip=H2X1Y4&distance=500 | |
| 10 | +# &entitySelectingHelper.selectedEntity=&offset=N&maxResults=50 | |
| 11 | +# &sortDir=ASC&sortType=AGE_IN_DAYS&filtersModified=true | |
| 12 | +# → tableau JSON de ~48 annonces ultra-riches : VIN (≈100 % — alimente la | |
| 13 | +# dédup inter-sources), prix + PRIX ATTENDU + dealRating/dealScore | |
| 14 | +# (l'évaluation « bonne/mauvaise affaire » maison de CarGurus, stockée | |
| 15 | +# dans details), km, boîte, carburant, rouage, carrosserie, couleurs, | |
| 16 | +# moteur, équipements, concessionnaire + ville + téléphone, photo. | |
| 17 | +# | |
| 18 | +# Rayon : zip Montréal (H2X 1Y4) + distance=500 km couvre tout le Québec ; | |
| 19 | +# les débordements ON/NB sont filtrés par sellerRegion == "QC". | |
| 20 | +# Tri : AGE_IN_DAYS ASC = plus récentes d'abord — fenêtre glissante. | |
| 21 | +# | |
| 22 | +# Budget SERRÉ (documenté) : plafond AUTOKA_CARGURUS_MAX (défaut 500) | |
| 23 | +# → ~11 requêtes ≈ 11 crédits Scrapfly par sync, ~130/jour au rythme | |
| 24 | +# de 12 syncs/jour. Les annonces sorties de la fenêtre passent inactives | |
| 25 | +# après le délai de grâce standard (MISS_GRACE). | |
| 26 | +# ----------------------------------------------------------------------------- | |
| 27 | +from __future__ import annotations | |
| 28 | + | |
| 29 | +import json | |
| 30 | +import os | |
| 31 | +import re | |
| 32 | + | |
| 33 | +from ..schema import Vehicle | |
| 34 | +from .base import BaseConnector | |
| 35 | + | |
| 36 | +API = ("https://www.cargurus.ca/Cars/searchResults.action" | |
| 37 | + "?zip=H2X1Y4&distance=500&entitySelectingHelper.selectedEntity=" | |
| 38 | + "&offset={offset}&maxResults=50" | |
| 39 | + "&sortDir=ASC&sortType=AGE_IN_DAYS&filtersModified=true") | |
| 40 | + | |
| 41 | +VDP = "https://www.cargurus.ca/Cars/inventorylisting/vdp.action?listingId={id}" | |
| 42 | + | |
| 43 | +MAX_OFFSET_SITE = 5000 # garde-fou de pagination | |
| 44 | +_VIN_RE = re.compile(r"^[A-HJ-NPR-Z0-9]{17}$") | |
| 45 | +_DOORS_RE = re.compile(r"(\d+)") | |
| 46 | + | |
| 47 | + | |
| 48 | +def _max_listings() -> int: | |
| 49 | + try: | |
| 50 | + return max(48, int(os.environ.get("AUTOKA_CARGURUS_MAX", "500"))) | |
| 51 | + except ValueError: | |
| 52 | + return 500 | |
| 53 | + | |
| 54 | + | |
| 55 | +class CarGurusQuebec(BaseConnector): | |
| 56 | + """Portail CarGurus.ca — annonces QC récentes + deal rating (via Scrapfly).""" | |
| 57 | + | |
| 58 | + source_id = "cargurus" | |
| 59 | + request_delay = 1.0 | |
| 60 | + | |
| 61 | + # -- API --------------------------------------------------------------------- | |
| 62 | + | |
| 63 | + def _fetch_batch(self, offset: int) -> list[dict]: | |
| 64 | + """Un lot d'annonces JSON (via Scrapfly ASP, sans render_js). | |
| 65 | + | |
| 66 | + L'ASP rate parfois un coup (contenu vide/HTML DataDome) : 2 nouvelles | |
| 67 | + tentatives avant d'abandonner — coût borné à 3 crédits par lot. | |
| 68 | + """ | |
| 69 | + import time | |
| 70 | + last_exc: Exception | None = None | |
| 71 | + for attempt in range(3): | |
| 72 | + body = self.get_scrapfly(API.format(offset=offset), | |
| 73 | + render_js=False, asp=True) | |
| 74 | + try: | |
| 75 | + data = json.loads(body) | |
| 76 | + return data if isinstance(data, list) else [] | |
| 77 | + except ValueError as exc: | |
| 78 | + last_exc = exc | |
| 79 | + time.sleep(2.0 * (attempt + 1)) | |
| 80 | + raise RuntimeError(f"cargurus : réponse non-JSON à offset={offset} " | |
| 81 | + f"({str(last_exc)[:80]})") from last_exc | |
| 82 | + | |
| 83 | + def _to_vehicle(self, l: dict) -> Vehicle | None: | |
| 84 | + ext_id = str(l.get("id") or "") | |
| 85 | + if not ext_id: | |
| 86 | + return None | |
| 87 | + if (l.get("sellerRegion") or "") != "QC": | |
| 88 | + return None # rayon 500 km : déborde sur ON/NB | |
| 89 | + | |
| 90 | + price = l.get("price") | |
| 91 | + price = float(price) if price else None | |
| 92 | + mileage = l.get("mileage") | |
| 93 | + mileage = float(mileage) if mileage is not None else None | |
| 94 | + | |
| 95 | + vin = (l.get("vin") or "").strip().upper() | |
| 96 | + if not _VIN_RE.fullmatch(vin): | |
| 97 | + vin = "" | |
| 98 | + | |
| 99 | + is_private = (l.get("sellerType") or "").upper() == "PRIVATE" | |
| 100 | + dealer_name = (l.get("serviceProviderName") or "").strip() | |
| 101 | + if is_private or not dealer_name: | |
| 102 | + dealer_name = "Particulier (CarGurus)" if is_private else \ | |
| 103 | + "CarGurus.ca" | |
| 104 | + | |
| 105 | + details: dict = {"forsaleby": "particulier" if is_private | |
| 106 | + else "concessionnaire"} | |
| 107 | + # l'évaluation d'aubaine maison de CarGurus — la valeur ajoutée du portail | |
| 108 | + for src_key, dst_key in (("dealRating", "cargurus_deal_rating"), | |
| 109 | + ("dealScore", "cargurus_deal_score"), | |
| 110 | + ("expectedPrice", "cargurus_expected_price"), | |
| 111 | + ("priceDifferential", | |
| 112 | + "cargurus_price_differential"), | |
| 113 | + ("daysOnMarket", "days_on_market")): | |
| 114 | + if l.get(src_key) is not None: | |
| 115 | + details[dst_key] = l[src_key] | |
| 116 | + if l.get("phoneNumberString"): | |
| 117 | + details["dealer_phone"] = l["phoneNumberString"] | |
| 118 | + | |
| 119 | + doors = None | |
| 120 | + m = _DOORS_RE.search(l.get("localizedDoors") or "") | |
| 121 | + if m: | |
| 122 | + doors = int(m.group(1)) | |
| 123 | + | |
| 124 | + city = (l.get("sellerCity") or "").split(",")[0].strip() | |
| 125 | + img = (l.get("originalPictureData") or {}).get("url") or "" | |
| 126 | + | |
| 127 | + return Vehicle( | |
| 128 | + source=self.source_id, | |
| 129 | + external_id=ext_id, | |
| 130 | + url=VDP.format(id=ext_id), | |
| 131 | + kind="auto", | |
| 132 | + title=l.get("listingTitle") or "", | |
| 133 | + make=l.get("makeName") or "", | |
| 134 | + model=l.get("modelName") or "", | |
| 135 | + trim=(l.get("trimName") or "").strip(), | |
| 136 | + year=l.get("carYear"), | |
| 137 | + price=price, | |
| 138 | + price_label=(l.get("priceString") or "").strip(), | |
| 139 | + mileage_km=mileage, | |
| 140 | + transmission=l.get("localizedTransmission") or "", | |
| 141 | + fuel=l.get("localizedFuelType") or "", | |
| 142 | + drivetrain=l.get("localizedDriveTrain") or "", | |
| 143 | + body_type=l.get("bodyTypeName") or "", | |
| 144 | + exterior_color=l.get("localizedExteriorColor") or "", | |
| 145 | + interior_color=l.get("localizedInteriorColor") or "", | |
| 146 | + engine=l.get("localizedEngineDisplayName") or "", | |
| 147 | + doors=doors, | |
| 148 | + vin=vin, | |
| 149 | + stock_number=l.get("stockNumber") or "", | |
| 150 | + dealer_name=dealer_name, | |
| 151 | + city=city, | |
| 152 | + features=[o for o in (l.get("options") or []) if o], | |
| 153 | + details=details, | |
| 154 | + images=[img] if img else [], | |
| 155 | + ) | |
| 156 | + | |
| 157 | + # -- contrat ----------------------------------------------------------------- | |
| 158 | + | |
| 159 | + def fetch(self) -> list[Vehicle]: | |
| 160 | + cap = _max_listings() | |
| 161 | + vehicles: dict[str, Vehicle] = {} | |
| 162 | + offset = 0 | |
| 163 | + while len(vehicles) < cap and offset < MAX_OFFSET_SITE: | |
| 164 | + batch = self._fetch_batch(offset) | |
| 165 | + if not batch: | |
| 166 | + break | |
| 167 | + for l in batch: | |
| 168 | + v = self._to_vehicle(l) | |
| 169 | + if v and v.external_id not in vehicles: | |
| 170 | + vehicles[v.external_id] = v | |
| 171 | + offset += len(batch) | |
| 172 | + return list(vehicles.values()) | |
added
autoka/connectors/otogo.py
+176 −0
@@ -0,0 +1,176 @@ | ||
| 1 | +# ----------------------------------------------------------------------------- | |
| 2 | +# Auto-Ka — Agrégateur de voitures usagées à vendre (province de Québec) | |
| 3 | +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai | |
| 4 | +# connectors/otogo.py : PORTAIL — Otogo.ca (LC Media, 100 % Québec) | |
| 5 | +# | |
| 6 | +# Otogo expose une API JSON publique, sans clé ni rendu JS : | |
| 7 | +# GET https://www.otogo.ca/api-search/?latlng=0,0&locale=fr&no_facets= | |
| 8 | +# &rpp=100&page=N&status=Usagé,Certifié,Démo[&price=lo-hi] | |
| 9 | +# Chaque annonce de la liste est déjà riche : titre/version FR, année, | |
| 10 | +# prix, kilométrage, VIN (≈100 %), nom du concessionnaire et surtout le | |
| 11 | +# GPS EXACT du vendeur (latitude/longitude) — parfait pour la recherche | |
| 12 | +# par rayon et la dédup VIN avec les concessionnaires connectés. | |
| 13 | +# (Paramètres découverts dans les bundles Nuxt : constantes c.b du module | |
| 14 | +# 5731047.js — latlng/locale/rpp/page/price/status/no_facets/no_results.) | |
| 15 | +# | |
| 16 | +# Profondeur : l'API plafonne à ~1 000 résultats par requête (rpp=100 → | |
| 17 | +# meta.pages ≤ 10) pour ~24 000 annonces d'occasion. On PARTITIONNE donc | |
| 18 | +# par tranches de prix (bissection récursive + tranche ouverte « lo- ») : | |
| 19 | +# chaque annonce n'est téléchargée qu'une fois, ~250 requêtes directes | |
| 20 | +# par sync complet, zéro crédit d'API de scraping externe. | |
| 21 | +# | |
| 22 | +# Ville : absente de la liste — déduite du GPS exact via nearest_city() | |
| 23 | +# (data/villes_gps.json) pour l'affichage et la région administrative. | |
| 24 | +# Carburant/boîte : absents de la liste ; repli commun finalize() (jetons | |
| 25 | +# du titre/de la version). Page détail : /auto/{slug_fr} (non visitée — | |
| 26 | +# économie ; le VIN et le GPS sont déjà dans la liste). | |
| 27 | +# | |
| 28 | +# Plafond : AUTOKA_OTOGO_MAX (défaut 0 = inventaire complet). | |
| 29 | +# ----------------------------------------------------------------------------- | |
| 30 | +from __future__ import annotations | |
| 31 | + | |
| 32 | +import os | |
| 33 | +import re | |
| 34 | + | |
| 35 | +from ..normalize import nearest_city | |
| 36 | +from ..schema import Vehicle | |
| 37 | +from .base import BaseConnector | |
| 38 | + | |
| 39 | +BASE = "https://www.otogo.ca" | |
| 40 | +API = BASE + "/api-search/" | |
| 41 | + | |
| 42 | +RPP = 100 # résultats par page (max utile observé) | |
| 43 | +DEPTH = 1000 # profondeur max de pagination par requête | |
| 44 | +STATUS = "Usagé,Certifié,Démo" # occasion seulement (exclut Neuf) | |
| 45 | +PRICE_SPLIT_FLOOR = 250 # $ : largeur minimale d'une tranche de prix | |
| 46 | + | |
| 47 | +_VIN_RE = re.compile(r"^[A-HJ-NPR-Z0-9]{17}$") | |
| 48 | + | |
| 49 | + | |
| 50 | +def _max_listings() -> int: | |
| 51 | + try: | |
| 52 | + return max(0, int(os.environ.get("AUTOKA_OTOGO_MAX", "0"))) | |
| 53 | + except ValueError: | |
| 54 | + return 0 | |
| 55 | + | |
| 56 | + | |
| 57 | +class OtogoQuebec(BaseConnector): | |
| 58 | + """Portail Otogo.ca — inventaire d'occasion des concessionnaires du Québec.""" | |
| 59 | + | |
| 60 | + source_id = "otogo" | |
| 61 | + request_delay = 0.8 | |
| 62 | + | |
| 63 | + def __init__(self) -> None: | |
| 64 | + super().__init__() | |
| 65 | + self.session.headers.update({ | |
| 66 | + "Accept": "application/json", | |
| 67 | + "Accept-Language": "fr-CA,fr;q=0.9,en;q=0.5", | |
| 68 | + }) | |
| 69 | + | |
| 70 | + # -- API -------------------------------------------------------------------- | |
| 71 | + | |
| 72 | + def _query(self, page: int, lo: int, hi: int | None) -> tuple[dict, list[dict]]: | |
| 73 | + """Une page de résultats pour la tranche de prix [lo, hi] (hi=None = ouvert).""" | |
| 74 | + params = { | |
| 75 | + "latlng": "0,0", "locale": "fr", "no_facets": "", | |
| 76 | + "rpp": str(RPP), "status": STATUS, | |
| 77 | + "price": f"{lo}-{'' if hi is None else hi}", | |
| 78 | + } | |
| 79 | + if page > 1: | |
| 80 | + params["page"] = str(page) | |
| 81 | + data = self.get(API, params=params).json() | |
| 82 | + meta = data.get("meta") or {} | |
| 83 | + results = ((data.get("data") or {}).get("results")) or [] | |
| 84 | + return meta, results | |
| 85 | + | |
| 86 | + def _to_vehicle(self, r: dict) -> Vehicle | None: | |
| 87 | + ext_id = str(r.get("id") or "") | |
| 88 | + slug = r.get("slug_fr") or r.get("slug_en") or "" | |
| 89 | + if not ext_id or not slug: | |
| 90 | + return None | |
| 91 | + | |
| 92 | + price = r.get("price") | |
| 93 | + price = float(price) if price and price >= 500 else None | |
| 94 | + mileage = r.get("mileage") | |
| 95 | + mileage = float(mileage) if mileage is not None else None | |
| 96 | + | |
| 97 | + try: | |
| 98 | + lat = float(r.get("latitude")) | |
| 99 | + lng = float(r.get("longitude")) | |
| 100 | + except (TypeError, ValueError): | |
| 101 | + lat = lng = None | |
| 102 | + | |
| 103 | + vin = (r.get("vin") or "").strip().upper() | |
| 104 | + if not _VIN_RE.fullmatch(vin): | |
| 105 | + vin = "" | |
| 106 | + | |
| 107 | + details: dict = {} | |
| 108 | + if r.get("status_fr"): | |
| 109 | + details["otogo_status"] = r["status_fr"] # Usagé | Certifié | Démo | |
| 110 | + | |
| 111 | + return Vehicle( | |
| 112 | + source=self.source_id, | |
| 113 | + external_id=ext_id, | |
| 114 | + url=f"{BASE}/auto/{slug}", | |
| 115 | + kind="auto", | |
| 116 | + title=r.get("title_fr") or r.get("title_en") or "", | |
| 117 | + trim=(r.get("trim_fr") or r.get("trim_en") or "").strip(" |"), | |
| 118 | + year=r.get("year"), | |
| 119 | + price=price, | |
| 120 | + price_label=(f"{price:,.0f} $".replace(",", " ") if price else ""), | |
| 121 | + mileage_km=mileage, | |
| 122 | + vin=vin, | |
| 123 | + dealer_name=(r.get("dealer_name") or "").strip(), | |
| 124 | + city=nearest_city(lat, lng), | |
| 125 | + lat=lat, | |
| 126 | + lng=lng, | |
| 127 | + details=details, | |
| 128 | + images=[u for u in [r.get("thumbnail")] if u], | |
| 129 | + ) | |
| 130 | + | |
| 131 | + # -- contrat ----------------------------------------------------------------- | |
| 132 | + | |
| 133 | + def fetch(self) -> list[Vehicle]: | |
| 134 | + cap = _max_listings() | |
| 135 | + vehicles: dict[str, Vehicle] = {} | |
| 136 | + # partition par prix : bissection récursive tant qu'une tranche | |
| 137 | + # dépasse la profondeur maximale de l'API (~1 000 annonces) | |
| 138 | + stack: list[tuple[int, int | None]] = [(0, 100_000), (100_000, None)] | |
| 139 | + while stack: | |
| 140 | + if cap and len(vehicles) >= cap: | |
| 141 | + break | |
| 142 | + lo, hi = stack.pop() | |
| 143 | + meta, results = self._query(1, lo, hi) | |
| 144 | + total = int(meta.get("total") or 0) | |
| 145 | + if total == 0: | |
| 146 | + continue | |
| 147 | + if total > DEPTH: | |
| 148 | + if hi is None: # tranche ouverte : décaler | |
| 149 | + stack.append((lo + 100_000, None)) | |
| 150 | + stack.append((lo, lo + 100_000)) | |
| 151 | + continue | |
| 152 | + if hi - lo > PRICE_SPLIT_FLOOR: # bissection | |
| 153 | + mid = (lo + hi) // 2 | |
| 154 | + stack.append((mid, hi)) | |
| 155 | + stack.append((lo, mid)) | |
| 156 | + continue | |
| 157 | + # tranche déjà minimale : on prend ce que la profondeur permet | |
| 158 | + for r in results: | |
| 159 | + v = self._to_vehicle(r) | |
| 160 | + if v: | |
| 161 | + vehicles[v.external_id] = v | |
| 162 | + pages = int(meta.get("pages") or 1) | |
| 163 | + for page in range(2, pages + 1): | |
| 164 | + if cap and len(vehicles) >= cap: | |
| 165 | + break | |
| 166 | + try: | |
| 167 | + _, results = self._query(page, lo, hi) | |
| 168 | + except Exception: | |
| 169 | + break # fin de pagination | |
| 170 | + if not results: | |
| 171 | + break | |
| 172 | + for r in results: | |
| 173 | + v = self._to_vehicle(r) | |
| 174 | + if v: | |
| 175 | + vehicles[v.external_id] = v | |
| 176 | + return list(vehicles.values()) | |
modified
autoka/normalize.py
+50 −0
@@ -431,3 +431,53 @@ def city_gps(city: str | None) -> tuple[float, float] | None: | ||
| 431 | 431 | if re.search(rf"(?<![\w-]){re.escape(name)}(?![\w-])", key): |
| 432 | 432 | return table[name] |
| 433 | 433 | return None |
| 434 | + | |
| 435 | + | |
| 436 | +# --------------------------------------------------------------------------- | |
| 437 | +# GPS -> ville connue la plus proche (repli pour les sources qui ne | |
| 438 | +# fournissent QUE les coordonnées du vendeur, ex. le portail Otogo). | |
| 439 | +# Nom d'affichage : restauré avec accents depuis data/sources.json quand une | |
| 440 | +# source connue est dans la même ville, sinon clé de villes_gps.json en | |
| 441 | +# casse de titre. | |
| 442 | +# --------------------------------------------------------------------------- | |
| 443 | + | |
| 444 | +_CITY_DISPLAY: dict[str, str] | None = None | |
| 445 | + | |
| 446 | + | |
| 447 | +def _load_city_display() -> dict[str, str]: | |
| 448 | + global _CITY_DISPLAY | |
| 449 | + if _CITY_DISPLAY is None: | |
| 450 | + import json | |
| 451 | + from pathlib import Path | |
| 452 | + table: dict[str, str] = {} | |
| 453 | + path = Path(__file__).resolve().parent.parent / "data" / "sources.json" | |
| 454 | + try: | |
| 455 | + for s in json.loads(path.read_text(encoding="utf-8"))["sources"]: | |
| 456 | + city = (s.get("city") or "").strip() | |
| 457 | + if city: | |
| 458 | + table.setdefault(_gps_key(city), city) | |
| 459 | + except (OSError, ValueError, KeyError): | |
| 460 | + pass | |
| 461 | + _CITY_DISPLAY = table | |
| 462 | + return _CITY_DISPLAY | |
| 463 | + | |
| 464 | + | |
| 465 | +def nearest_city(lat: float | None, lng: float | None, | |
| 466 | + max_km: float = 40.0) -> str: | |
| 467 | + """Ville du Québec (data/villes_gps.json) la plus proche du point donné. | |
| 468 | + | |
| 469 | + Approximation équirectangulaire (suffisante à l'échelle du Québec) ; | |
| 470 | + chaîne vide si aucune ville connue à moins de `max_km` km. | |
| 471 | + """ | |
| 472 | + if lat is None or lng is None: | |
| 473 | + return "" | |
| 474 | + import math | |
| 475 | + coslat = math.cos(math.radians(lat)) | |
| 476 | + best, best_d2 = "", float("inf") | |
| 477 | + for name, (clat, clng) in _load_villes_gps().items(): | |
| 478 | + d2 = (clat - lat) ** 2 + ((clng - lng) * coslat) ** 2 | |
| 479 | + if d2 < best_d2: | |
| 480 | + best, best_d2 = name, d2 | |
| 481 | + if not best or math.sqrt(best_d2) * 111.0 > max_km: | |
| 482 | + return "" | |
| 483 | + return _load_city_display().get(best) or best.title() | |
modified
data/sources.json
+40 −1
@@ -1557,6 +1557,45 @@ | ||
| 1557 | 1557 | "platform": "Next.js/Apollo — le SRP embarque les AutosListing complets dans __NEXT_DATA__ (attributs canoniques, GPS, VIN parfois) ; kijijiautos.ca (MoVe) n'existe plus (domaine SERVFAIL, 2026-08)", |
| 1558 | 1558 | "connector": "kijiji", |
| 1559 | 1559 | "status": "actif" |
| 1560 | + }, | |
| 1561 | + { | |
| 1562 | + "id": "autotrader", | |
| 1563 | + "name": "AutoTrader.ca / AutoHebdo (Québec)", | |
| 1564 | + "url": "https://www.autotrader.ca", | |
| 1565 | + "listing_url": "https://www.autotrader.ca/cars/reg_qc?offer=U&sort=age&desc=1", | |
| 1566 | + "city": "", | |
| 1567 | + "region": "", | |
| 1568 | + "type": "portail", | |
| 1569 | + "type_note": "grand portail national (inventaire concessionnaires + quelques particuliers) — fenêtre des plus récentes (AUTOKA_AUTOTRADER_MAX, défaut 2 000) ; pas de VIN exposé", | |
| 1570 | + "platform": "AutoScout24 / Next.js — SRP __NEXT_DATA__ embarque 20 annonces/page (prix, km, boîte, carburant, ville, photos, priceLabel) ; direct OK, repli Scrapfly ASP sans render_js sur 403", | |
| 1571 | + "connector": "autotrader", | |
| 1572 | + "status": "actif" | |
| 1573 | + }, | |
| 1574 | + { | |
| 1575 | + "id": "otogo", | |
| 1576 | + "name": "Otogo.ca", | |
| 1577 | + "url": "https://www.otogo.ca", | |
| 1578 | + "listing_url": "https://www.otogo.ca/auto-usagee", | |
| 1579 | + "city": "", | |
| 1580 | + "region": "", | |
| 1581 | + "type": "portail", | |
| 1582 | + "type_note": "portail QC de LC Media — inventaire complet (~24 000), VIN ≈100 % + GPS exact du vendeur → gros pourvoyeur de dédup inter-sources", | |
| 1583 | + "platform": "Nuxt / API JSON publique /api-search/ (latlng/locale/rpp/page/price/status) — profondeur 1 000/requête, partition par tranches de prix ; ville déduite du GPS (nearest_city)", | |
| 1584 | + "connector": "otogo", | |
| 1585 | + "status": "actif" | |
| 1586 | + }, | |
| 1587 | + { | |
| 1588 | + "id": "cargurus", | |
| 1589 | + "name": "CarGurus.ca (Québec)", | |
| 1590 | + "url": "https://www.cargurus.ca", | |
| 1591 | + "listing_url": "https://www.cargurus.ca/Cars/l-Used-Cars-Quebec", | |
| 1592 | + "city": "", | |
| 1593 | + "region": "", | |
| 1594 | + "type": "portail", | |
| 1595 | + "type_note": "portail national avec deal rating (dealScore/expectedPrice dans details) — cap bas (AUTOKA_CARGURUS_MAX, défaut 500), VIN ≈100 %", | |
| 1596 | + "platform": "searchResults.action JSON (zip Montréal + distance 500 km, tri AGE_IN_DAYS, filtre sellerRegion=QC) via Scrapfly ASP sans render_js (DataDome) — ~1 crédit/requête, ~11 requêtes/sync", | |
| 1597 | + "connector": "cargurus", | |
| 1598 | + "status": "actif" | |
| 1560 | 1599 | } |
| 1561 | 1600 | ] |
| 1562 | −} | |
| \ No newline at end of file | ||
| 1601 | +} | |
modified
docs/connecteurs/INDEX.md
+18 −13
@@ -1,13 +1,13 @@ | ||
| 1 | 1 | # Auto-Ka — Documentation des connecteurs |
| 2 | 2 | |
| 3 | −_Générée le 2026-08-18 03:24 par `scripts/gen_connector_docs.py` (rejouable : `.venv/bin/python3 scripts/gen_connector_docs.py`)._ | |
| 3 | +_Générée le 2026-08-18 06:00 par `scripts/gen_connector_docs.py` (rejouable : `.venv/bin/python3 scripts/gen_connector_docs.py`)._ | |
| 4 | 4 | |
| 5 | 5 | ## Vue d'ensemble |
| 6 | 6 | |
| 7 | −- **Sources** : 139 (registre `data/sources.json`) | |
| 8 | −- **Modules connecteurs** : 27 (`autoka/connectors/`) | |
| 9 | −- **Annonces en base** : 23110 — dont 20452 actives (19822 autos, 606 motos, 24 scooters) | |
| 10 | −- **Doublons VIN masqués** : 2315 (voir [dédup VIN](transverse-dedup-vin.md)) | |
| 7 | +- **Sources** : 142 (registre `data/sources.json`) | |
| 8 | +- **Modules connecteurs** : 30 (`autoka/connectors/`) | |
| 9 | +- **Annonces en base** : 49397 — dont 46838 actives (46208 autos, 606 motos, 24 scooters) | |
| 10 | +- **Doublons VIN masqués** : 8438 (voir [dédup VIN](transverse-dedup-vin.md)) | |
| 11 | 11 | - **Rappels Transports Canada** : 16969 rappels distincts (voir [rappels TC](transverse-rappels-tc.md)) |
| 12 | 12 | - **Concessionnaires géolocalisés** : 138 (voir [dealers](transverse-dealers.md)) |
| 13 | 13 | |
@@ -15,30 +15,33 @@ _Générée le 2026-08-18 03:24 par `scripts/gen_connector_docs.py` (rejouable : | ||
| 15 | 15 | |
| 16 | 16 | | Module | Fiche | Sources | Annonces actives | Backend | |
| 17 | 17 | |---|---|---|---|---| |
| 18 | −| `d2c_dealers` | [d2c_dealers.md](d2c_dealers.md) | 66 | 5229 | requests direct — Firecrawl par source au besoin | | |
| 18 | +| `d2c_dealers` | [d2c_dealers.md](d2c_dealers.md) | 66 | 5348 | requests direct — Firecrawl par source au besoin | | |
| 19 | 19 | | `sm360_dealers` | [sm360_dealers.md](sm360_dealers.md) | 15 | 1434 | requests direct — Firecrawl par source au besoin | |
| 20 | 20 | | `moto_dealers` | [moto_dealers.md](moto_dealers.md) | 13 | 630 | requests direct | |
| 21 | 21 | | `central_dealers` | [central_dealers.md](central_dealers.md) | 9 | 345 | requests direct | |
| 22 | −| `gatsby_dealers` | [gatsby_dealers.md](gatsby_dealers.md) | 6 | 191 | requests direct | | |
| 23 | −| `magnetis_dealers` | [magnetis_dealers.md](magnetis_dealers.md) | 4 | 423 | requests direct | | |
| 22 | +| `gatsby_dealers` | [gatsby_dealers.md](gatsby_dealers.md) | 6 | 198 | requests direct | | |
| 23 | +| `magnetis_dealers` | [magnetis_dealers.md](magnetis_dealers.md) | 4 | 426 | requests direct | | |
| 24 | 24 | | `vvu_dealers` | [vvu_dealers.md](vvu_dealers.md) | 4 | 183 | requests direct | |
| 25 | 25 | | `convertus_fc` | [convertus_fc.md](convertus_fc.md) | 3 | 296 | Firecrawl | |
| 26 | 26 | | `albioccasion` | [albioccasion.md](albioccasion.md) | 1 | 1136 | requests direct | |
| 27 | 27 | | `autodurocher` | [autodurocher.md](autodurocher.md) | 1 | 196 | requests direct | |
| 28 | 28 | | `automobileendirect` | [automobileendirect.md](automobileendirect.md) | 1 | 1687 | requests direct | |
| 29 | +| `autotrader` | [autotrader.md](autotrader.md) | 1 | 1960 | Scrapfly | | |
| 30 | +| `cargurus` | [cargurus.md](cargurus.md) | 1 | 504 | Scrapfly | | |
| 29 | 31 | | `classeauto` | [classeauto.md](classeauto.md) | 1 | 109 | requests direct | |
| 30 | 32 | | `clubautozone` | [clubautozone.md](clubautozone.md) | 1 | 47 | requests direct | |
| 31 | −| `desmeules` | [desmeules.md](desmeules.md) | 1 | 243 | requests direct | | |
| 32 | −| `dupontford` | [dupontford.md](dupontford.md) | 1 | 65 | requests direct | | |
| 33 | +| `desmeules` | [desmeules.md](desmeules.md) | 1 | 244 | requests direct | | |
| 34 | +| `dupontford` | [dupontford.md](dupontford.md) | 1 | 66 | requests direct | | |
| 33 | 35 | | `hgregoire` | [hgregoire.md](hgregoire.md) | 1 | 1672 | Scrapfly | |
| 34 | 36 | | `jlkauto` | [jlkauto.md](jlkauto.md) | 1 | 68 | requests direct | |
| 35 | −| `kijiji` | [kijiji.md](kijiji.md) | 1 | 3002 | requests direct | | |
| 36 | −| `leprixdugros` | [leprixdugros.md](leprixdugros.md) | 1 | 886 | Scrapfly | | |
| 37 | −| `megacentre` | [megacentre.md](megacentre.md) | 1 | 455 | requests direct | | |
| 37 | +| `kijiji` | [kijiji.md](kijiji.md) | 1 | 3005 | requests direct | | |
| 38 | +| `leprixdugros` | [leprixdugros.md](leprixdugros.md) | 1 | 973 | Scrapfly | | |
| 39 | +| `megacentre` | [megacentre.md](megacentre.md) | 1 | 456 | requests direct | | |
| 38 | 40 | | `montjolichrysler` | [montjolichrysler.md](montjolichrysler.md) | 1 | 414 | requests direct | |
| 39 | 41 | | `occasionbeaucage` | [occasionbeaucage.md](occasionbeaucage.md) | 1 | 1226 | requests direct | |
| 40 | 42 | | `occasioncharlevoix` | [occasioncharlevoix.md](occasioncharlevoix.md) | 1 | 30 | requests direct | |
| 41 | 43 | | `okaze` | [okaze.md](okaze.md) | 1 | 50 | requests direct | |
| 44 | +| `otogo` | [otogo.md](otogo.md) | 1 | 23700 | requests direct | | |
| 42 | 45 | | `ototr` | [ototr.md](ototr.md) | 1 | 4 | requests direct | |
| 43 | 46 | | `stefoychrysler` | [stefoychrysler.md](stefoychrysler.md) | 1 | 414 | requests direct | |
| 44 | 47 | | `yannicklaberge` | [yannicklaberge.md](yannicklaberge.md) | 1 | 17 | requests direct | |
@@ -58,3 +61,5 @@ _Générée le 2026-08-18 03:24 par `scripts/gen_connector_docs.py` (rejouable : | ||
| 58 | 61 | |---|---| |
| 59 | 62 | | `2a35ac2` | Enrichissement connecteurs : Carfax, détails AED/LPDG, verticale moto, photos Magnetis, GPS concessionnaires | |
| 60 | 63 | | `bbc070b` | Vague 2 : dédup VIN inter-sources, Kijiji particuliers, rappels Transports Canada, coordonnées concessionnaires | |
| 64 | +| `b6d5459` | Documentation standardisée des connecteurs (générateur + fiches) | | |
| 65 | +| Vague 3 | Grands portails : AutoTrader.ca/AutoHebdo (fenêtre récente), Otogo.ca (inventaire complet, VIN+GPS), CarGurus.ca (deal rating, via Scrapfly) | | |
added
docs/connecteurs/autotrader.md
+68 −0
@@ -0,0 +1,68 @@ | ||
| 1 | +# Module `autotrader` — AutoScout24 / Next.js — SRP __NEXT_DATA__ embarque 20 annonces/page | |
| 2 | + | |
| 3 | +_Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas éditer à la main._ | |
| 4 | + | |
| 5 | +## Vue d'ensemble | |
| 6 | + | |
| 7 | +- **Fichier** : `autoka/connectors/autotrader.py` | |
| 8 | +- **Sources membres** : 1 | |
| 9 | +- **Annonces actives (BD)** : 1960 | |
| 10 | +- **Backend de fetch** : Scrapfly | |
| 11 | +- **Mécanique détectée** : __NEXT_DATA__ | |
| 12 | +- **Plafonds / cache** : AUTOKA_AUTOTRADER_MAX | |
| 13 | +- **Classes** : `AutoTraderQuebec` → `autotrader` | |
| 14 | + | |
| 15 | +## Mécanique (en-tête du module) | |
| 16 | + | |
| 17 | +> connectors/autotrader.py : PORTAIL — AutoTrader.ca / AutoHebdo (Québec) | |
| 18 | +> | |
| 19 | +> AutoTrader.ca tourne depuis 2025-2026 sur la plateforme AutoScout24 | |
| 20 | +> (Next.js) : chaque page de résultats (SRP) embarque les 20 annonces | |
| 21 | +> complètes en JSON dans <script id="__NEXT_DATA__"> — marque/modèle/ | |
| 22 | +> version, année, prix (brut + évaluation de prix), kilométrage, boîte, | |
| 23 | +> carburant, description, ville/code postal du vendeur, concessionnaire | |
| 24 | +> vs particulier, téléphone et photos CDN. Pas de VIN dans la liste ni | |
| 25 | +> sur la page détail (vérifié 2026-08-18) — la dédup VIN ne s'applique | |
| 26 | +> donc pas à cette source. | |
| 27 | +> | |
| 28 | +> URL : /cars/reg_qc?offer=U&sort=age&desc=1&page=N | |
| 29 | +> - reg_qc : province de Québec seulement | |
| 30 | +> - offer=U : occasion seulement (34 870 annonces au 2026-08-18) | |
| 31 | +> - sort=age&desc=1 : plus récentes d'abord — la fenêtre plafonnée | |
| 32 | +> couvre ainsi les nouveautés à chaque cycle | |
| 33 | +> - profondeur max : 200 pages × 20 = 4 000 annonces par requête | |
| 34 | +> | |
| 35 | +> Plafond : AUTOKA_AUTOTRADER_MAX (défaut 2 000) — fenêtre glissante des | |
| 36 | +> annonces les plus récentes ; les annonces sorties de la fenêtre passent | |
| 37 | +> inactives après le délai de grâce standard (MISS_GRACE), la source garde | |
| 38 | +> donc un volume ≈ plafond en base. | |
| 39 | +> | |
| 40 | +> Anti-bot : Akamai présent mais le HTML SSR se sert en direct (vérifié | |
| 41 | +> pages 1-50) ; repli automatique Scrapfly ASP SANS render_js sur 403/429 | |
| 42 | +> (~1 crédit/page en cas de blocage seulement — budget nul en régime normal). | |
| 43 | + | |
| 44 | +## Complétude des champs (annonces actives, N = 1 960) | |
| 45 | + | |
| 46 | +| Champ | % rempli | | |
| 47 | +|---|---| | |
| 48 | +| Marque | 100.0 % | | |
| 49 | +| Modèle | 100.0 % | | |
| 50 | +| Année | 99.8 % | | |
| 51 | +| Kilométrage | 99.6 % | | |
| 52 | +| Prix | 100.0 % | | |
| 53 | +| Carburant | 95.7 % | | |
| 54 | +| Boîte (transmission) | 98.6 % | | |
| 55 | +| VIN | 0.0 % | | |
| 56 | +| Photos | 97.5 % | | |
| 57 | +| Carfax | 0.0 % | | |
| 58 | +| GPS (lat/lng) | 91.2 % | | |
| 59 | + | |
| 60 | +## Sources membres (BD live) | |
| 61 | + | |
| 62 | +| Source | Nom | Ville | Région | Actives | Masquées (dup VIN) | Dernier sync | Statut | | |
| 63 | +|---|---|---|---|---|---|---|---| | |
| 64 | +| `autotrader` | AutoTrader.ca / AutoHebdo (Québec) | — | — | 1960 | 0 | 2026-08-18 05:50 | OK (1960 trouvés) | | |
| 65 | + | |
| 66 | +## Erreurs de synchronisation récentes | |
| 67 | + | |
| 68 | +Aucune erreur dans le sync_log pour les sources de ce module. | |
added
docs/connecteurs/cargurus.md
+64 −0
@@ -0,0 +1,64 @@ | ||
| 1 | +# Module `cargurus` — searchResults.action JSON | |
| 2 | + | |
| 3 | +_Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas éditer à la main._ | |
| 4 | + | |
| 5 | +## Vue d'ensemble | |
| 6 | + | |
| 7 | +- **Fichier** : `autoka/connectors/cargurus.py` | |
| 8 | +- **Sources membres** : 1 | |
| 9 | +- **Annonces actives (BD)** : 504 | |
| 10 | +- **Backend de fetch** : Scrapfly | |
| 11 | +- **Mécanique détectée** : — | |
| 12 | +- **Plafonds / cache** : AUTOKA_CARGURUS_MAX | |
| 13 | +- **Classes** : `CarGurusQuebec` → `cargurus` | |
| 14 | + | |
| 15 | +## Mécanique (en-tête du module) | |
| 16 | + | |
| 17 | +> connectors/cargurus.py : PORTAIL — CarGurus.ca (Québec, avec « deal rating ») | |
| 18 | +> | |
| 19 | +> CarGurus est protégé par DataDome (403 en direct, page « enable JS »). | |
| 20 | +> MAIS son API interne de liste répond en JSON pur à travers Scrapfly ASP | |
| 21 | +> SANS render_js — coût observé : 1 crédit/requête (proxy datacenter) : | |
| 22 | +> GET /Cars/searchResults.action?zip=H2X1Y4&distance=500 | |
| 23 | +> &entitySelectingHelper.selectedEntity=&offset=N&maxResults=50 | |
| 24 | +> &sortDir=ASC&sortType=AGE_IN_DAYS&filtersModified=true | |
| 25 | +> → tableau JSON de ~48 annonces ultra-riches : VIN (≈100 % — alimente la | |
| 26 | +> dédup inter-sources), prix + PRIX ATTENDU + dealRating/dealScore | |
| 27 | +> (l'évaluation « bonne/mauvaise affaire » maison de CarGurus, stockée | |
| 28 | +> dans details), km, boîte, carburant, rouage, carrosserie, couleurs, | |
| 29 | +> moteur, équipements, concessionnaire + ville + téléphone, photo. | |
| 30 | +> | |
| 31 | +> Rayon : zip Montréal (H2X 1Y4) + distance=500 km couvre tout le Québec ; | |
| 32 | +> les débordements ON/NB sont filtrés par sellerRegion == "QC". | |
| 33 | +> Tri : AGE_IN_DAYS ASC = plus récentes d'abord — fenêtre glissante. | |
| 34 | +> | |
| 35 | +> Budget SERRÉ (documenté) : plafond AUTOKA_CARGURUS_MAX (défaut 500) | |
| 36 | +> → ~11 requêtes ≈ 11 crédits Scrapfly par sync, ~130/jour au rythme | |
| 37 | +> de 12 syncs/jour. Les annonces sorties de la fenêtre passent inactives | |
| 38 | +> après le délai de grâce standard (MISS_GRACE). | |
| 39 | + | |
| 40 | +## Complétude des champs (annonces actives, N = 504) | |
| 41 | + | |
| 42 | +| Champ | % rempli | | |
| 43 | +|---|---| | |
| 44 | +| Marque | 100.0 % | | |
| 45 | +| Modèle | 100.0 % | | |
| 46 | +| Année | 99.8 % | | |
| 47 | +| Kilométrage | 99.6 % | | |
| 48 | +| Prix | 99.4 % | | |
| 49 | +| Carburant | 66.1 % | | |
| 50 | +| Boîte (transmission) | 99.0 % | | |
| 51 | +| VIN | 99.8 % | | |
| 52 | +| Photos | 92.3 % | | |
| 53 | +| Carfax | 0.0 % | | |
| 54 | +| GPS (lat/lng) | 97.8 % | | |
| 55 | + | |
| 56 | +## Sources membres (BD live) | |
| 57 | + | |
| 58 | +| Source | Nom | Ville | Région | Actives | Masquées (dup VIN) | Dernier sync | Statut | | |
| 59 | +|---|---|---|---|---|---|---|---| | |
| 60 | +| `cargurus` | CarGurus.ca (Québec) | — | — | 504 | 170 | 2026-08-18 05:55 | OK (504 trouvés) | | |
| 61 | + | |
| 62 | +## Erreurs de synchronisation récentes | |
| 63 | + | |
| 64 | +Aucune erreur dans le sync_log pour les sources de ce module. | |
modified
docs/connecteurs/d2c_dealers.md
+56 −56
@@ -6,7 +6,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 6 | 6 | |
| 7 | 7 | - **Fichier** : `autoka/connectors/d2c_dealers.py` |
| 8 | 8 | - **Sources membres** : 66 |
| 9 | −- **Annonces actives (BD)** : 5229 | |
| 9 | +- **Annonces actives (BD)** : 5348 | |
| 10 | 10 | - **Backend de fetch** : requests direct — Firecrawl par source au besoin (`use_firecrawl`, sites derrière Cloudflare) |
| 11 | 11 | - **Mécanique détectée** : sitemap, JSON-LD schema.org, endpoint JSON |
| 12 | 12 | - **Plafonds / cache** : AUTOKA_MAX_DETAILS, cache détail BD (detail_cache) |
@@ -40,7 +40,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 40 | 40 | > Le lastmod du sitemap est bumpé quotidiennement par D2C (inutilisable |
| 41 | 41 | > comme clé de cache) — d'où la clé temporelle hebdomadaire. |
| 42 | 42 | |
| 43 | −## Complétude des champs (annonces actives, N = 5 229) | |
| 43 | +## Complétude des champs (annonces actives, N = 5 348) | |
| 44 | 44 | |
| 45 | 45 | | Champ | % rempli | |
| 46 | 46 | |---|---| |
@@ -53,7 +53,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 53 | 53 | | Boîte (transmission) | 99.8 % | |
| 54 | 54 | | VIN | 99.3 % | |
| 55 | 55 | | Photos | 99.5 % | |
| 56 | −| Carfax | 16.1 % | | |
| 56 | +| Carfax | 34.5 % | | |
| 57 | 57 | | GPS (lat/lng) | 100.0 % | |
| 58 | 58 | |
| 59 | 59 | ## Sources membres (BD live) |
@@ -61,71 +61,71 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 61 | 61 | | Source | Nom | Ville | Région | Actives | Masquées (dup VIN) | Dernier sync | Statut | |
| 62 | 62 | |---|---|---|---|---|---|---|---| |
| 63 | 63 | | `abitibiautocredit` | Abitibi Auto Crédit | Rouyn-Noranda | Abitibi-Témiscamingue | 91 | 85 | 2026-08-18 03:05 | OK (89 trouvés) | |
| 64 | −| `almahonda` | Alma Honda | Alma | Saguenay–Lac-Saint-Jean | 40 | 0 | 2026-08-17 23:35 | OK (40 trouvés) | | |
| 64 | +| `almahonda` | Alma Honda | Alma | Saguenay–Lac-Saint-Jean | 41 | 0 | 2026-08-18 05:14 | OK (40 trouvés) | | |
| 65 | 65 | | `autoquebeclevis` | AutoQuébec Occasion Lévis | Lévis | Chaudière-Appalaches | 77 | 0 | 2026-08-18 02:59 | OK (76 trouvés) | |
| 66 | 66 | | `autoquebectm` | AutoQuébec Occasion Thetford Mines | Thetford Mines | Chaudière-Appalaches | 35 | 0 | 2026-08-18 03:01 | OK (35 trouvés) | |
| 67 | 67 | | `autosevasion` | Autos Évasion | Sherbrooke | Estrie | 51 | 0 | 2026-08-18 02:46 | OK (50 trouvés) | |
| 68 | 68 | | `autosoccasionld` | Autos Occasion LD | Lévis | Chaudière-Appalaches | 158 | 0 | 2026-08-18 02:53 | OK (158 trouvés) | |
| 69 | 69 | | `autosthubert` | Auto St-Hubert | Saint-Hubert | Montérégie | 143 | 0 | 2026-08-18 03:22 | OK (138 trouvés) | |
| 70 | −| `autovilleneuve` | Automobile Villeneuve Amqui | Amqui | Bas-Saint-Laurent | 5 | 0 | 2026-08-17 23:35 | OK (5 trouvés) | | |
| 71 | −| `berniercrepeau` | Bernier Crépeau | Trois-Rivières | Mauricie | 31 | 0 | 2026-08-17 23:34 | OK (31 trouvés) | | |
| 72 | −| `charlesbourgtoyota` | Charlesbourg Toyota | Québec | Capitale-Nationale | 97 | 0 | 2026-08-17 23:35 | OK (97 trouvés) | | |
| 73 | −| `charlevoixtoyota` | Charlevoix Toyota | La Malbaie | Capitale-Nationale | 50 | 0 | 2026-08-17 23:35 | OK (50 trouvés) | | |
| 74 | −| `chomedeytoyota` | Chomedey Toyota Laval | Laval | Laval | 59 | 0 | 2026-08-17 23:34 | OK (59 trouvés) | | |
| 75 | −| `drummondvilleford` | Drummondville Ford | Drummondville | Centre-du-Québec | 52 | 0 | 2026-08-17 23:34 | OK (52 trouvés) | | |
| 76 | −| `drummondvillevw` | Drummondville Volkswagen | Drummondville | Centre-du-Québec | 80 | 0 | 2026-08-17 23:34 | OK (80 trouvés) | | |
| 77 | −| `dubechrysler` | Dubé Chrysler | Rivière-du-Loup | Bas-Saint-Laurent | 162 | 0 | 2026-08-17 23:34 | OK (162 trouvés) | | |
| 78 | −| `ducloslongueuil` | Duclos Longueuil Chrysler | Longueuil | Montérégie | 80 | 0 | 2026-08-17 23:34 | OK (80 trouvés) | | |
| 79 | −| `dupontautomobile` | Dupont Automobile | Alma | Saguenay–Lac-Saint-Jean | 56 | 0 | 2026-08-17 23:35 | OK (56 trouvés) | | |
| 80 | −| `dynastiehyundai` | Dynastie Hyundai | Rouyn-Noranda | Abitibi-Témiscamingue | 34 | 33 | 2026-08-17 23:34 | OK (34 trouvés) | | |
| 70 | +| `autovilleneuve` | Automobile Villeneuve Amqui | Amqui | Bas-Saint-Laurent | 5 | 0 | 2026-08-18 05:33 | OK (5 trouvés) | | |
| 71 | +| `berniercrepeau` | Bernier Crépeau | Trois-Rivières | Mauricie | 31 | 0 | 2026-08-18 03:27 | OK (29 trouvés) | | |
| 72 | +| `charlesbourgtoyota` | Charlesbourg Toyota | Québec | Capitale-Nationale | 100 | 0 | 2026-08-18 05:25 | OK (99 trouvés) | | |
| 73 | +| `charlevoixtoyota` | Charlevoix Toyota | La Malbaie | Capitale-Nationale | 53 | 0 | 2026-08-18 05:33 | OK (53 trouvés) | | |
| 74 | +| `chomedeytoyota` | Chomedey Toyota Laval | Laval | Laval | 68 | 0 | 2026-08-18 03:30 | OK (68 trouvés) | | |
| 75 | +| `drummondvilleford` | Drummondville Ford | Drummondville | Centre-du-Québec | 55 | 0 | 2026-08-18 03:32 | OK (54 trouvés) | | |
| 76 | +| `drummondvillevw` | Drummondville Volkswagen | Drummondville | Centre-du-Québec | 95 | 0 | 2026-08-18 03:36 | OK (94 trouvés) | | |
| 77 | +| `dubechrysler` | Dubé Chrysler | Rivière-du-Loup | Bas-Saint-Laurent | 165 | 0 | 2026-08-18 03:43 | OK (164 trouvés) | | |
| 78 | +| `ducloslongueuil` | Duclos Longueuil Chrysler | Longueuil | Montérégie | 82 | 0 | 2026-08-18 03:47 | OK (81 trouvés) | | |
| 79 | +| `dupontautomobile` | Dupont Automobile | Alma | Saguenay–Lac-Saint-Jean | 56 | 0 | 2026-08-18 05:12 | OK (56 trouvés) | | |
| 80 | +| `dynastiehyundai` | Dynastie Hyundai | Rouyn-Noranda | Abitibi-Témiscamingue | 34 | 33 | 2026-08-18 03:48 | OK (34 trouvés) | | |
| 81 | 81 | | `equipeformule` | Équipe Formule | Rimouski | Bas-Saint-Laurent | 71 | 0 | 2026-08-18 02:44 | OK (69 trouvés) | |
| 82 | −| `fichaultkia` | Fichault Kia | Châteauguay | Montérégie | 35 | 0 | 2026-08-17 23:34 | OK (35 trouvés) | | |
| 83 | −| `forceoccasion` | Force Occasion | Québec | Capitale-Nationale | 505 | 0 | 2026-08-18 02:20 | OK (490 trouvés) | | |
| 84 | −| `fordthetford` | Ford Thetford | Thetford Mines | Chaudière-Appalaches | 33 | 0 | 2026-08-17 23:34 | OK (33 trouvés) | | |
| 85 | −| `gaspesiehyundai` | Gaspésie Hyundai | Bonaventure | Gaspésie–Îles-de-la-Madeleine | 33 | 30 | 2026-08-17 23:34 | OK (33 trouvés) | | |
| 86 | −| `gaspetoyota` | Gaspé Toyota | Gaspé | Gaspésie–Îles-de-la-Madeleine | 12 | 0 | 2026-08-17 23:34 | OK (12 trouvés) | | |
| 82 | +| `fichaultkia` | Fichault Kia | Châteauguay | Montérégie | 37 | 0 | 2026-08-18 03:50 | OK (37 trouvés) | | |
| 83 | +| `forceoccasion` | Force Occasion | Québec | Capitale-Nationale | 505 | 1 | 2026-08-18 02:20 | OK (490 trouvés) | | |
| 84 | +| `fordthetford` | Ford Thetford | Thetford Mines | Chaudière-Appalaches | 33 | 0 | 2026-08-18 03:51 | OK (33 trouvés) | | |
| 85 | +| `gaspesiehyundai` | Gaspésie Hyundai | Bonaventure | Gaspésie–Îles-de-la-Madeleine | 35 | 31 | 2026-08-18 03:53 | OK (34 trouvés) | | |
| 86 | +| `gaspetoyota` | Gaspé Toyota | Gaspé | Gaspésie–Îles-de-la-Madeleine | 12 | 0 | 2026-08-18 03:51 | OK (12 trouvés) | | |
| 87 | 87 | | `gervaisauto` | Gervais Auto | Shawinigan | Mauricie | 559 | 0 | 2026-08-18 02:41 | OK (548 trouvés) | |
| 88 | 88 | | `girardautos` | Girard Autos | Trois-Rivières | Mauricie | 20 | 0 | 2026-08-18 02:17 | OK (20 trouvés) | |
| 89 | −| `granbytoyota` | Granby Toyota | Granby | Montérégie | 20 | 0 | 2026-08-17 23:34 | OK (20 trouvés) | | |
| 90 | −| `grandportage` | Groupe Grand-Portage | Rivière-du-Loup | Bas-Saint-Laurent | 112 | 0 | 2026-08-17 23:34 | OK (112 trouvés) | | |
| 91 | −| `groupemotion` | Groupe Motion | Longueuil | Montérégie | 69 | 0 | 2026-08-17 23:34 | OK (69 trouvés) | | |
| 92 | −| `groupepaquin` | Groupe Paquin | Rouyn-Noranda | Abitibi-Témiscamingue | 50 | 0 | 2026-08-17 23:34 | OK (50 trouvés) | | |
| 93 | −| `hondacharlesbourg` | Honda Charlesbourg | Québec | Capitale-Nationale | 14 | 0 | 2026-08-17 23:35 | OK (14 trouvés) | | |
| 94 | −| `hondacharlevoix` | Honda Charlevoix | La Malbaie | Capitale-Nationale | 35 | 0 | 2026-08-17 23:35 | OK (35 trouvés) | | |
| 95 | −| `hondanewrichmond` | Honda New Richmond | New Richmond | Gaspésie–Îles-de-la-Madeleine | 60 | 0 | 2026-08-17 23:35 | OK (60 trouvés) | | |
| 96 | −| `hullhyundai` | Hull Hyundai | Gatineau | Outaouais | 97 | 0 | 2026-08-17 23:34 | OK (97 trouvés) | | |
| 97 | −| `hyundaibeauce` | Hyundai Beauce | Saint-Georges | Chaudière-Appalaches | 31 | 0 | 2026-08-17 23:35 | OK (31 trouvés) | | |
| 98 | −| `hyundaicharlevoix` | Hyundai Charlevoix | Baie-Saint-Paul | Capitale-Nationale | 22 | 0 | 2026-08-17 23:35 | OK (22 trouvés) | | |
| 99 | −| `hyundaidrummondville` | Hyundai Drummondville | Drummondville | Centre-du-Québec | 89 | 0 | 2026-08-17 23:35 | OK (89 trouvés) | | |
| 100 | −| `hyundailanaudiere` | Hyundai Lanaudière | Joliette | Lanaudière | 60 | 0 | 2026-08-17 23:35 | OK (60 trouvés) | | |
| 101 | −| `hyundailongueuil` | Hyundai Longueuil | Longueuil | Montérégie | 16 | 0 | 2026-08-17 23:35 | OK (16 trouvés) | | |
| 102 | −| `hyundaistjerome` | Hyundai St-Jérôme | Mirabel | Laurentides | 115 | 0 | 2026-08-17 23:35 | OK (115 trouvés) | | |
| 103 | −| `jeandumas` | Jean Dumas Multiconcessionnaire | Alma | Saguenay–Lac-Saint-Jean | 105 | 105 | 2026-08-17 23:35 | OK (105 trouvés) | | |
| 104 | −| `jeandumasfordbc` | Jean Dumas Ford Baie-Comeau | Baie-Comeau | Côte-Nord | 105 | 0 | 2026-08-17 23:35 | OK (105 trouvés) | | |
| 105 | −| `jeandumashondabc` | Jean Dumas Honda Baie-Comeau | Baie-Comeau | Côte-Nord | 105 | 105 | 2026-08-17 23:35 | OK (105 trouvés) | | |
| 106 | −| `joliettemitsubishi` | Joliette Mitsubishi | Joliette | Lanaudière | 68 | 0 | 2026-08-17 23:35 | OK (68 trouvés) | | |
| 107 | −| `kiabeauport` | Kia Beauport | Québec | Capitale-Nationale | 72 | 0 | 2026-08-17 23:35 | OK (72 trouvés) | | |
| 108 | −| `kiajoliette` | Kia Joliette | Joliette | Lanaudière | 58 | 0 | 2026-08-17 23:35 | OK (58 trouvés) | | |
| 109 | −| `lamihonda` | L'Ami Honda | Rouyn-Noranda | Abitibi-Témiscamingue | 91 | 0 | 2026-08-17 23:35 | OK (91 trouvés) | | |
| 110 | −| `leoautomobile` | Léo Automobile | Chicoutimi | Saguenay–Lac-Saint-Jean | 62 | 0 | 2026-08-17 23:35 | OK (62 trouvés) | | |
| 89 | +| `granbytoyota` | Granby Toyota | Granby | Montérégie | 21 | 0 | 2026-08-18 03:54 | OK (20 trouvés) | | |
| 90 | +| `grandportage` | Groupe Grand-Portage | Rivière-du-Loup | Bas-Saint-Laurent | 114 | 0 | 2026-08-18 03:58 | OK (108 trouvés) | | |
| 91 | +| `groupemotion` | Groupe Motion | Longueuil | Montérégie | 73 | 0 | 2026-08-18 04:01 | OK (71 trouvés) | | |
| 92 | +| `groupepaquin` | Groupe Paquin | Rouyn-Noranda | Abitibi-Témiscamingue | 50 | 0 | 2026-08-18 04:03 | OK (50 trouvés) | | |
| 93 | +| `hondacharlesbourg` | Honda Charlesbourg | Québec | Capitale-Nationale | 15 | 0 | 2026-08-18 05:25 | OK (14 trouvés) | | |
| 94 | +| `hondacharlevoix` | Honda Charlevoix | La Malbaie | Capitale-Nationale | 36 | 0 | 2026-08-18 05:30 | OK (36 trouvés) | | |
| 95 | +| `hondanewrichmond` | Honda New Richmond | New Richmond | Gaspésie–Îles-de-la-Madeleine | 60 | 0 | 2026-08-18 05:16 | OK (60 trouvés) | | |
| 96 | +| `hullhyundai` | Hull Hyundai | Gatineau | Outaouais | 102 | 0 | 2026-08-18 04:07 | OK (98 trouvés) | | |
| 97 | +| `hyundaibeauce` | Hyundai Beauce | Saint-Georges | Chaudière-Appalaches | 32 | 0 | 2026-08-18 04:08 | OK (31 trouvés) | | |
| 98 | +| `hyundaicharlevoix` | Hyundai Charlevoix | Baie-Saint-Paul | Capitale-Nationale | 22 | 0 | 2026-08-18 05:31 | OK (21 trouvés) | | |
| 99 | +| `hyundaidrummondville` | Hyundai Drummondville | Drummondville | Centre-du-Québec | 92 | 0 | 2026-08-18 04:12 | OK (88 trouvés) | | |
| 100 | +| `hyundailanaudiere` | Hyundai Lanaudière | Joliette | Lanaudière | 60 | 0 | 2026-08-18 04:15 | OK (60 trouvés) | | |
| 101 | +| `hyundailongueuil` | Hyundai Longueuil | Longueuil | Montérégie | 17 | 0 | 2026-08-18 04:15 | OK (17 trouvés) | | |
| 102 | +| `hyundaistjerome` | Hyundai St-Jérôme | Mirabel | Laurentides | 122 | 0 | 2026-08-18 04:20 | OK (114 trouvés) | | |
| 103 | +| `jeandumas` | Jean Dumas Multiconcessionnaire | Alma | Saguenay–Lac-Saint-Jean | 108 | 108 | 2026-08-18 04:24 | OK (100 trouvés) | | |
| 104 | +| `jeandumasfordbc` | Jean Dumas Ford Baie-Comeau | Baie-Comeau | Côte-Nord | 108 | 76 | 2026-08-18 04:28 | OK (100 trouvés) | | |
| 105 | +| `jeandumashondabc` | Jean Dumas Honda Baie-Comeau | Baie-Comeau | Côte-Nord | 108 | 32 | 2026-08-18 04:33 | OK (100 trouvés) | | |
| 106 | +| `joliettemitsubishi` | Joliette Mitsubishi | Joliette | Lanaudière | 72 | 0 | 2026-08-18 04:35 | OK (72 trouvés) | | |
| 107 | +| `kiabeauport` | Kia Beauport | Québec | Capitale-Nationale | 72 | 0 | 2026-08-18 05:28 | OK (72 trouvés) | | |
| 108 | +| `kiajoliette` | Kia Joliette | Joliette | Lanaudière | 60 | 0 | 2026-08-18 04:38 | OK (54 trouvés) | | |
| 109 | +| `lamihonda` | L'Ami Honda | Rouyn-Noranda | Abitibi-Témiscamingue | 92 | 0 | 2026-08-18 04:42 | OK (90 trouvés) | | |
| 110 | +| `leoautomobile` | Léo Automobile | Chicoutimi | Saguenay–Lac-Saint-Jean | 67 | 0 | 2026-08-18 04:44 | OK (64 trouvés) | | |
| 111 | 111 | | `levisautos` | Lévis Autos | Lévis | Chaudière-Appalaches | 91 | 0 | 2026-08-18 02:56 | OK (83 trouvés) | |
| 112 | −| `levistoyota` | Lévis Toyota | Lévis | Chaudière-Appalaches | 45 | 0 | 2026-08-17 23:35 | OK (45 trouvés) | | |
| 113 | −| `longueuilhonda` | Longueuil Honda | Longueuil | Montérégie | 44 | 0 | 2026-08-17 23:35 | OK (44 trouvés) | | |
| 112 | +| `levistoyota` | Lévis Toyota | Lévis | Chaudière-Appalaches | 45 | 0 | 2026-08-18 04:46 | OK (45 trouvés) | | |
| 113 | +| `longueuilhonda` | Longueuil Honda | Longueuil | Montérégie | 49 | 0 | 2026-08-18 04:48 | OK (49 trouvés) | | |
| 114 | 114 | | `mferlandauto` | M. Ferland Auto | Sherbrooke | Estrie | 14 | 0 | 2026-08-18 02:47 | OK (13 trouvés) | |
| 115 | 115 | | `nicoloccasion` | Nicol Occasion | La Sarre | Abitibi-Témiscamingue | 57 | 0 | 2026-08-18 03:07 | OK (57 trouvés) | |
| 116 | −| `olivierchryslerbc` | Olivier Chrysler Baie-Comeau | Baie-Comeau | Côte-Nord | 39 | 0 | 2026-08-17 23:35 | OK (39 trouvés) | | |
| 117 | −| `olivierhyundaibc` | Olivier Hyundai Baie-Comeau | Baie-Comeau | Côte-Nord | 20 | 0 | 2026-08-17 23:35 | OK (20 trouvés) | | |
| 118 | −| `olivieroccasionseptiles` | Olivier Occasion Sept-Îles | Sept-Îles | Côte-Nord | 88 | 0 | 2026-08-17 23:35 | OK (88 trouvés) | | |
| 116 | +| `olivierchryslerbc` | Olivier Chrysler Baie-Comeau | Baie-Comeau | Côte-Nord | 40 | 0 | 2026-08-18 04:50 | OK (37 trouvés) | | |
| 117 | +| `olivierhyundaibc` | Olivier Hyundai Baie-Comeau | Baie-Comeau | Côte-Nord | 20 | 0 | 2026-08-18 05:17 | OK (20 trouvés) | | |
| 118 | +| `olivieroccasionseptiles` | Olivier Occasion Sept-Îles | Sept-Îles | Côte-Nord | 89 | 0 | 2026-08-18 05:21 | OK (86 trouvés) | | |
| 119 | 119 | | `poirieroccasion` | Poirier Occasion | Val-d'Or | Abitibi-Témiscamingue | 231 | 0 | 2026-08-18 03:16 | OK (230 trouvés) | |
| 120 | −| `sherbrookeautooccasion` | Sherbrooke Auto Occasion | Sherbrooke | Estrie | 91 | 0 | 2026-08-17 23:34 | OK (91 trouvés) | | |
| 121 | −| `stgeorgesgm` | St-Georges Chevrolet Buick GMC | Saint-Georges | Chaudière-Appalaches | 29 | 0 | 2026-08-17 23:35 | OK (29 trouvés) | | |
| 122 | −| `stjeromemitsubishi` | St-Jérôme Mitsubishi | Saint-Jérôme | Laurentides | 129 | 0 | 2026-08-17 23:35 | OK (129 trouvés) | | |
| 123 | −| `subaruvictoriaville` | Subaru Victoriaville | Victoriaville | Centre-du-Québec | 43 | 0 | 2026-08-17 23:35 | OK (43 trouvés) | | |
| 124 | −| `toyotabdc` | Toyota Baie des Chaleurs | Caplan | Gaspésie–Îles-de-la-Madeleine | 39 | 3 | 2026-08-17 23:35 | OK (39 trouvés) | | |
| 125 | −| `troisriviereshonda` | Trois-Rivières Honda | Trois-Rivières | Mauricie | 76 | 0 | 2026-08-17 23:35 | OK (76 trouvés) | | |
| 126 | −| `trtoyota` | Trois-Rivières Toyota | Trois-Rivières | Mauricie | 58 | 0 | 2026-08-17 23:35 | OK (58 trouvés) | | |
| 127 | −| `vaudreuilvw` | Vaudreuil Volkswagen | Vaudreuil-Dorion | Montérégie | 40 | 0 | 2026-08-17 23:35 | OK (40 trouvés) | | |
| 128 | −| `vwsherbrooke` | VW Sherbrooke | Sherbrooke | Estrie | 70 | 0 | 2026-08-17 23:35 | OK (70 trouvés) | | |
| 120 | +| `sherbrookeautooccasion` | Sherbrooke Auto Occasion | Sherbrooke | Estrie | 91 | 0 | 2026-08-18 03:26 | OK (90 trouvés) | | |
| 121 | +| `stgeorgesgm` | St-Georges Chevrolet Buick GMC | Saint-Georges | Chaudière-Appalaches | 32 | 0 | 2026-08-18 04:51 | OK (32 trouvés) | | |
| 122 | +| `stjeromemitsubishi` | St-Jérôme Mitsubishi | Saint-Jérôme | Laurentides | 136 | 0 | 2026-08-18 04:56 | OK (129 trouvés) | | |
| 123 | +| `subaruvictoriaville` | Subaru Victoriaville | Victoriaville | Centre-du-Québec | 44 | 0 | 2026-08-18 04:58 | OK (42 trouvés) | | |
| 124 | +| `toyotabdc` | Toyota Baie des Chaleurs | Caplan | Gaspésie–Îles-de-la-Madeleine | 41 | 4 | 2026-08-18 05:00 | OK (35 trouvés) | | |
| 125 | +| `troisriviereshonda` | Trois-Rivières Honda | Trois-Rivières | Mauricie | 80 | 0 | 2026-08-18 05:03 | OK (74 trouvés) | | |
| 126 | +| `trtoyota` | Trois-Rivières Toyota | Trois-Rivières | Mauricie | 61 | 0 | 2026-08-18 05:05 | OK (57 trouvés) | | |
| 127 | +| `vaudreuilvw` | Vaudreuil Volkswagen | Vaudreuil-Dorion | Montérégie | 42 | 0 | 2026-08-18 05:07 | OK (42 trouvés) | | |
| 128 | +| `vwsherbrooke` | VW Sherbrooke | Sherbrooke | Estrie | 70 | 0 | 2026-08-18 05:10 | OK (70 trouvés) | | |
| 129 | 129 | |
| 130 | 130 | ## Erreurs de synchronisation récentes (sync_log, ok = 0) |
| 131 | 131 | |
modified
docs/connecteurs/desmeules.md
+4 −4
@@ -6,7 +6,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 6 | 6 | |
| 7 | 7 | - **Fichier** : `autoka/connectors/desmeules.py` |
| 8 | 8 | - **Sources membres** : 1 |
| 9 | −- **Annonces actives (BD)** : 243 | |
| 9 | +- **Annonces actives (BD)** : 244 | |
| 10 | 10 | - **Backend de fetch** : requests direct (aucun anti-bot) |
| 11 | 11 | - **Mécanique détectée** : flux RSS, microdata schema.org |
| 12 | 12 | - **Plafonds / cache** : AUTOKA_MAX_DETAILS, cache détail BD (detail_cache) |
@@ -35,7 +35,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 35 | 35 | > changent pas ; le prix frais vient du RSS). AUTOKA_MAX_DETAILS (env) |
| 36 | 36 | > plafonne les vraies requêtes détail par synchronisation. |
| 37 | 37 | |
| 38 | −## Complétude des champs (annonces actives, N = 243) | |
| 38 | +## Complétude des champs (annonces actives, N = 244) | |
| 39 | 39 | |
| 40 | 40 | | Champ | % rempli | |
| 41 | 41 | |---|---| |
@@ -44,7 +44,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 44 | 44 | | Année | 100.0 % | |
| 45 | 45 | | Kilométrage | 100.0 % | |
| 46 | 46 | | Prix | 98.4 % | |
| 47 | −| Carburant | 83.1 % | | |
| 47 | +| Carburant | 83.2 % | | |
| 48 | 48 | | Boîte (transmission) | 98.4 % | |
| 49 | 49 | | VIN | 99.6 % | |
| 50 | 50 | | Photos | 100.0 % | |
@@ -55,7 +55,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 55 | 55 | |
| 56 | 56 | | Source | Nom | Ville | Région | Actives | Masquées (dup VIN) | Dernier sync | Statut | |
| 57 | 57 | |---|---|---|---|---|---|---|---| |
| 58 | −| `desmeules` | Desmeules Chrysler | Laval | Laval | 243 | 0 | 2026-08-17 23:35 | OK (243 trouvés) | | |
| 58 | +| `desmeules` | Desmeules Chrysler | Laval | Laval | 244 | 0 | 2026-08-18 05:33 | OK (235 trouvés) | | |
| 59 | 59 | |
| 60 | 60 | ## Erreurs de synchronisation récentes |
| 61 | 61 | |
modified
docs/connecteurs/dupontford.md
+4 −4
@@ -6,7 +6,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 6 | 6 | |
| 7 | 7 | - **Fichier** : `autoka/connectors/dupontford.py` |
| 8 | 8 | - **Sources membres** : 1 |
| 9 | −- **Annonces actives (BD)** : 65 | |
| 9 | +- **Annonces actives (BD)** : 66 | |
| 10 | 10 | - **Backend de fetch** : requests direct (aucun anti-bot) |
| 11 | 11 | - **Mécanique détectée** : sitemap, endpoint JSON |
| 12 | 12 | - **Plafonds / cache** : cache détail BD (detail_cache) |
@@ -28,7 +28,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 28 | 28 | > URL détail reconstruite comme dans le sitemap inventory.xml : |
| 29 | 29 | > /vue/used-<année>-<marque>-<modèle>-<id>/. |
| 30 | 30 | |
| 31 | −## Complétude des champs (annonces actives, N = 65) | |
| 31 | +## Complétude des champs (annonces actives, N = 66) | |
| 32 | 32 | |
| 33 | 33 | | Champ | % rempli | |
| 34 | 34 | |---|---| |
@@ -40,7 +40,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 40 | 40 | | Carburant | 100.0 % | |
| 41 | 41 | | Boîte (transmission) | 100.0 % | |
| 42 | 42 | | VIN | 100.0 % | |
| 43 | −| Photos | 92.3 % | | |
| 43 | +| Photos | 92.4 % | | |
| 44 | 44 | | Carfax | 0.0 % | |
| 45 | 45 | | GPS (lat/lng) | 100.0 % | |
| 46 | 46 | |
@@ -48,7 +48,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 48 | 48 | |
| 49 | 49 | | Source | Nom | Ville | Région | Actives | Masquées (dup VIN) | Dernier sync | Statut | |
| 50 | 50 | |---|---|---|---|---|---|---|---| |
| 51 | −| `dupontdupontford` | Dupont & Dupont Ford | Gatineau | Outaouais | 65 | 0 | 2026-08-17 23:35 | OK (65 trouvés) | | |
| 51 | +| `dupontdupontford` | Dupont & Dupont Ford | Gatineau | Outaouais | 66 | 0 | 2026-08-18 05:33 | OK (65 trouvés) | | |
| 52 | 52 | |
| 53 | 53 | ## Erreurs de synchronisation récentes |
| 54 | 54 | |
modified
docs/connecteurs/gatsby_dealers.md
+9 −9
@@ -6,7 +6,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 6 | 6 | |
| 7 | 7 | - **Fichier** : `autoka/connectors/gatsby_dealers.py` |
| 8 | 8 | - **Sources membres** : 6 |
| 9 | −- **Annonces actives (BD)** : 191 | |
| 9 | +- **Annonces actives (BD)** : 198 | |
| 10 | 10 | - **Backend de fetch** : requests direct (aucun anti-bot) |
| 11 | 11 | - **Mécanique détectée** : sitemap, GraphQL, AWS AppSync, page-data Gatsby, endpoint JSON |
| 12 | 12 | - **Plafonds / cache** : AUTOKA_MAX_DETAILS, AUTOKA_MAX_PAGES |
@@ -34,7 +34,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 34 | 34 | > usedCar -> Vehicle) est hérité de MegaCentreDeLiquidation. |
| 35 | 35 | > AUTOKA_MAX_PAGES (env) limite la pagination AppSync pour les tests. |
| 36 | 36 | |
| 37 | −## Complétude des champs (annonces actives, N = 191) | |
| 37 | +## Complétude des champs (annonces actives, N = 198) | |
| 38 | 38 | |
| 39 | 39 | | Champ | % rempli | |
| 40 | 40 | |---|---| |
@@ -47,19 +47,19 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 47 | 47 | | Boîte (transmission) | 100.0 % | |
| 48 | 48 | | VIN | 100.0 % | |
| 49 | 49 | | Photos | 100.0 % | |
| 50 | −| Carfax | 51.3 % | | |
| 50 | +| Carfax | 52.0 % | | |
| 51 | 51 | | GPS (lat/lng) | 100.0 % | |
| 52 | 52 | |
| 53 | 53 | ## Sources membres (BD live) |
| 54 | 54 | |
| 55 | 55 | | Source | Nom | Ville | Région | Actives | Masquées (dup VIN) | Dernier sync | Statut | |
| 56 | 56 | |---|---|---|---|---|---|---|---| |
| 57 | −| `kiadrummondville` | Kia Drummondville | Drummondville | Centre-du-Québec | 22 | 0 | 2026-08-17 23:35 | OK (22 trouvés) | | |
| 58 | −| `kiagranby` | Kia de Granby | Granby | Montérégie | 19 | 0 | 2026-08-17 23:35 | OK (19 trouvés) | | |
| 59 | −| `mazdadesherbrooke` | Mazda de Sherbrooke | Sherbrooke | Estrie | 49 | 0 | 2026-08-17 23:35 | OK (49 trouvés) | | |
| 60 | −| `mitsubishidrummondville` | Mitsubishi Drummondville | Drummondville | Centre-du-Québec | 14 | 0 | 2026-08-17 23:35 | OK (14 trouvés) | | |
| 61 | −| `nissandrummondville` | Nissan Drummondville | Drummondville | Centre-du-Québec | 44 | 0 | 2026-08-17 23:35 | OK (44 trouvés) | | |
| 62 | −| `nissanvictoriaville` | Nissan de Victoriaville | Victoriaville | Centre-du-Québec | 43 | 0 | 2026-08-17 23:35 | OK (43 trouvés) | | |
| 57 | +| `kiadrummondville` | Kia Drummondville | Drummondville | Centre-du-Québec | 22 | 0 | 2026-08-18 05:33 | OK (22 trouvés) | | |
| 58 | +| `kiagranby` | Kia de Granby | Granby | Montérégie | 19 | 0 | 2026-08-18 05:33 | OK (19 trouvés) | | |
| 59 | +| `mazdadesherbrooke` | Mazda de Sherbrooke | Sherbrooke | Estrie | 54 | 0 | 2026-08-18 05:33 | OK (54 trouvés) | | |
| 60 | +| `mitsubishidrummondville` | Mitsubishi Drummondville | Drummondville | Centre-du-Québec | 14 | 0 | 2026-08-18 05:33 | OK (14 trouvés) | | |
| 61 | +| `nissandrummondville` | Nissan Drummondville | Drummondville | Centre-du-Québec | 44 | 0 | 2026-08-18 05:33 | OK (44 trouvés) | | |
| 62 | +| `nissanvictoriaville` | Nissan de Victoriaville | Victoriaville | Centre-du-Québec | 45 | 0 | 2026-08-18 05:33 | OK (45 trouvés) | | |
| 63 | 63 | |
| 64 | 64 | ## Erreurs de synchronisation récentes |
| 65 | 65 | |
modified
docs/connecteurs/hgregoire.md
+1 −1
@@ -65,7 +65,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 65 | 65 | |
| 66 | 66 | | Source | Nom | Ville | Région | Actives | Masquées (dup VIN) | Dernier sync | Statut | |
| 67 | 67 | |---|---|---|---|---|---|---|---| |
| 68 | −| `hgregoire` | HGrégoire | St-Léonard | Montréal | 1672 | 2 | 2026-08-17 23:48 | OK (1672 trouvés) | | |
| 68 | +| `hgregoire` | HGrégoire | St-Léonard | Montréal | 1672 | 2 | 2026-08-18 05:46 | OK (1672 trouvés) | | |
| 69 | 69 | |
| 70 | 70 | ## Erreurs de synchronisation récentes |
| 71 | 71 | |
modified
docs/connecteurs/jlkauto.md
+1 −1
@@ -43,7 +43,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 43 | 43 | |
| 44 | 44 | | Source | Nom | Ville | Région | Actives | Masquées (dup VIN) | Dernier sync | Statut | |
| 45 | 45 | |---|---|---|---|---|---|---|---| |
| 46 | −| `jlkauto` | JLK Auto | Montréal | Montréal | 68 | 0 | 2026-08-17 23:48 | OK (68 trouvés) | | |
| 46 | +| `jlkauto` | JLK Auto | Montréal | Montréal | 68 | 0 | 2026-08-18 05:46 | OK (68 trouvés) | | |
| 47 | 47 | |
| 48 | 48 | ## Erreurs de synchronisation récentes |
| 49 | 49 | |
modified
docs/connecteurs/kijiji.md
+3 −3
@@ -6,7 +6,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 6 | 6 | |
| 7 | 7 | - **Fichier** : `autoka/connectors/kijiji.py` |
| 8 | 8 | - **Sources membres** : 1 |
| 9 | −- **Annonces actives (BD)** : 3002 | |
| 9 | +- **Annonces actives (BD)** : 3005 | |
| 10 | 10 | - **Backend de fetch** : requests direct (aucun anti-bot) |
| 11 | 11 | - **Mécanique détectée** : __NEXT_DATA__ |
| 12 | 12 | - **Plafonds / cache** : aucun |
@@ -34,7 +34,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 34 | 34 | > Pagination : /b-autos-camions/quebec/page-N/c174l9001?for-sale-by=ownr |
| 35 | 35 | > jusqu'à totalCount (bornée par MAX_PAGES par politesse). |
| 36 | 36 | |
| 37 | −## Complétude des champs (annonces actives, N = 3 002) | |
| 37 | +## Complétude des champs (annonces actives, N = 3 005) | |
| 38 | 38 | |
| 39 | 39 | | Champ | % rempli | |
| 40 | 40 | |---|---| |
@@ -54,7 +54,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 54 | 54 | |
| 55 | 55 | | Source | Nom | Ville | Région | Actives | Masquées (dup VIN) | Dernier sync | Statut | |
| 56 | 56 | |---|---|---|---|---|---|---|---| |
| 57 | −| `kijiji` | Kijiji (particuliers) | — | — | 3002 | 10 | 2026-08-18 02:09 | OK (3002 trouvés) | | |
| 57 | +| `kijiji` | Kijiji (particuliers) | — | — | 3005 | 14 | 2026-08-18 05:49 | OK (3001 trouvés) | | |
| 58 | 58 | |
| 59 | 59 | ## Erreurs de synchronisation récentes |
| 60 | 60 | |
modified
docs/connecteurs/leprixdugros.md
+5 −5
@@ -6,7 +6,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 6 | 6 | |
| 7 | 7 | - **Fichier** : `autoka/connectors/leprixdugros.py` |
| 8 | 8 | - **Sources membres** : 1 |
| 9 | −- **Annonces actives (BD)** : 886 | |
| 9 | +- **Annonces actives (BD)** : 973 | |
| 10 | 10 | - **Backend de fetch** : Scrapfly |
| 11 | 11 | - **Mécanique détectée** : endpoint JSON |
| 12 | 12 | - **Plafonds / cache** : AUTOKA_MAX_DETAILS, AUTOKA_MAX_PAGES, cache détail BD (detail_cache) |
@@ -42,7 +42,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 42 | 42 | > (hybride/EV/diesel), un moteur « N cylindres X.XL » sans indice électrifié |
| 43 | 43 | > => Essence (sauf modèles hybrides « sans badge » connus, ex. Prius). |
| 44 | 44 | |
| 45 | −## Complétude des champs (annonces actives, N = 886) | |
| 45 | +## Complétude des champs (annonces actives, N = 973) | |
| 46 | 46 | |
| 47 | 47 | | Champ | % rempli | |
| 48 | 48 | |---|---| |
@@ -51,18 +51,18 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 51 | 51 | | Année | 100.0 % | |
| 52 | 52 | | Kilométrage | 100.0 % | |
| 53 | 53 | | Prix | 99.9 % | |
| 54 | −| Carburant | 97.4 % | | |
| 54 | +| Carburant | 97.6 % | | |
| 55 | 55 | | Boîte (transmission) | 100.0 % | |
| 56 | 56 | | VIN | 100.0 % | |
| 57 | 57 | | Photos | 100.0 % | |
| 58 | 58 | | Carfax | 0.0 % | |
| 59 | −| GPS (lat/lng) | 42.0 % | | |
| 59 | +| GPS (lat/lng) | 38.4 % | | |
| 60 | 60 | |
| 61 | 61 | ## Sources membres (BD live) |
| 62 | 62 | |
| 63 | 63 | | Source | Nom | Ville | Région | Actives | Masquées (dup VIN) | Dernier sync | Statut | |
| 64 | 64 | |---|---|---|---|---|---|---|---| |
| 65 | −| `leprixdugros` | Le Prix du Gros | Trois-Rivières | Mauricie | 886 | 653 | 2026-08-18 01:44 | OK (107 trouvés) | | |
| 65 | +| `leprixdugros` | Le Prix du Gros | Trois-Rivières | Mauricie | 973 | 738 | 2026-08-18 05:50 | OK (654 trouvés) | | |
| 66 | 66 | |
| 67 | 67 | ## Erreurs de synchronisation récentes (sync_log, ok = 0) |
| 68 | 68 | |
modified
docs/connecteurs/magnetis_dealers.md
+9 −9
@@ -6,7 +6,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 6 | 6 | |
| 7 | 7 | - **Fichier** : `autoka/connectors/magnetis_dealers.py` |
| 8 | 8 | - **Sources membres** : 4 |
| 9 | −- **Annonces actives (BD)** : 423 | |
| 9 | +- **Annonces actives (BD)** : 426 | |
| 10 | 10 | - **Backend de fetch** : requests direct (aucun anti-bot) |
| 11 | 11 | - **Mécanique détectée** : sitemap, JSON-LD schema.org, blob JSON data-class |
| 12 | 12 | - **Plafonds / cache** : AUTOKA_MAX_DETAILS, cache détail BD (detail_cache) |
@@ -32,19 +32,19 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 32 | 32 | > Pages détail en cache BD (clé hebdomadaire) ; AUTOKA_MAX_DETAILS (env) |
| 33 | 33 | > plafonne les vraies requêtes détail par synchronisation. |
| 34 | 34 | |
| 35 | −## Complétude des champs (annonces actives, N = 423) | |
| 35 | +## Complétude des champs (annonces actives, N = 426) | |
| 36 | 36 | |
| 37 | 37 | | Champ | % rempli | |
| 38 | 38 | |---|---| |
| 39 | 39 | | Marque | 100.0 % | |
| 40 | 40 | | Modèle | 100.0 % | |
| 41 | 41 | | Année | 99.8 % | |
| 42 | −| Kilométrage | 98.3 % | | |
| 42 | +| Kilométrage | 98.4 % | | |
| 43 | 43 | | Prix | 100.0 % | |
| 44 | −| Carburant | 94.3 % | | |
| 45 | −| Boîte (transmission) | 88.2 % | | |
| 46 | −| VIN | 2.4 % | | |
| 47 | −| Photos | 2.1 % | | |
| 44 | +| Carburant | 94.6 % | | |
| 45 | +| Boîte (transmission) | 88.7 % | | |
| 46 | +| VIN | 73.2 % | | |
| 47 | +| Photos | 66.4 % | | |
| 48 | 48 | | Carfax | 0.0 % | |
| 49 | 49 | | GPS (lat/lng) | 100.0 % | |
| 50 | 50 | |
@@ -52,9 +52,9 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 52 | 52 | |
| 53 | 53 | | Source | Nom | Ville | Région | Actives | Masquées (dup VIN) | Dernier sync | Statut | |
| 54 | 54 | |---|---|---|---|---|---|---|---| |
| 55 | −| `baiecomeautoyota` | Baie-Comeau Toyota | Baie-Comeau | Côte-Nord | 247 | 0 | 2026-08-18 01:43 | OK (10 trouvés) | | |
| 55 | +| `baiecomeautoyota` | Baie-Comeau Toyota | Baie-Comeau | Côte-Nord | 250 | 0 | 2026-08-18 05:57 | OK (249 trouvés) | | |
| 56 | 56 | | `lussierchevrolet` | Lussier Chevrolet Buick GMC | Saint-Hyacinthe | Montérégie | 83 | 0 | 2026-08-17 23:49 | OK (83 trouvés) | |
| 57 | −| `sherbrooketoyota` | Sherbrooke Toyota | Sherbrooke | Estrie | 63 | 0 | 2026-08-17 23:49 | OK (63 trouvés) | | |
| 57 | +| `sherbrooketoyota` | Sherbrooke Toyota | Sherbrooke | Estrie | 63 | 0 | 2026-08-18 06:00 | OK (63 trouvés) | | |
| 58 | 58 | | `stgnissan` | St-Georges Nissan | Saint-Georges | Chaudière-Appalaches | 30 | 0 | 2026-08-17 23:49 | OK (30 trouvés) | |
| 59 | 59 | |
| 60 | 60 | ## Erreurs de synchronisation récentes |
modified
docs/connecteurs/megacentre.md
+4 −4
@@ -6,7 +6,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 6 | 6 | |
| 7 | 7 | - **Fichier** : `autoka/connectors/megacentre.py` |
| 8 | 8 | - **Sources membres** : 1 |
| 9 | −- **Annonces actives (BD)** : 455 | |
| 9 | +- **Annonces actives (BD)** : 456 | |
| 10 | 10 | - **Backend de fetch** : requests direct (aucun anti-bot) |
| 11 | 11 | - **Mécanique détectée** : sitemap, page-data Gatsby, endpoint JSON |
| 12 | 12 | - **Plafonds / cache** : AUTOKA_MAX_DETAILS, cache détail BD (detail_cache) |
@@ -33,7 +33,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 33 | 33 | > hebdomadaire génèrent de vraies requêtes. AUTOKA_MAX_DETAILS (env) |
| 34 | 34 | > plafonne les vraies requêtes détail par synchronisation. |
| 35 | 35 | |
| 36 | −## Complétude des champs (annonces actives, N = 455) | |
| 36 | +## Complétude des champs (annonces actives, N = 456) | |
| 37 | 37 | |
| 38 | 38 | | Champ | % rempli | |
| 39 | 39 | |---|---| |
@@ -46,14 +46,14 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 46 | 46 | | Boîte (transmission) | 97.4 % | |
| 47 | 47 | | VIN | 100.0 % | |
| 48 | 48 | | Photos | 99.1 % | |
| 49 | −| Carfax | 69.2 % | | |
| 49 | +| Carfax | 69.3 % | | |
| 50 | 50 | | GPS (lat/lng) | 100.0 % | |
| 51 | 51 | |
| 52 | 52 | ## Sources membres (BD live) |
| 53 | 53 | |
| 54 | 54 | | Source | Nom | Ville | Région | Actives | Masquées (dup VIN) | Dernier sync | Statut | |
| 55 | 55 | |---|---|---|---|---|---|---|---| |
| 56 | −| `megacentredeliquidation` | Méga Centre de liquidation | Mont-Joli | Bas-Saint-Laurent | 455 | 451 | 2026-08-17 23:35 | OK (455 trouvés) | | |
| 56 | +| `megacentredeliquidation` | Méga Centre de liquidation | Mont-Joli | Bas-Saint-Laurent | 456 | 452 | 2026-08-18 05:33 | OK (453 trouvés) | | |
| 57 | 57 | |
| 58 | 58 | ## Erreurs de synchronisation récentes |
| 59 | 59 | |
modified
docs/connecteurs/occasionbeaucage.md
+1 −1
@@ -61,7 +61,7 @@ _Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas édite | ||
| 61 | 61 | |
| 62 | 62 | | Source | Nom | Ville | Région | Actives | Masquées (dup VIN) | Dernier sync | Statut | |
| 63 | 63 | |---|---|---|---|---|---|---|---| |
| 64 | −| `occasionbeaucage` | Occasion Beaucage | Sherbrooke | Estrie | 1226 | 192 | 2026-08-17 23:50 | OK (1226 trouvés) | | |
| 64 | +| `occasionbeaucage` | Occasion Beaucage | Sherbrooke | Estrie | 1226 | 199 | 2026-08-17 23:50 | OK (1226 trouvés) | | |
| 65 | 65 | |
| 66 | 66 | ## Erreurs de synchronisation récentes |
| 67 | 67 | |
added
docs/connecteurs/otogo.md
+67 −0
@@ -0,0 +1,67 @@ | ||
| 1 | +# Module `otogo` — Nuxt / API JSON publique /api-search/ | |
| 2 | + | |
| 3 | +_Généré automatiquement par `scripts/gen_connector_docs.py` — ne pas éditer à la main._ | |
| 4 | + | |
| 5 | +## Vue d'ensemble | |
| 6 | + | |
| 7 | +- **Fichier** : `autoka/connectors/otogo.py` | |
| 8 | +- **Sources membres** : 1 | |
| 9 | +- **Annonces actives (BD)** : 23700 | |
| 10 | +- **Backend de fetch** : requests direct (aucun anti-bot) | |
| 11 | +- **Mécanique détectée** : endpoint JSON | |
| 12 | +- **Plafonds / cache** : AUTOKA_OTOGO_MAX | |
| 13 | +- **Classes** : `OtogoQuebec` → `otogo` | |
| 14 | + | |
| 15 | +## Mécanique (en-tête du module) | |
| 16 | + | |
| 17 | +> connectors/otogo.py : PORTAIL — Otogo.ca (LC Media, 100 % Québec) | |
| 18 | +> | |
| 19 | +> Otogo expose une API JSON publique, sans clé ni rendu JS : | |
| 20 | +> GET https://www.otogo.ca/api-search/?latlng=0,0&locale=fr&no_facets= | |
| 21 | +> &rpp=100&page=N&status=Usagé,Certifié,Démo[&price=lo-hi] | |
| 22 | +> Chaque annonce de la liste est déjà riche : titre/version FR, année, | |
| 23 | +> prix, kilométrage, VIN (≈100 %), nom du concessionnaire et surtout le | |
| 24 | +> GPS EXACT du vendeur (latitude/longitude) — parfait pour la recherche | |
| 25 | +> par rayon et la dédup VIN avec les concessionnaires connectés. | |
| 26 | +> (Paramètres découverts dans les bundles Nuxt : constantes c.b du module | |
| 27 | +> 5731047.js — latlng/locale/rpp/page/price/status/no_facets/no_results.) | |
| 28 | +> | |
| 29 | +> Profondeur : l'API plafonne à ~1 000 résultats par requête (rpp=100 → | |
| 30 | +> meta.pages ≤ 10) pour ~24 000 annonces d'occasion. On PARTITIONNE donc | |
| 31 | +> par tranches de prix (bissection récursive + tranche ouverte « lo- ») : | |
| 32 | +> chaque annonce n'est téléchargée qu'une fois, ~250 requêtes directes | |
| 33 | +> par sync complet, zéro crédit d'API de scraping externe. | |
| 34 | +> | |
| 35 | +> Ville : absente de la liste — déduite du GPS exact via nearest_city() | |
| 36 | +> (data/villes_gps.json) pour l'affichage et la région administrative. | |
| 37 | +> Carburant/boîte : absents de la liste ; repli commun finalize() (jetons | |
| 38 | +> du titre/de la version). Page détail : /auto/{slug_fr} (non visitée — | |
| 39 | +> économie ; le VIN et le GPS sont déjà dans la liste). | |
| 40 | +> | |
| 41 | +> Plafond : AUTOKA_OTOGO_MAX (défaut 0 = inventaire complet). | |
| 42 | + | |
| 43 | +## Complétude des champs (annonces actives, N = 23 700) | |
| 44 | + | |
| 45 | +| Champ | % rempli | | |
| 46 | +|---|---| | |
| 47 | +| Marque | 99.9 % | | |
| 48 | +| Modèle | 100.0 % | | |
| 49 | +| Année | 100.0 % | | |
| 50 | +| Kilométrage | 99.3 % | | |
| 51 | +| Prix | 100.0 % | | |
| 52 | +| Carburant | 7.7 % | | |
| 53 | +| Boîte (transmission) | 0.0 % | | |
| 54 | +| VIN | 99.3 % | | |
| 55 | +| Photos | 95.9 % | | |
| 56 | +| Carfax | 0.0 % | | |
| 57 | +| GPS (lat/lng) | 100.0 % | | |
| 58 | + | |
| 59 | +## Sources membres (BD live) | |
| 60 | + | |
| 61 | +| Source | Nom | Ville | Région | Actives | Masquées (dup VIN) | Dernier sync | Statut | | |
| 62 | +|---|---|---|---|---|---|---|---| | |
| 63 | +| `otogo` | Otogo.ca | — | — | 23700 | 5847 | 2026-08-18 05:54 | OK (23700 trouvés) | | |
| 64 | + | |
| 65 | +## Erreurs de synchronisation récentes | |
| 66 | + | |
| 67 | +Aucune erreur dans le sync_log pour les sources de ce module. | |
modified
docs/connecteurs/transverse-dedup-vin.md
+10 −10
@@ -27,9 +27,9 @@ _Généré automatiquement par `scripts/gen_connector_docs.py`._ | ||
| 27 | 27 | |
| 28 | 28 | ## État live |
| 29 | 29 | |
| 30 | −- **Annonces en base** : 23110 | |
| 31 | −- **Annonces masquées (`dup_of` non nul)** : 2315 | |
| 32 | −- **Annonces canoniques avec doublons** : 1729 | |
| 30 | +- **Annonces en base** : 49397 | |
| 31 | +- **Annonces masquées (`dup_of` non nul)** : 8438 | |
| 32 | +- **Annonces canoniques avec doublons** : 6655 | |
| 33 | 33 | - **Autorité** : concessionnaire direct > portail/regroupeur > petites annonces ; puis complétude de la fiche, puis ancienneté (`first_seen`). |
| 34 | 34 | - Les listes filtrent `dup_of IS NULL` ; les fiches détail restent accessibles par uid. |
| 35 | 35 | |
@@ -37,17 +37,17 @@ _Généré automatiquement par `scripts/gen_connector_docs.py`._ | ||
| 37 | 37 | |
| 38 | 38 | | Source | Annonces masquées | |
| 39 | 39 | |---|---| |
| 40 | −| `leprixdugros` | 653 | | |
| 41 | −| `megacentredeliquidation` | 451 | | |
| 40 | +| `otogo` | 5847 | | |
| 41 | +| `leprixdugros` | 738 | | |
| 42 | +| `megacentredeliquidation` | 452 | | |
| 42 | 43 | | `montjolichrysler` | 382 | |
| 43 | −| `occasionbeaucage` | 192 | | |
| 44 | +| `occasionbeaucage` | 199 | | |
| 45 | +| `cargurus` | 170 | | |
| 44 | 46 | | `touteslesmarques` | 169 | |
| 45 | −| `jeandumas` | 105 | | |
| 46 | −| `jeandumashondabc` | 105 | | |
| 47 | +| `jeandumas` | 108 | | |
| 47 | 48 | | `abitibiautocredit` | 85 | |
| 49 | +| `jeandumasfordbc` | 76 | | |
| 48 | 50 | | `baiecomeaugm` | 37 | |
| 49 | 51 | | `stefoychrysler` | 35 | |
| 50 | −| `dynastiehyundai` | 33 | | |
| 51 | −| `gaspesiehyundai` | 30 | | |
| 52 | 52 | |
| 53 | 53 | Recalcul one-shot : `python3 -m autoka.dedup` (idempotent, relancé à la fin de chaque cycle d'ingestion). |
modified
scripts/gen_connector_docs.py
+3 −1
@@ -6,7 +6,7 @@ | ||
| 6 | 6 | # |
| 7 | 7 | # Génère docs/connecteurs/ (INDEX.md + une fiche par MODULE connecteur + |
| 8 | 8 | # fiches transverses) en croisant : |
| 9 | −# 1. data/sources.json — registre des sources (139 sources) ; | |
| 9 | +# 1. data/sources.json — registre des sources (142 sources) ; | |
| 10 | 10 | # 2. le code des connecteurs — en-tête « mécanique » de chaque module, |
| 11 | 11 | # backend de fetch, plafonds env (AUTOKA_*), classes exposées ; |
| 12 | 12 | # 3. la BD live data/autoka.db — volumétrie, complétude par champ, |
@@ -58,6 +58,8 @@ HISTORIQUE = """\ | ||
| 58 | 58 | |---|---| |
| 59 | 59 | | `2a35ac2` | Enrichissement connecteurs : Carfax, détails AED/LPDG, verticale moto, photos Magnetis, GPS concessionnaires | |
| 60 | 60 | | `bbc070b` | Vague 2 : dédup VIN inter-sources, Kijiji particuliers, rappels Transports Canada, coordonnées concessionnaires | |
| 61 | +| `b6d5459` | Documentation standardisée des connecteurs (générateur + fiches) | | |
| 62 | +| Vague 3 | Grands portails : AutoTrader.ca/AutoHebdo (fenêtre récente), Otogo.ca (inventaire complet, VIN+GPS), CarGurus.ca (deal rating, via Scrapfly) | | |
| 61 | 63 | """ |
| 62 | 64 | |
| 63 | 65 | |
| 64 | 66 | |