|
|
# Dépendances
|
|
|
from scrapy import signals
|
|
|
|
|
|
# Python a une fonctionnalité regex native (re.py) mais elle a quelques limitations qui dans le cas présent rendent les regex inopérants
|
|
|
# On importe donc regex.py (pip install regex) qui étend les capacités de re.py (notamment la tolérance aux erreurs)
|
|
|
import regex
|
|
|
|
|
|
|
|
|
class SplitAndSort:
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
# Une fonction qui sépare les adresses selon les noms de rue
|
|
|
def splitStreet(self, value):
|
|
|
# Le regex qui évalue là où il faut séparer la chaîne de caractère
|
|
|
# (présence de "et / - + ainsi que" )
|
|
|
# (sans que ce séparateur ne soit placé près d'un groupe de numéros de la même rue)
|
|
|
expr = r"((?<!(\d\sa)|(\d\sb)|(\d\sbis)|(\d\ster)|(\d)|(\da)|(\db)|(\dbis)|(\dter)|(\d\s)|(\da\s)|(\db\s)|(\dbis\s)|(\dter\s)|(\d\sa\s)|(\d\sb\s)|(\d\sbis\s)|(\d\ster\s))(–|\bet)|(?<!(\d\sa)|(\d\sb)|(\d\sbis)|(\d\ster)|(\d)|(\da)|(\db)|(\dbis)|(\dter)|(\d\s)|(\da\s)|(\db\s)|(\dbis\s)|(\dter\s)|(\d\sa\s)|(\d\sb\s)|(\d\sbis\s)|(\d\ster\s))\/|(ainsi\sque)|((?<!(\d))\s-\s(?!(bt)|(bis)|(ter)))|(?<!(\d\sa)|(\d\sb)|(\d\sbis)|(\d\ster)|(\d)|(\da)|(\db)|(\dbis)|(\dter)|(\d\s)|(\da\s)|(\db\s)|(\dbis\s)|(\dter\s)|(\d\sa\s)|(\d\sb\s)|(\d\sbis\s)|(\d\ster\s))\+)"
|
|
|
# Remplacement de chaque séparateur par un pipe ("|")
|
|
|
subst = "|"
|
|
|
repl = regex.sub(expr, subst, value, 0, regex.MULTILINE | regex.IGNORECASE)
|
|
|
# Formattage (supression des espaces insécables et des ":")
|
|
|
filtered = regex.sub('\\xa0|:', '', repl, 0, regex.MULTILINE | regex.IGNORECASE)
|
|
|
# Séparation en liste
|
|
|
splitted = regex.split('\|', filtered, regex.MULTILINE | regex.IGNORECASE)
|
|
|
# Suppression des espaces en début et fin de chaîne
|
|
|
stripped = [x.strip() for x in splitted]
|
|
|
# Suppression des "None" de la liste (failsafe, ne devrait pas être nécessaire)
|
|
|
result = list(filter(None, stripped))
|
|
|
return result
|
|
|
|
|
|
|
|
|
# Une fonction qui valide tout ce qui est un couple n°/nom de rue
|
|
|
def isWorth(self, value):
|
|
|
# Le regex qui évalue ce qu'il faut garder
|
|
|
expr = r"(?=.*[a-z])(?=.*\d+).*"
|
|
|
# Un regex contre lequel comparer avant pour supprimer les exceptions
|
|
|
# (cas d'usage : "jardin public du 19 mars 1962" est reconnu comme un couple n°/nom de rue)
|
|
|
# ce n'est pas très sexy, il vaudrait mieux un regex plus strict,
|
|
|
# mais il serait trop complexe pour que ça en vaille la peine pour de rares exceptions
|
|
|
# (possibilité d'en ajouter d'autres ici par la suite si besoin)
|
|
|
xcp = r"(jardin public)|(Bâtiment 12)"
|
|
|
# Le résultat (booléen) si le regex match ou non, après comparaison avec les exceptions
|
|
|
if bool(regex.findall(xcp, value, regex.MULTILINE | regex.IGNORECASE)) is False :
|
|
|
result =bool(regex.findall(expr, value, regex.MULTILINE | regex.IGNORECASE))
|
|
|
else :
|
|
|
result = False
|
|
|
return result
|
|
|
|
|
|
|
|
|
# Une fonction qui enlève tous les trucs peu utiles ou encombrants des adresses
|
|
|
# ("immeuble", infos en parenthèses, infos sur l'arrêté, etc)
|
|
|
def removeClutter(self,value):
|
|
|
# Le regex qui évalue ce qu'il faut retirer
|
|
|
expr = r"\(.*\)|Immeuble|Chapelle,|Arrêté.*|\((.*){0,3}|"
|
|
|
# Le résultat après retrait
|
|
|
result = regex.sub(expr, '', value, regex.IGNORECASE | regex.MULTILINE)
|
|
|
stripped = result.strip()
|
|
|
return stripped
|
|
|
|
|
|
# Une fonction qui sépare les numéros des adresses
|
|
|
def separateNbr(self,value):
|
|
|
# Le regex qui évalue ce qui est un numéro ou un groupe de numéros
|
|
|
expr = r"(((\d).*(\da\b))(?=\s?\S?.{8,})|((\d).*(\db\b))(?=\s?\S?.{8,})|((\d).*(\dt\b))(?=\s?\S?.{8,})|((\d).*(\dbis\b))(?=\s?\S?.{8,})|((\d).*(\dter\b))(?=\s?\S?.{8,})|((\d).*(\d))(?=\s?\S?.{8,})|(\da\b)(?=\s?\S?.{8,})|(\db\b)(?=\s?\S?.{8,})|(\dbis\b)(?=\s?\S?.{8,})|(\dt\b)(?=\s?\S?.{8,})|(\dter\b)(?=\s?\S?.{8,})|(\d)(?=\s?\S?.{8,}))"
|
|
|
# On extrait le groupe numérique et on le place dans la variable "nbr"
|
|
|
sep = regex.search(expr, value, regex.IGNORECASE | regex.MULTILINE)
|
|
|
nbr = sep[0]
|
|
|
# On supprime le texte de la variable "nbr" de l'adresse (et on enlève les espaces en trop)
|
|
|
invexpr = regex.compile(nbr)
|
|
|
name = regex.sub(invexpr, '', value, regex.IGNORECASE | regex.MULTILINE)
|
|
|
name = name.strip()
|
|
|
# On retourne une liste avec le groupe numérique d'un côté, le nom de rue de l'autre
|
|
|
return (nbr,name)
|
|
|
|
|
|
|
|
|
|
|
|
# Une fonction qui sépare les numéros (hors rangées)
|
|
|
def splitNumber(self, value):
|
|
|
# Le regex qui évalue là où il faut séparer les numéros
|
|
|
expr = r"((?<=\d)\s?(&|et|_|-|\/|\+|,)\s?(?=\d))|((?<=\da)\s?(&|et|_|-|\/|\+|,)\s?(?=\d))|((?<=\db)\s?(&|et|_|-|\/|\+|,)\s?(?=\d))|((?<=\dt)\s?(&|et|_|-|\/|\+|,)\s?(?=\d))|((?<=\dbis)\s?(&|et|_|-|\/|\+|,)\s?(?=\d))|((?<=\dter)\s?(&|et|_|-|\/|\+|,)\s?(?=\d))"
|
|
|
# Remplacement de chaque séparateur par un pipe ("|")
|
|
|
repl = regex.sub(expr, "|", value, regex.MULTILINE | regex.IGNORECASE)
|
|
|
# Séparation en liste
|
|
|
splitted = regex.split('\|', repl, regex.MULTILINE | regex.IGNORECASE)
|
|
|
# Suppression des espaces en début et fin de chaîne
|
|
|
stripped = [x.strip() for x in splitted]
|
|
|
# Suppression des "None" de la liste (failsafe, ne devrait pas être nécessaire)
|
|
|
value = list(filter(None, stripped))
|
|
|
return value
|
|
|
|
|
|
#Une fonction qui interpole les numéros manquants à partir des rangées
|
|
|
def splitRanges(self,value):
|
|
|
# Le regex qui évalue là où il faut séparer les numéros
|
|
|
expr = r"((?<=\d)\s?(au|à)\s?(?=\d))|((?<=\da)\s?(au|à)\s?(?=\d))|((?<=\db)\s?(au|à)\s?(?=\d))|((?<=\dt)\s?(au|à)\s?(?=\d))|((?<=\dbis)\s?(au|à)\s?(?=\d))|((?<=\dter)\s?(au|à)\s?(?=\d))"
|
|
|
# Remplacement de chaque séparateur par un pipe ("|")
|
|
|
repl = regex.sub(expr, "|", value, regex.MULTILINE | regex.IGNORECASE)
|
|
|
# Séparation en liste
|
|
|
splitted = regex.split('\|', repl, regex.MULTILINE | regex.IGNORECASE)
|
|
|
# Suppression des espaces en début et fin de chaîne
|
|
|
stripped = [x.strip() for x in splitted]
|
|
|
# Suppression des "None" de la liste (failsafe, ne devrait pas être nécessaire)
|
|
|
listed = list(filter(None, stripped))
|
|
|
# Si c'est effectivement une rangée :
|
|
|
if len(listed) > 1 :
|
|
|
# On initie une liste vide et un index correspondant au premier numéro de la rangée
|
|
|
rawList = []
|
|
|
index = int(listed[0])
|
|
|
# Tant que l'index n'est pas supérieur au dernier numéro de la rangée...
|
|
|
while index <= int(listed[1]):
|
|
|
# Inscrire l'index dans la liste
|
|
|
rawList.append(index)
|
|
|
# augmenter l'index d'un numéro
|
|
|
index += 1
|
|
|
# Un petit check pour supprimer les numéros pairs ou impairs selon la parité du premier numéro.
|
|
|
if int(listed[0])%2 == 0 :
|
|
|
for i in rawList :
|
|
|
if i%2 != 0 :
|
|
|
rawList.remove(i)
|
|
|
if int(listed[0])%2 != 0 :
|
|
|
for i in rawList :
|
|
|
if i%2 == 0 :
|
|
|
rawList.remove(i)
|
|
|
return rawList
|
|
|
else :
|
|
|
return value
|
|
|
|
|
|
def removeAbrog(self,value):
|
|
|
expr = r"(?:((main\s?-?levée)|(abrog))){i<=2,d<=2,e<=3}"
|
|
|
part = r"(?:(partiel)){i<=2,d<=2,e<=3}"
|
|
|
if bool(regex.findall(expr, value, regex.MULTILINE | regex.IGNORECASE)):
|
|
|
if bool(regex.findall(part, value, regex.MULTILINE | regex.IGNORECASE)):
|
|
|
return 1 # partiellement abrogé
|
|
|
else :
|
|
|
return 2 # abrogé
|
|
|
else :
|
|
|
return 0
|
|
|
|
|
|
def typeOf(self,value):
|
|
|
# mise en sécurité
|
|
|
peril = r"(?:((?<!périmètre)(sécurité|péril))){i<=2,d<=2,e<=2}"
|
|
|
# périmètre de sécurité
|
|
|
perimetre = r"(?:(périmètre)){i<=2,d<=2,e<=3}"
|
|
|
# imminent
|
|
|
urgent = r"(?:(urgent|imminent|grave)){i<=2,d<=2,e<=3}"
|
|
|
# interdiction d'occupation
|
|
|
interdi = r"(?:((?<=(occup)).*(interdi)|(?<=(util)).*(interdi)|(interdi).*(?=(occup))|(interdi).*(?=(util)))){i<=2,d<=2,e<=3}"
|
|
|
# déconstruction
|
|
|
deconstr = r"(?:(déconstr)){i<=2,d<=2,e<=3}"
|
|
|
# astreinte administrative
|
|
|
astr = r"(?:(astreinte)){i<=2,d<=2,e<=3}"
|
|
|
if bool(regex.findall(perimetre, value, regex.MULTILINE | regex.IGNORECASE)):
|
|
|
return "Périmètre de sécurité"
|
|
|
elif bool(regex.findall(interdi, value, regex.MULTILINE | regex.IGNORECASE)):
|
|
|
return "Interdiction d'occupation"
|
|
|
elif bool(regex.findall(deconstr, value, regex.MULTILINE | regex.IGNORECASE)):
|
|
|
return "Déconstruction"
|
|
|
elif bool(regex.findall(astr, value, regex.MULTILINE | regex.IGNORECASE)):
|
|
|
return "Astreinte administrative"
|
|
|
elif bool(regex.findall(peril, value, regex.MULTILINE | regex.IGNORECASE)):
|
|
|
if bool(regex.findall(urgent, value, regex.MULTILINE | regex.IGNORECASE)):
|
|
|
return "Péril imminent"
|
|
|
else :
|
|
|
return "Péril"
|
|
|
else :
|
|
|
return ""
|
|
|
|
|
|
# La fonction principale de traitement du résultat de la requête de scrapy avant yield
|
|
|
def process_spider_output(self, response, result, spider):
|
|
|
# Pour chaque résultat individuel dans le résultat de la requête :
|
|
|
for r in result:
|
|
|
# Extraire le texte brut de l'adresse et le stocker dans la variable "adresses"
|
|
|
adresses = r.pop('adrs')
|
|
|
# Extraire le dernier arrêté et le stocker dans la variable "dernA"
|
|
|
dernA = r.pop('dernierA')
|
|
|
As = r.pop('As')
|
|
|
raw = r.pop('raw')
|
|
|
# Ne prendre que le premier index de la liste
|
|
|
# il n'y en a qu'un de toute manière, c'est pour extraire le texte de l'objet car regex.py ne sait pas
|
|
|
# traiter les objets tuple et scrapy retourne un objet tuple, pas seulement une chaîne de caractères
|
|
|
adresses = adresses[0]
|
|
|
dernA = dernA[0]
|
|
|
# Si l'adresse est non-nulle :
|
|
|
# (pour éviter que regex.py ne plante à cause d'un objet de type None au lieu d'une chaîne de caractères)
|
|
|
# (ça supprime par la même occasion les arrêtés sans adresse, à date il n'y en a qu'un, sur un passage privé)
|
|
|
if dernA :
|
|
|
abrog = self.removeAbrog(dernA)
|
|
|
if abrog == 1:
|
|
|
typeof = "(Mainlevée partielle) "+ self.typeOf(dernA)
|
|
|
elif abrog == 2:
|
|
|
typeof = "Mainlevée"
|
|
|
else :
|
|
|
typeof = self.typeOf(dernA)
|
|
|
|
|
|
if adresses :
|
|
|
# On apelle la fonction splitStreet pour séparer les rues dans la variable "adresses" et les inscrire dans la liste "indiv"
|
|
|
indiv = self.splitStreet(adresses)
|
|
|
# Pour chaque adresse de la liste d'adresses "indiv" précemment obtenue :
|
|
|
for i in indiv:
|
|
|
# Si l'adresse est utile
|
|
|
if self.isWorth(i) :
|
|
|
iClean = self.removeClutter(i)
|
|
|
iSep = self.separateNbr(iClean)
|
|
|
iNbrs = self.splitNumber(iSep[0])
|
|
|
for r in iNbrs:
|
|
|
x = self.splitRanges(r)
|
|
|
print(x)
|
|
|
if isinstance(x, list):
|
|
|
for n in x:
|
|
|
yield {'N°':n,'Nom de rue':iSep[1],'Statut':typeof,'Dernier arrêté (hors modificatif)':dernA,'Arrêtés':As,'Données brutes':raw,"QGIS-RAW":str(n)+" "+iSep[1],"QGIS-City":"Marseille","QGIS-Country":"France"}
|
|
|
else :
|
|
|
yield {'N°':r,'Nom de rue':iSep[1],'Statut':typeof,'Dernier arrêté (hors modificatif)':dernA,'Arrêtés':As,'Données brutes':raw,"QGIS-RAW":str(r)+" "+iSep[1],"QGIS-City":"Marseille","QGIS-Country":"France"} |