[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 | # Fichier : jobka/connectors/jazzhr.py | 5 | # Fichier : jobka/connectors/jazzhr.py | ||
| 6 | # Rôle : Classe de plateforme JazzHR (applytojob.com) — liste HTML publique | 6 | # Rôle : Classe de plateforme JazzHR (applytojob.com) — liste HTML publique | ||
| 7 | # /apply/ + JSON-LD JobPosting des pages détail (lieu, salaire…) | 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 | """Plateforme JazzHR (<org>.applytojob.com), répandue chez les PME. | 10 | """Plateforme JazzHR (<org>.applytojob.com), répandue chez les PME. | ||
| 11 | 11 | ||||
@@ -21,6 +21,7 @@ from __future__ import annotations | |||||
| 21 | 21 | ||||
| 22 | import os | 22 | import os | ||
| 23 | import re | 23 | import re | ||
| 24 | +import time | ||||
| 24 | 25 | ||||
| 25 | from ..schema import JobPosting, is_quebec_location | 26 | from ..schema import JobPosting, is_quebec_location | ||
| 26 | from . import _jsonld | 27 | from . import _jsonld | ||
@@ -52,8 +53,31 @@ class JazzHRConnector(BaseConnector): | |||||
| 52 | node = _jsonld.extract_jobposting(html) | 53 | node = _jsonld.extract_jobposting(html) | ||
| 53 | return _jsonld.jobposting_fields(node) if node else {} | 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 | def fetch(self) -> list[JobPosting]: | 79 | def fetch(self) -> list[JobPosting]: | ||
| 56 | − html = self.get(f"{self._base}/apply/").text | 80 | + html = self._fetch_list() | ||
| 57 | out: list[JobPosting] = [] | 81 | out: list[JobPosting] = [] | ||
| 58 | details_used = 0 | 82 | details_used = 0 | ||
| 59 | seen: set[str] = set() | 83 | seen: set[str] = set() | ||
| 60 | 84 | ||||