[ka2] fix connecteur groupe_robin: timeouts admin-ajax récurrents retryables sur le POST liste. Au sync de 11:45 le connecteur a échoué (stale) sur un ReadTimeout 30 s — 3e épisode du même hoquet (ConnectTimeout 09-13, ReadTimeout 10-17 et 10-28) sur le POST admin-ajax get_list_immeubles_ajx dont la réponse pèse ~3,9 Mo (backend WP parfois lent), alors que le connecteur est sain (found=62 stable, re-testé live : 200 en 3-5 s ×3). Les deux missions précédentes avaient conclu « transitoire, rien à faire » : le hoquet revient — fix minimal (patron morin ae6ac03) : _post_ajax fait désormais 3 tentatives avec backoff 0/5/10 s sur Timeout/ConnectionError/HTTP 5xx (4xx propagés tels quels), erreur franche inchangée si les 3 échouent (pas de zéro silencieux) ; timeout de classe porté 30 → 60 s pour le payload lourd. Fixtures inchangées, pytest groupe_robin 2 passed. Testé : fetch() venv → 62 unités (715-2 190 $, T-R 39 / St-Hyacinthe 23), run.py sync groupe_robin → found=62 ok (added=0, updated=0, removed=0, 3,5 s) = médiane exacte 62.0, pm2 restart lou-ka-sync, site :8095 → 200.
1 changed file +31 −12
modified
louka/connectors/groupe_robin.py
+31 −12
@@ -15,6 +15,7 @@ from __future__ import annotations | ||
| 15 | 15 | import re |
| 16 | 16 | import time |
| 17 | 17 | |
| 18 | +import requests | |
| 18 | 19 | from bs4 import BeautifulSoup |
| 19 | 20 | |
| 20 | 21 | from ..schema import Listing, normalize_unit_type |
@@ -64,21 +65,39 @@ class GroupeRobinConnector(BaseConnector): | ||
| 64 | 65 | source_id = "groupe_robin" |
| 65 | 66 | request_delay = 0.8 |
| 66 | 67 | max_pages = 5 # garde-fou (2 pages réelles à 40 immeubles/page) |
| 68 | + timeout = 60 # payload admin-ajax ~3,9 Mo, backend WP parfois lent | |
| 67 | 69 | |
| 70 | + # Le POST admin-ajax (3,9 Mo) hoquète par épisodes (ReadTimeout isolés | |
| 71 | + # 09-13, 10-17, 10-28) alors que le site est sain entre deux — 3 | |
| 72 | + # tentatives avec backoff ; erreur franche inchangée si les 3 échouent. | |
| 68 | 73 | def _post_ajax(self, offset: int) -> list: |
| 69 | 74 | """POST admin-ajax paginé, avec la même politesse que get().""" |
| 70 | − wait = self.request_delay - (time.time() - self._last_request) | |
| 71 | − if wait > 0: | |
| 72 | − time.sleep(wait) | |
| 73 | − resp = self.session.post(AJAX_URL, data={ | |
| 74 | − "action": "get_list_immeubles_ajx", | |
| 75 | − "offset": str(offset), | |
| 76 | − "immeubles_per_page": str(PER_PAGE), | |
| 77 | − "categories[]": CAT_APPARTEMENT, | |
| 78 | − }, timeout=self.timeout) | |
| 79 | − self._last_request = time.time() | |
| 80 | − resp.raise_for_status() | |
| 81 | − return resp.json() | |
| 75 | + last: Exception | None = None | |
| 76 | + for attempt in range(3): | |
| 77 | + if attempt: | |
| 78 | + time.sleep(5 * attempt) | |
| 79 | + wait = self.request_delay - (time.time() - self._last_request) | |
| 80 | + if wait > 0: | |
| 81 | + time.sleep(wait) | |
| 82 | + try: | |
| 83 | + resp = self.session.post(AJAX_URL, data={ | |
| 84 | + "action": "get_list_immeubles_ajx", | |
| 85 | + "offset": str(offset), | |
| 86 | + "immeubles_per_page": str(PER_PAGE), | |
| 87 | + "categories[]": CAT_APPARTEMENT, | |
| 88 | + }, timeout=self.timeout) | |
| 89 | + except (requests.Timeout, requests.ConnectionError) as exc: | |
| 90 | + self._last_request = time.time() | |
| 91 | + last = exc | |
| 92 | + continue | |
| 93 | + self._last_request = time.time() | |
| 94 | + if resp.status_code >= 500: | |
| 95 | + last = requests.HTTPError( | |
| 96 | + f"{resp.status_code} sur {AJAX_URL}", response=resp) | |
| 97 | + continue | |
| 98 | + resp.raise_for_status() | |
| 99 | + return resp.json() | |
| 100 | + raise last # type: ignore[misc] | |
| 82 | 101 | |
| 83 | 102 | def fetch(self) -> list[Listing]: |
| 84 | 103 | listings: dict[str, Listing] = {} |
| 85 | 104 | |