[ka6] fix connecteur motosillimitees: garde anti faux-zéro dans la plateforme jazzhr — la page liste renvoie parfois 200 sans la structure du jobboard (constaté 2026-09-04 10h09, sync à 0 en 0,2 s alors que 2 offres réelles), le marqueur 'resumator' est maintenant exigé avec retry+backoff sinon erreur explicite dans sync_log; sync rejoué: 2 offres (médiane 2.0), rotation d'offres de l'employeur absorbée
1 changed file +26 −2
modified
jobka/connectors/jazzhr.py
+26 −2
@@ -5,7 +5,7 @@ | ||
| 5 | 5 | # Fichier : jobka/connectors/jazzhr.py |
| 6 | 6 | # Rôle : Classe de plateforme JazzHR (applytojob.com) — liste HTML publique |
| 7 | 7 | # /apply/ + JSON-LD JobPosting des pages détail (lieu, salaire…) |
| 8 | −# Créé : 2026-08-25 Modifié : 2026-08-25 | |
| 8 | +# Créé : 2026-08-25 Modifié : 2026-09-04 | |
| 9 | 9 | # ============================================================================= |
| 10 | 10 | """Plateforme JazzHR (<org>.applytojob.com), répandue chez les PME. |
| 11 | 11 | |
@@ -21,6 +21,7 @@ from __future__ import annotations | ||
| 21 | 21 | |
| 22 | 22 | import os |
| 23 | 23 | import re |
| 24 | +import time | |
| 24 | 25 | |
| 25 | 26 | from ..schema import JobPosting, is_quebec_location |
| 26 | 27 | from . import _jsonld |
@@ -52,8 +53,31 @@ class JazzHRConnector(BaseConnector): | ||
| 52 | 53 | node = _jsonld.extract_jobposting(html) |
| 53 | 54 | return _jsonld.jobposting_fields(node) if node else {} |
| 54 | 55 | |
| 56 | + def _fetch_list(self) -> str: | |
| 57 | + # JazzHR renvoie parfois 200 avec une page sans la structure du | |
| 58 | + # jobboard (constaté le 2026-09-04 sur motosillimitees : sync à | |
| 59 | + # 0 offre en 0,2 s alors que la page réelle en listait 2). Une | |
| 60 | + # vraie page liste porte toujours le marqueur « resumator » | |
| 61 | + # (classe body resumator-jobboard-home), même sans aucune offre : | |
| 62 | + # son absence = page challenge/erreur, pas une liste vide — on | |
| 63 | + # retente avec backoff puis on lève une erreur explicite plutôt | |
| 64 | + # que de rapporter un faux « 0 offre ». | |
| 65 | + last = "" | |
| 66 | + for attempt in range(3): | |
| 67 | + resp = self.get(f"{self._base}/apply/") | |
| 68 | + html = resp.text | |
| 69 | + if "resumator" in html: | |
| 70 | + return html | |
| 71 | + last = (f"HTTP {resp.status_code}, {len(html)} octets, " | |
| 72 | + f"content-type {resp.headers.get('content-type')}") | |
| 73 | + if attempt < 2: | |
| 74 | + time.sleep(10 * (attempt + 1)) | |
| 75 | + raise RuntimeError( | |
| 76 | + f"jazzhr {self.ORG}: page liste sans structure jobboard " | |
| 77 | + f"après 3 essais ({last})") | |
| 78 | + | |
| 55 | 79 | def fetch(self) -> list[JobPosting]: |
| 56 | − html = self.get(f"{self._base}/apply/").text | |
| 80 | + html = self._fetch_list() | |
| 57 | 81 | out: list[JobPosting] = [] |
| 58 | 82 | details_used = 0 |
| 59 | 83 | seen: set[str] = set() |
| 60 | 84 | |