Python – Extraction des contextes

Il s’agit donc ici de :
– récupérer le contexte,
– écrire le contexte dans un fichier par url,
– créer un fichier global des contextes, qu’on utilisera dans les étapes suivantes du projet (étiquetage avec le Trameur, nuages de mots, etc).

Voici la partie du script qui s’occupe des contextes :

contextes.jpg

Dans un premier temps, on crée des chaînes de caractères vides qui correspondent respectivement au contexte gauche et au contexte droit.

Ici, en ce qui concerne l’expression régulière, on n’a pas besoin de rechercher séparément les deux termes, comme on l’a fait pour le décompte des occurrences. Par contre, on la compile, à nouveau pour pouvoir utiliser le flag re.IGNORECASE pour ne pas tenir compte de la casse, comme pour le décompte des occurrences cette fois.

Ensuite, on ouvre le fichier dans lequel on a stocké le dump nettoyé. On va le parcourir ligne par ligne, et vérifier si la ligne en cours contient un des deux mots (ou leurs équivalents dans les différentes langues). Procéder ainsi permet de stocker dans la variable ‘lignes’ une liste de toutes les lignes du fichier. Ainsi, chaque a un index, du coup on a des numéros de ligne. Ce qui permet de récupérer la ligne cible ainsi que les trois précédentes et les trois suivantes. On ne veut qu’une ligne de texte, mais comme les dumps contiennent des sauts de ligne, on étend la distance.

On stocke chacune des lignes précédentes et suivantes dans une liste qu’on transforme ensuite en chaînes de caractères : ‘contexte_gauche’ et ‘contexte_droite’. On écrit ensuite ces chaînes de caractères ainsi que celle de la ligne cible (celle qui contient le mot) dans des fichiers séparés -qu’on referme ensuite) puis dans un fichier global de contextes.

Tous les fichiers de contextes, même les fichiers globaux, sont stockés dans le répertoire ‘CONTEXTES’. Il y a un fichier global par langue, dont le nom porte un indice ‘j’, qui correspond à un compteur de fichiers d’URLS (ce point sera développé dans un autre billet).

Python – Extraction des contextes

Python – nombre d’occurrences

A ce stade, on se demande combien de fois on va trouver les mots ‘censure’ et ‘tabou’, et/ ou leur traduction dans les langues suivantes : français, anglais, allemand, finnois et russe. Parce que la partie analyse du projet n’est pas la plus importantes, nous nous sommes limitées à une traduction de ‘censure’ et une traduction de ‘tabou’ dans chaque langue.

On recherche donc les occurrences grâce à des expressions régulières. On cherche séparément les occurrences de ‘censure’ et ‘tabou’ (et leurs équivalents) parce qu’on se demande si on trouve les deux termes dans un même contexte, ou s’il n’y aurait pas, plus ou moins, une sorte de distribution complémentaire. On parlerait plus de censure dans tels ou tels cas, à propos de telle ou telle thématique, etc.

occurrences

On a donc comme expressions régulières ce que montre l’image ci-dessus. On utilise [^\W] qui correspond l’inverse de tout caractère ne formant pas de mot, donc prend en compte les caractères accentués (ce que \w ne fait pas). Ce caractère peut être présent zéro ou plusieurs fois, ce qui permet de matcher les mots préfixés (« painosensuuri » en finnois, par exemple). On utilise la même chose pour décrire la fin du mot, ce qui permet de matcher les mots déclinés (ce dont on a besoin en finnois, en allemand et en russe).

Pour utiliser des expressions régulières en python, on a besoin du module ‘re’, qu’on a importé en début de script. Il m’a été nécessaire de compiler les expressions régulières avant de les utiliser avec findall. J’ai eu besoin de m’y prendre comme ça pour préciser le flag re.IGNORECASE, qui précise qu’on ne tient pas compte de la casse. C’est utile dans les cas où le mot que l’on cherche est en début de phrase, et commence donc par une majuscule.

On a initié un compteur avant de lancer la recherche, et à chaque fois que le motif est repéré, le compteur augmente de 1. On imprime, pour l’instant, le nombre d’occurrences pour vérifier que tout fonctionne bien. On ajoute également une colonne dans le tableau.

 

Python – nombre d’occurrences

Python – Ecriture et nettoyage des dumps

Pour l’écriture et l’encodage des dumps, le script se passe comme ça :

dump

Ecriture des dumps

