Python et Regexp
Traiter les expression rationelles (ou expression régulière) avec Python. Exemple sur les E-Mail
Introduction
Objectif de l'exercice
Soit un texte quelconque. Notre but pour ce coup va être de chercher et extraire toutes les adresses email de ce texte.Nous prévenons tout de suite le lectorat sur le fait que ce tutoriel peut faire grincer beaucoup de dents car avec lui le lecteur pourra extraire des adresses email de tout fichier texte, et ainsi devenir un spammeur potentiel. Pour cette raison, nous aurons introduit quelques coquilles volontaires dans le code, de manière à ce que le bête copieur/colleur ne puisse pas l'exploiter tel quel.
Les adresses email sont un bon point de départ pour commencer à apprendre la manipulation de texte et les expressions rationelles.
Nous allons donc partir sur cette base.
Prérequis
Le lecteur de ce document devra juste avoir un esprit ouvert. Nous n'allons pas exprimer les choses en exploitant les possibilités syntaxiques de Python. Un simple esprit observateur suffira pour comprendre les lignes de codes entrées. C'est plus de l'algoritmique que nous allons faire qu'autre chose.Plan du cours
Il est normal de commencer quelquepart, avec des concepts simples, avant de s'attaquer aux choses compliquées.Nous allons donc commencer par rechercher si dans un texte ou une chaine de caractère donnée, il y a une adresse email. Juste ça.
Une fois cela accompli, nous saurons aisément rechercher toutes les adresses email dans un texte.
Juste pour faire une analogie, si il fallait rechercher une séquence de type 'ab' dans une chaine de caractères quelconque, nous allons d'abord apprendre à trouver si dans une chaine quelconque, il y a un ou des 'ab'. Nous allons ensuite apprendre à isoler les 'ab'.
Répéter zéro fois
Dans ce “niveau”, nous allons d'abord nous concentrer sur de la recherche de texte dans un texte.Expression française
Avant de nous plonger dans les expressions rationelles, nous allons d'abord faire du Français.Soit un texte ou une chaine de caractères quelconque. Quand nous voulons y chercher des adresses email, nous serions à la recherche d'une “suite de lettres, suivie d'un arowbase, suivi d'une suite de lettres”.
Suite de lettres
Les suites de lettre sont les chaines de caractères du type “dsofihs” ou “fqdfd”.Une autre façon de dire “suite de lettres” est: “des lettres quelconques mis les uns après les autres, et ce autant de fois que l'on veut” (0 fois, 1 fois, plusieurs fois,...).
“Fsqs3e” n'est pas strictement une suite de lettres car contient un chiffre.
“gfdEFF.”, “FREr-fds”, “SDFxsq_”,... non plus car contenant 0 ou plusieurs caractères qui ne font pas partie de l'alphabet.
On peut traduire “des lettres quelconques mis les uns après les autres, et ce autant de fois que l'on veut” en expression rationelle par [a-z]*.
Exemple en python
import reA ce point, nous avons dit à python de garder dans “exprat” (expression rationelle), la structure de chaine de caractères que nous souhaitons chercher: un caractères compris entre 'a' et 'z', se répétant autant de fois que l'on veut (y compris 0 fois et 1 fois).
exprat=re.compile('[a-z]*')
Nous allons maintenant faire des vérifications pour chercher notre chaine de caractères dans des chaines que nous choisirons pour vérifier.
Nous allons dans un premier temps vérifier si dans la chaine vide, il y a une chaine de caractères telle que nous l'avons définie:
print exprat.search(“”)Ce que nous avons fait là c'est de vérifier si dans “” (la chaine vide), il y a une suite de caractères du type [a-z]*.
<_sre.SRE_Match object at 0xb7b5a448>
Nous voyonsque la chaine vide contient une “chaine de caractères”.
Voyons si “toto” contient une “chaine de caractère”
print exprat.search("toto")
<_sre.SRE_Match object at 0xb7b5a4f0>
Ok, “toto” en contient une. On pouvait s'en douter, c'est normal.
On essaie avec un chiffre. Est-ce que le chiffre “3” contient une chaine de caractères?
print exprat.search("3")
<_sre.SRE_Match object at 0xb7aaf058>
Oh! Tel que nous avons conçu notre epxression rationelle, “3” contient une chaine de caractères.
Effectivement, “3” peut-être vue la mise bout à bout de la “chaine vide”, puis du chiffre “3” et enfin de la “chaine vide”.
La “chaine vide” est donc contenue dans “3”. C'est pour cela que Python a déclaré qu'il a trouvé quelquechose qui correspond à une chaine de caractères dans “3”, tout comme il en a trouvé dans “” et dans “toto”.
Nomenclature
D'une manière plus technique, on dit que la chaine de caractères “toto” vérifie l'expression rationnelle [a-z]*.Il nous faut donc impérativement changer notre définition d'une “suite de lettres”, puisqu'elle est manifestement fausse.
La forme actuelle de notre expression rationelle est [a-z]*. Les chaines “”, “toto”, “3” et “titi@rktmb.org” verifient l'expression rationelle. Portant “3” et “toto” ne sont pas des adresses email.
Vraiment répéter
Occupons-nous d'abord de modifier notre expression rationelle pour que “” ne la vérifie plus.Notre but est de rechercher les adresses email dans une chaine de caractères quelconque, alors si on présente un “” à notre programme et qu'il dit que ça en est une, c'est mal parti.
Pour cela nous allons reprendre notre façon d'exprimer les choses en Français. Au lieu de chercher “des lettres quelconques mis les uns après les autres, et ce autant de fois que l'on veut” (0 fois, 1 fois, plusieurs fois,...), nous allons chercher “des lettres quelconques mis les uns après les autres, et ce autant de fois que l'on veut, mais au moins une fois”.
Dans le chapitre précédent, nous avons inclu le fait qu'un caractère pouvait se présenter zéro fois, c'est ce qui a fait que “” vérifie l'expession. Pour faire en sorte de dire “au moins une fois” il faut remplacer “*” par un “+”.
Ainsi, l'expression rationelle exprat devient [a-z]+.
En pratique, ça donne qu'il faut changer exprat, et faire les tests ensuite:
exprat=re.compile('[a-z]+')
print exprat.search("")
None
On voit que “” ne vérifie plus l'expression régulière.
print exprat.search("eee")
<_sre.SRE_Match object at 0xb7be13d8>
print exprat.search("toto@free.fr")
<_sre.SRE_Match object at 0xb7be13d8>
print exprat.search("toto@free.fr@locataire-serveur")
<_sre.SRE_Match object at 0xb7be1410>
Par contre, les autres chaines de caractères vérifient l'expession rationelle,
car toutes contiennent une suite de lettre d'au moins une lettre.
Les @
Nous allons maintenant chercher si la chaine contient un “@”.Car une adresse email doit contenir un @, et pas deux.
En python:
exprat=re.compile('@')
print exprat.search("toto@free.fr@locataire-serveur")
<_sre.SRE_Match object at 0xb7be1330>
print exprat.search("eee")
None
On voit donc que toute chaine de caractères qui contient un '@'
vérifie l'expression rationelle. Par contre, “eee”,
qui n'en contient pas, ne la vérifie pas.
Composer, bout à bout
Maintenant, nous allons rechercher “une suite d'au moins une lettre, suivie d'un @”. Nous savons comment traduire individuellement ces propositions: [a-z]+ et @. En faire une seule proposition consiste à les mettre bout à bout.Ainsi, “une suite d'au moins une lettre, suivie d'un @” peut-être traduit en [a-z]+@. On peut aussi écrire [a-z]+[@] c'est peut-être plus clair pour certains.
En Python:
exprat=re.compile('[a-z]+[@]')
print exprat.search(“@”)
Nous répond négativement, car @ ne contient pas de “suite d'au moins une lettre, suivie d'un @”.
Par contre:
print exprat.search(“eee@”)Nous répond positivement.
print exprat.search(“eee@xxxx@dddd”)Aussi nous répond positivement.
Nous allons maintenant rechercher “suite d'au moins une lettre, suivie d'un @, suivi d'une suite d'au moins une lettre”. En “regexp”, cela s'écrit: [a-z]+[@][a-z]+.
En se basant sur ça:
print exprat.search(“eee@”)Nous répond négativement, mais
print exprat.search(“eee@xxxx@dddd”)aussi nous répond positivement.
Commencer et Terminer
Au point ou nous en sommes, notre regexp sait détecter si il y a quelquechose du type “eee@fff”, et dire que c'est une adresse email.Le souci c'est que si il rencontre “eee@fff@ggg”, eh bien il dira aussi que c'est une adresse email.
Ce qu'il nous faut donc c'est un outil qui nous permette de dire que ça doit commencer par un seul ensemble de lettres, suivi par un seul “@” et fini par un seul ensemble de lettres. Ainsi, “eee@fff@ggg” ne serait plus reconnu comme une adresse email valide.
Pour cela nous disposons de “^” et “$” qui signifient respectivement “début de ligne” et “fin de ligne”.
Ainsi, si nous proposons la regexp ^[a-z]+[@][a-z]+$, qui se traduit en français par “un début de ligne suivi d'une suite d'au moins une lettre, suivie d'un @, suivi d'une suite d'au moins une lettre suivi d'une fin de ligne”, alors un deuxième “@” n'aura pas sa place dans la petite histoire.
Faisons donc nos tests:
exprat=re.compile('^[a-z]+[@][a-z]+$')
print exprat.search("eee@")
None
print exprat.search("eee@eee")
<_sre.SRE_Match object at 0xb7bddbb8>
print exprat.search("toto@free.fr")
None
print exprat.search("eee@ee.e@ooo")
None
On note que toto@free.fr n'a pas vérifié l'expession rationelle.
Celà est dû au “.” entre “free” et “fr”: Dans ^[a-z]+[@][a-z]+$ nous n'avons pas autorisé les “.” ni même les chiffres et les caractères spéciaux.
Pour autoriser les “.”, nous devons autoriser les “.”. Pour ce faire nous modifierons notre expression comme suis: ^[a-z\.]+[@][a-z\.]+$.
Ainsi:
exprat=re.compile('^[a-z\.]+[@][a-z\.]+$')
print exprat.search("toto.tata@linux.fr")
<_sre.SRE_Match object at 0xb7b68790>
Dans le même état d'esprit, puisque les adresses email peuvent contenir des “-”,
alors il faut explicitement des autoriser:
^[a-z\.\-]+[@][a-z\.\-]+$.
Ainsi:
exprat=re.compile('^[a-z\.]+[@][a-z\.]+$')
print exprat.search("to-to.ta-ta@li-nux.fr")
<_sre.SRE_Match object at 0xb7b68790>
Il y a toujours moyen de parfaire une expression rationelle.
Nous n'allons pas aller chercher la perfection.
L'auteur propose juste une expression qui devrait satisfaire pour les cas simples:
^[0-9a-z\.\-]+[@][0-9a-z\.\-]+$.
Le lecteur s'y retrouve?
Pour une approche simple des expressions rationelles, c'est siffusant.
Listage
Maintenant, prenons une chaine de caractères suffisamment longue et générique:s=”””Nous allons chercher à extraire les adresses email. Nous remarquons qu'il y en a trois.
nom prenom <espagna09@gringo.es>, name surname'foo.bar@bla-bla.eu.org',
liama: toto.titi@tata.info , nada
“””
En Python:
exprat=re.compile('[0-9a-z\.\-]+[@][0-9a-z\.\-]+')
exprat.findall(s)
['espagna09@gringo.es', 'toto.titi@tata.info', 'foo.bar@bla-bla.eu.org']
Et voilà!
On peut maintenant utiliser les fameuses chaînes de mails d'une manière amusante.
Le lecteur attentif aura remarqué la suppression des “^” et “$”.
Reflexion
Effectivement, si nous les avions gardé, nous aurons demandé au système de chercher uniquement les adresse email qui commencent par un “début de ligne” et qui se terminent par une “fin de ligne”.Dit autrement, nous aurions cherché les lignes qui ne contiennent qu'une seule adresse email propre (sans “<” ni “>”, ou autre...).
Il faut garder à l'esprit la signification de “^” et “$” pour remarquer cela.