V1.23.33 Fin de la récupération des entreprises

This commit is contained in:
2026-09-20 08:49:20 +00:00
parent f0711f49ac
commit dd2b7a6872
11 changed files with 131 additions and 14 deletions
+71 -12
View File
@@ -19,6 +19,7 @@ Usage :
(ou export ENTREPRISES_DB_PASS=... pour ne pas passer le mot de passe en argument)
"""
import argparse
import http.client
import json
import os
import sys
@@ -240,15 +241,39 @@ def log(msg):
print(msg, file=sys.stderr, flush=True)
def http_get_json(url, retries=5):
def http_get_json(url, retries=8):
# Durci le 17/09/2026 : la version precedente ne rattrapait que
# urllib.error.URLError/TimeoutError, avec un backoff court (1-5s) --
# insuffisant face a deux incidents reels constates le meme jour :
# http.client.RemoteDisconnected (pas une URLError, jamais rattrapee,
# a fait planter le departement 74) et HTTP 429 Too Many Requests
# soutenu (deux extractions tournant par erreur en parallele ont fait
# plafonner l'API, cascadant sur 3 departements avant plantage). Meme
# esprit que le durcissement deja fait sur inpi_bilans_backfill.py.
for attempt in range(retries):
try:
with urllib.request.urlopen(url, timeout=20) as resp:
return json.load(resp)
except (urllib.error.URLError, TimeoutError) as e:
except urllib.error.HTTPError as e:
if e.code == 429:
if attempt == retries - 1:
raise
retry_after = e.headers.get("Retry-After") if e.headers else None
wait = int(retry_after) if retry_after and retry_after.isdigit() else min(10 * (2 ** attempt), 300)
log(f" !! 429 Too Many Requests -- pause de {wait}s avant nouvelle tentative "
f"({attempt + 1}/{retries})")
time.sleep(wait)
continue
if attempt == retries - 1:
raise
time.sleep(1 + attempt)
log(f" retry apres erreur HTTP ({e})")
time.sleep(2 + attempt)
except (urllib.error.URLError, TimeoutError, http.client.HTTPException,
ConnectionError) as e:
if attempt == retries - 1:
raise
log(f" retry apres erreur reseau ({e})")
time.sleep(2 + attempt)
# Paris, Lyon et Marseille (PLM) : dans les donnees Sirene, le siege d'une
@@ -389,13 +414,16 @@ def paginate_all(params, seen):
return out
def fetch_commune_entreprises(code_commune, seen):
base = {"code_commune": code_commune, "etat_administratif": "A"}
def fetch_entreprises_for_filter(base, seen, label):
"""Cascade de repli generique (section NAF puis code NAF precis si
plafonnee a CAP resultats), utilisee aussi bien pour un filtre par
commune que, depuis le 18/09/2026, par departement entier (cas 2A/2B,
cf. fetch_departement_entreprises_direct)."""
total = probe_total(base)
if total < CAP:
return paginate_all(base, seen)
log(f" commune {code_commune} plafonnee ({total}), repli par section NAF")
log(f" {label} plafonnee ({total}), repli par section NAF")
out = []
for section in NAF_SECTIONS:
sec_params = dict(base, section_activite_principale=section)
@@ -410,7 +438,7 @@ def fetch_commune_entreprises(code_commune, seen):
# ex. Rennes/section L). On decoupe par code NAF precis (732 codes possibles) --
# un code implique deja sa section, donc on l'utilise seul, sans le combiner
# au filtre de section.
log(f" commune {code_commune} section {section} encore plafonnee ({sec_total}), "
log(f" {label} section {section} encore plafonnee ({sec_total}), "
f"repli par code NAF precis")
for code in codes_for_section(section):
code_params = dict(base, activite_principale=code)
@@ -418,12 +446,30 @@ def fetch_commune_entreprises(code_commune, seen):
if code_total == 0:
continue
if code_total >= CAP:
log(f" !! commune {code_commune} code {code} encore plafonnee "
log(f" !! {label} code {code} encore plafonnee "
f"({code_total}) -- a decouper manuellement (ex: par tranche_effectif_salarie)")
out.extend(paginate_all(code_params, seen))
return out
def fetch_commune_entreprises(code_commune, seen):
base = {"code_commune": code_commune, "etat_administratif": "A"}
return fetch_entreprises_for_filter(base, seen, f"commune {code_commune}")
def fetch_departement_entreprises_direct(dep, seen):
"""Repli specifique aux departements dont le code commune INSEE n'est pas
purement numerique (2A, 2B -- Corse) : le parametre `code_commune` de
l'API recherche-entreprises echoue silencieusement (0 resultat, HTTP 200,
aucune erreur) sur ces codes alphanumeriques, verifie empiriquement le
18/09/2026 (2A004/2B033 -> 0 alors que 01004 -> 6124). Le parametre
`departement`, lui, fonctionne normalement -- on interroge donc le
departement entier d'un coup, avec la meme cascade de repli que pour une
commune plafonnee."""
base = {"departement": dep, "etat_administratif": "A"}
return fetch_entreprises_for_filter(base, seen, f"departement {dep}")
def upsert_entreprise(conn, table, row):
cols = list(row.keys())
placeholders = ", ".join(["%s"] * len(cols))
@@ -504,8 +550,9 @@ def run_department(conn, dep):
seen = set()
total_upserts = 0
total_finances = 0
for c in communes_geo:
results = fetch_commune_entreprises(c["code"], seen)
def store(results):
nonlocal total_upserts, total_finances
for r in results:
row = map_entreprise(r)
if row["ENT_Siren"]:
@@ -514,8 +561,20 @@ def run_department(conn, dep):
if row["ENT_FinancesAnnee"] is not None:
upsert_finances(conn, table3, row)
total_finances += 1
if results:
log(f" {c['nom']} ({c['code']}) : {len(results)} entreprises")
if not dep.isdigit():
# Code commune INSEE alphanumerique (2A, 2B) : le filtre code_commune
# de l'API echoue silencieusement, on interroge le departement entier
# d'un coup (cf. fetch_departement_entreprises_direct).
results = fetch_departement_entreprises_direct(dep, seen)
store(results)
log(f" departement {dep} (requete directe) : {len(results)} entreprises")
else:
for c in communes_geo:
results = fetch_commune_entreprises(c["code"], seen)
store(results)
if results:
log(f" {c['nom']} ({c['code']}) : {len(results)} entreprises")
with conn.cursor() as cur:
cur.execute(f"SELECT COUNT(*) FROM `{table1}`")