Et là, au niveau de l’encodage nous avons en théorie deux possibilités : soit c’est de l’utf-8, soit c’est autre chose. En pratique, nous avons ici trois conditions if, à cause d’une url dont l’encodage n’est pas correctement détecté. Je n’ai pas trouvé pourquoi, alors j’ai rajouté une condition pour cet encodage (MacCyrillic), lignes 78 à 80, qui remplace l’encodage erroné par le bon.

On rappelle que le contenu, le code source, est stocké dans la variable ‘data’, issue de l’étape précédente (décompression et/ou aspiration des pages). Les codes sources déjà en utf-8 sont écrits tels quels. Ceux dont l’encodage est différent sont écrit en étant converties en utf-8. La conversion se passe en deux temps : d’abord on utilise l’encodage ‘de base’ pour récupérer les données qu’on convertit ensuite en utf-8 pendant qu’on les écrit dans le fichier dump.

 

Nettoyage des dumps : BeautifulSoup

On passe ensuite au nettoyage des dumps. Qu’entend-on par nettoyage ? Les dumps, à ce stade-là, ne sont en fait que les codes sources des pages qu’on a aspirées, stockés dans des fichiers txt. Et qui dit ‘code source’, dit ‘tout un tas de choses’ qu’on peut considérer comme du bruit autour du texte sur lequel on veut faire notre analyse. On trouve là dedans : tout le code html, mais aussi – et c’est plus problématique – des scripts javascript.

A priori, on peut supprimer plutôt facilement le code html au moyen d’expressions régulières, puisque celui-ci est constitué de balises entre chevrons. Or il y a deux problèmes : je n’ai pas réussi à utiliser d’expression régulière en mode multiligne (grâce aux flags .MULTILINE et .DOTALL du module re), et ça ne supprime pas le script javascript entre les balises html épnoymes.

Comme lors de l’étape précédente, après moult lectures, je trouve une pise sérieuse : « BeautifulSoup ». Un nom loufque qui impressionne la novice mais qui intrigue la curieuse. En fait, BeautifulSoup vient de la bilbilthèque bs4, et permet grâce à parser.html (un module) de créer un objet ‘soup’ qui est en fait le code html, étiqueté. Il y a pas mal de fonctions très intéressantes. Par exemple, on peut ‘ranger’ le code avec .prettify(), qui indente correctement ledit code. Mais on peut aussi, parce que nous ne sommes vraiment pas les premiers à vouloir extraire du texte d’une page html, récupérer directement le texte de la page au moyen de .get_text().

Vous vous souvenez, je vous ai dit que le javascript était plus problématique que le html… Eh bien voilà pourquoi : même avec la formidable BeautifulSoup, on ne parvient pas à s’en débarasser. Enfin si, on y arrive, mais pas du premier coup. En fait, il faut commencer par repérer tout ce qui est ‘rangé’ comme étant du script ou du style, et le supprimer. Et récupérer le texte après avec get_text(). C’est vraiment dans cet ordre-là que ça fonctionne le mieux. Après ce nettoyage, les dump sont presque nickel : il reste, parfois, une petite coquille dans un dump. Mais c’est vraiment négligeable par rapport à tout ce que j’ai obtenu en essayant autrement. Un autre point négatif : il reste beaucoup de lignes blanches dans les dump, on en tiendra compte dans la recherche de contexte.

Pour finir, avant d’écrire le dump propre dans un fichier, on rend le texte présentable avec une série de découpages (le texte en ligne, la ligne en « phrases » et les « phrases » en « chunks ») pour supprimer les derniers caractères avant de tout reconcaténer dans une variable ‘text’, celle qui contient le dump propre, et qu’on écrit. Soit dit en passant, on l’encode directement en utf-8, comme ça on est tranquilles.

Et on referme le fichier !

Python – Ecriture et nettoyage des dumps

Python – Aspiration des pages et encodage

Avant toute chose, voici la partie du script responsable de la récupération des pages :

aspi-url

La variable « liste » est le résultat de la commande  liste=fichierurls.readlines(), c’est donc le contenu d’un fichier urls, lu ligne par ligne, et stocké dans une liste. Liste que l’on parcourt url par url, en effectuant les traitements suivants.

Dans un premier temps, on supprime le dernier caractère de l’url. Je ne l’avais pas fait au départ, et j’avais énormément d’erreurs de requêtes HTTP. Il m’en reste encore quelques unes (ça fait partie des erreurs mentionnées dans mon billet précédent, celles que je corrigerai en dernier lieu).

