199 lines
7.0 KiB
Python
199 lines
7.0 KiB
Python
"""Extraction par règles : texte brut d'une page Facebook → champs du prospect."""
|
||
|
||
import re
|
||
import unicodedata
|
||
from datetime import date
|
||
|
||
from departements import DEPARTEMENTS, departement_depuis_cp
|
||
|
||
RE_TELEPHONE = re.compile(r"(?:\+33\s?[1-9]|0[1-9])(?:[\s.\-]?\d{2}){4}")
|
||
RE_EMAIL = re.compile(r"[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}")
|
||
RE_CP_VILLE = re.compile(r"(?<!\d)(\d{5})(?!\d)[ \t,]+([A-Za-zÀ-ÖØ-öø-ÿ'\- ]{2,40})")
|
||
RE_CP_SEUL = re.compile(r"(?<!\d)(\d{5})(?!\d)")
|
||
# « à Saint-Omer-de-Blain (44) » : ville + numéro de département entre parenthèses
|
||
RE_VILLE_DEPT = re.compile(r"([A-Za-zÀ-ÖØ-öø-ÿ'’\-]{3,40})?\s*\(\s*(\d{2,3}|2[AB])\s*\)", re.I)
|
||
# « Blain, France, 44130 » ou « Blain, 44130 » : ville avant le code postal
|
||
RE_VILLE_CP = re.compile(
|
||
r"([A-Za-zÀ-ÖØ-öø-ÿ'’\- ]{2,40})\s*,\s*(?:France\s*,?\s*)?(\d{5})(?!\d)", re.I)
|
||
|
||
VOIES = (r"rue|avenue|av\.|boulevard|bd\.?|chemin|route|impasse|place|quai|allée|cours"
|
||
r"|esplanade|promenade|lieu-dit|hameau|zone|za|zac|zi")
|
||
# Ligne ressemblant à une voie : « 12 Quai du Port », « 6, rue du moulin »...
|
||
RE_ADRESSE = re.compile(
|
||
rf"(?:^|\n)\s*((?:\d{{1,4}}\s?(?:bis|ter)?\s*,?\s+)?(?:{VOIES})\s[^\n;]{{2,60}})", re.I)
|
||
|
||
# Mots d'interface Facebook à ne pas prendre pour une ville devant « (NN) »
|
||
MOTS_INTERFACE = {
|
||
"photos", "avis", "reels", "followers", "publications", "posts",
|
||
"mentions", "tout", "plus", "voir", "vidéos", "amis",
|
||
}
|
||
|
||
# (mot-clé, type) — ordre = priorité, le premier trouvé gagne
|
||
TYPES = [
|
||
("camping", "Camping"),
|
||
("guinguette", "Guinguette"),
|
||
("festival", "Festival"),
|
||
("restaurant", "Restaurant"),
|
||
("pizzeria", "Restaurant"),
|
||
("creperie", "Restaurant"),
|
||
("brasserie", "Restaurant"),
|
||
("bistrot", "Restaurant"),
|
||
("bistro", "Restaurant"),
|
||
("pub", "Bar"),
|
||
("bar", "Bar"),
|
||
("cafe", "Bar"),
|
||
("cave", "Bar"),
|
||
("salle", "Salle"),
|
||
("mjc", "Salle"),
|
||
("hotel", "Hôtel"),
|
||
("domaine", "Domaine"),
|
||
]
|
||
|
||
INFOS_LIEU = [
|
||
"jardin", "terrasse", "plage", "bord de mer", "port", "piscine",
|
||
"lac", "riviere", "rooftop", "patio", "plein air", "scene", "guinguette",
|
||
]
|
||
|
||
|
||
def _sans_accents(texte):
|
||
return unicodedata.normalize("NFKD", texte).encode("ascii", "ignore").decode()
|
||
|
||
|
||
def _chercher_mot(mot, texte_normalise):
|
||
return re.search(rf"(?<![a-z]){re.escape(mot)}(?![a-z])", texte_normalise) is not None
|
||
|
||
|
||
def _telephone(texte):
|
||
m = RE_TELEPHONE.search(texte)
|
||
if not m:
|
||
return ""
|
||
chiffres = re.sub(r"\D", "", m.group(0))
|
||
if chiffres.startswith("33"):
|
||
chiffres = "0" + chiffres[2:]
|
||
if len(chiffres) != 10:
|
||
return ""
|
||
return " ".join(chiffres[i:i + 2] for i in range(0, 10, 2))
|
||
|
||
|
||
def _email(texte):
|
||
for m in RE_EMAIL.finditer(texte):
|
||
adresse = m.group(0)
|
||
if not adresse.lower().endswith(("@facebook.com", "@fb.com")):
|
||
return adresse
|
||
return ""
|
||
|
||
|
||
def _cp_ville(texte):
|
||
for m in RE_CP_VILLE.finditer(texte):
|
||
cp, ville = m.group(1), m.group(2)
|
||
if not ("01" <= cp[:2] <= "98"):
|
||
continue
|
||
ville = ville.split("\n")[0].strip(" -'")
|
||
ville = re.sub(r"\bfrance\b", "", ville, flags=re.IGNORECASE).strip(" ,-")
|
||
return cp, ville.title()
|
||
# « Blain, France, 44130 » : ville avant le code postal
|
||
for m in RE_VILLE_CP.finditer(texte):
|
||
cp, ville = m.group(2), m.group(1).split("\n")[-1].strip(" -'’")
|
||
if not ("01" <= cp[:2] <= "98") or ville.lower() == "france":
|
||
continue
|
||
return cp, ville.title() if ville.islower() else ville
|
||
m = RE_CP_SEUL.search(texte)
|
||
if m and "01" <= m.group(1)[:2] <= "98":
|
||
return m.group(1), ""
|
||
return "", ""
|
||
|
||
|
||
def _nettoyer_adresse(brut):
|
||
# couper avant le code postal s'il suit sur la même ligne
|
||
brut = re.split(r",?\s*\d{5}\b", brut)[0]
|
||
# « 6, rue du moulin , Blain, France » : ne garder que jusqu'au segment de voie,
|
||
# la ville et le pays ont leurs propres colonnes
|
||
segments = [s.strip() for s in brut.split(",") if s.strip()]
|
||
dernier_voie = -1
|
||
for i, seg in enumerate(segments):
|
||
if re.search(rf"\b(?:{VOIES})\b", seg, re.I):
|
||
dernier_voie = i
|
||
if dernier_voie >= 0:
|
||
segments = segments[:dernier_voie + 1]
|
||
else:
|
||
segments = [s for s in segments if s.lower() != "france"]
|
||
return ", ".join(segments).strip(" ,-–")
|
||
|
||
|
||
def _adresse(texte):
|
||
lignes = [l.strip() for l in texte.split("\n")]
|
||
# libellé « Adresse » de la section Coordonnées (mais pas « Adresse e-mail »)
|
||
for i, ligne in enumerate(lignes):
|
||
if ligne.lower() in ("adresse", "address"):
|
||
for suite in lignes[i + 1:i + 3]:
|
||
if suite and not re.match(r"^(adresse|address|e-?mail|téléphone|phone|site|web|messenger)", suite, re.I):
|
||
return _nettoyer_adresse(suite)
|
||
m = RE_ADRESSE.search(texte)
|
||
return _nettoyer_adresse(m.group(1)) if m else ""
|
||
|
||
|
||
def _ville_dept_parentheses(texte):
|
||
"""Repli quand il n'y a pas d'adresse postale : « Saint-Omer-de-Blain (44) »."""
|
||
for m in RE_VILLE_DEPT.finditer(texte):
|
||
cle = m.group(2).upper()
|
||
nom = DEPARTEMENTS.get(cle)
|
||
if not nom:
|
||
continue
|
||
ville = (m.group(1) or "").strip("-'’ ")
|
||
if len(ville) < 3 or ville.lower() in MOTS_INTERFACE or ville.lower() == "france":
|
||
ville = ""
|
||
return ville, f"{cle} - {nom}"
|
||
return "", ""
|
||
|
||
|
||
def _type_lieu(titre, texte):
|
||
# La catégorie de la page apparaît en général dans les premiers caractères du texte
|
||
for zone in (titre, texte[:500], texte):
|
||
zone_norm = _sans_accents(zone.lower())
|
||
for mot, type_lieu in TYPES:
|
||
if _chercher_mot(mot, zone_norm):
|
||
return type_lieu
|
||
return ""
|
||
|
||
|
||
def _infos_lieu(texte):
|
||
texte_norm = _sans_accents(texte.lower())
|
||
trouves = [mot for mot in INFOS_LIEU if _chercher_mot(mot, texte_norm)]
|
||
return ", ".join(trouves[:5])
|
||
|
||
|
||
def extraire(titre, texte, url):
|
||
cp, ville = _cp_ville(texte)
|
||
departement = departement_depuis_cp(cp)
|
||
if not cp:
|
||
ville_repli, departement = _ville_dept_parentheses(texte)
|
||
ville = ville or ville_repli
|
||
return {
|
||
"Nom du prospect": titre.strip(),
|
||
"Département": departement,
|
||
"Ville": ville,
|
||
"Code Postal": cp,
|
||
"Adresse": _adresse(texte),
|
||
"Date d'ajout": date.today().strftime("%d/%m/%Y"),
|
||
"Date de contact": "",
|
||
"Nom de contact": "",
|
||
"Téléphone": _telephone(texte),
|
||
"Email": _email(texte),
|
||
"Infos du lieu": _infos_lieu(texte),
|
||
"Type": _type_lieu(titre, texte),
|
||
"Lien Facebook": url,
|
||
}
|
||
|
||
|
||
if __name__ == "__main__":
|
||
exemple = """Le Vieux Gréement
|
||
Bar · Restaurant
|
||
12 Quai du Port, 29200 Brest, France
|
||
06 12 34 56 78
|
||
contact@vieuxgreement.fr
|
||
Grande terrasse avec vue sur le port, concerts en plein air l'été dans le jardin.
|
||
"""
|
||
import json
|
||
champs = extraire("Le Vieux Gréement", exemple, "https://www.facebook.com/vieuxgreement")
|
||
print(json.dumps(champs, ensure_ascii=False, indent=2))
|