Ce qui nous amène donc à l’aspiration de la page web à proprement parler. Pour ce faire, j’ai utilisé la bibliothèque urllib2 de python. La documentation officielle de python est ici

Dans un premier temps, on fait une requête : request = urllib2.Request(url), qui nous donne un objet Request (request), qui va nous servir à ouvrir l’url.

La ligne request.add_header(‘Accept-encoding’, ‘gzip’) permet de résoudre un problème lié à l’encodage et aux manipulations qu’on souhaite faire après. Pour récupérer l’encodage de la page, on utilise la fonction detect de chardet, qui renvoie une variable de type dictionnaire :

{'encoding': 'utf-8', 'confidence': 0.99}

C’est plutôt explicite : à gauche l’encodage, à droite un taux de fiabilité de la détection, allant de 0 à 1 (où 1 est à 100% fiable). Il est possible d’adapter les traitements que l’on veut appliquer après selon le degré de fiabilité de l’encodage détecté. En exécutant le script, je n’ai trouvé que des taux de ‘confidence’ très hauts, donc je n’ai pas rajouté de conditions.

Le problème dans un premier temps, c’était que pour la majorité des urls, j’obtenais ‘None’ en tant que ‘encoding’. Après de nombreuses recherches et lectures sur internet, j’ai cru comprendre que le contenu des pages était parfois compressés au moyen d’algorithmes de compression HTTP(ici), l’un de ces algorithmes étant gzip. Et dans ces cas-là, chardet.detect ne trouve pas d’encodage, qui est pourtant mentionné dans le code source. Un peu plus loin, j’ai lu que l’on pouvait utiliser une bibliothèque du nom de gzip, grâce à laquelle on peut vérifier si la valeur de ‘Content-Encoding’ était ou non « gzip ». Et si oui, décompresser ledit contenu.

Ce n’est pas tellement utile pour l’aspiration en elle-même, ça l’est pour l’encodage. Mais, comme on vient de le voir, il faut intervenir après la requête, et avant de stocker la page aspirée dans le dossier approprié. Et donc, pour extraire le nom de l’encodage a proprement parler, on extrait la valeur grâce à .values (c étant une variable de type dicitonnaire) la valeur du premier champ de « l’entrée » du dictionnaire (‘encodin’: ‘utf-8’).

Pour finir, on stocke la page dans le dossier des pages aspirées, en insérant le numéro de l’url dans le nom du fichier html. Un lien vers chaque page aspirée est inséré dans une colonne du tableau de résultats. Tableau dans lequel on rajoute par ailleurs une autre colonne où on indique l’encodage.

A ce stade, le tableau ressemble à ceci (mais en beaucoup plus long).

tableau_aspi-encodage

A propos de la lecture et de la fermeture de fichiers en python : on utilise des variables buffer (‘buf’ ou ‘response’, par exemple), comme objet intermédiaire dans les traitements.

Les dernières lignes qu’on voit sur la capture écran servent à créer les fichiers dans lesquels on va stocker les codes sources des pages, et à ouvrir les fichiers en question (un seul fichier à la fois, par url).

Python – Aspiration des pages et encodage

Reprise des pythonneries !

Bonjour à toutes et à tous !

Comme vous avez eu l’occasion de le constater, les aventures du projet en python sont tombées dans les limbes de ce blog. Faute de progrès significatif, je n’osais pas poster de nouveaux billets simplement pour dire que je n’avançais pas. C’était une erreur de ma part : une âme charitable  m’aurait sûrement aidée ; mais je n’ai pas osé. Je note pour plus tard.

Ceci dit, la version python du projet est maintenant finie ! Elle n’est pas parfaite, donc je n’oserai pas dire qu’elle est « bouclée » ; j’ai encore quelques erreurs à régler, et je le ferai si j’en ai le temps lorsque nous aurons bouclé le site (sachez, au passage, que l’architecture de celui-ci est finie et qu’il est en cours de remplissage – mais ce sera l’objet d’un autre billet). Ceci dit, le script tourne quand-même, alors je ne suis pas mécontente.

Il faut dire qu’avant de plancher sur ce projet, mon expérience en python se limitait à peu près à ouvrir une liste, la chatouiller un peu, et la refermer. Il y a donc eu pas mal de moments qu’on pourrait qualifier familièrement de « galère », à tel point que j’ai failli abandonner. Et puis finalement non, ça avait l’air de valoir le coup. Du coup mes billets n’arrivent que maintenant.

Je m’en vais donc vous raconter dans les billets suivants les principales péripéties de la Version Python.

Reprise des pythonneries !