05
May

Prototypage Rapide : Créer son premier Data Crawler.

Il y a quelques mois j’entreprenais avec une amie travaillant dans le domaine de la santé de prototyper un nouveau service d’accompagnement des personnes ayant à charge un proche en situation de dépendance (Maladie, Alzheimer, Handicap, …). Je fais parti de ceux qui pense qu’il faut visualiser au maximum ce qu’on pense et tester au maximum ce que l’on pense bien, pour parfois vite se rendre compte des problèmes.

Bref, revenons à notre sujet. le data Crawler. Dans ce projet, comme souvent au début de projets, on a besoin de se constituer une base de données pour réfléchir, ou pour construire son prototype de service. Et c’est dans ce genre de situation que nous nous rendons compte que l’open-data, c’est encore bien une tendance plus qu’une réalité. Dans ce cas comment fait-on ? Et bien, comme peuvent le faire les moteurs de recherche comme Google ou Bing, nous allons développer notre propre robot d’exploration qui va récupérer de façon automatique les données qui nous intéresse.

Cet article s’adresse à tous, donc je ne vais pas expliquer comment développer un crawler complet, mais plutôt comment faire son petit script pour récupérer des données depuis un site internet.

Le Problème : Dans le cadre de mon projet, je souhaitais pouvoir récupérer la site de tous les Centre Locaux D’information et de Coordination (CLIC) disponible sur ce site  pour élaborer un outil de recommandation géolocalisé. Pour faire quelques essais j’avais donc besoin de me constituer une base de données. Seulement voilà, quand on clique sur “France entière” on se retrouve avec un listing de 556 pages de 5 résultats. Je n’allais pas tout faire à la main, alors je me suis lancé dans la confection de deux petits scripts qui ont fait tout le travail pour moi.

  1. Comprendre le mécanisme de navigation
  2. Récupérer les données
  3. Data Cleaning
  4. Intégration dans une base SQL

 

Comprendre le mécanisme de navigation 

Tout d’abord nous allons chercher à comprendre comment fonctionne le système de recherche sur le site en question. Pour cela nous allons nous munir de Firefox et l’extension Firebug.

Puis nous nous rendons sur le listing France entière évoqué plus haut. On active Firebug, puis on va dans l’onglet Réseau. S’il n’est pas activé on l’active. Puis on change de page dans le listing.

crawler_2

On voit apparaitre dans le panneau réseau un ligne intitulée POST rechercher.do. Ce script est également celui présent dans l’url de la page et est donc celui qui comprend comment nous lui demandons de passer d’une page à l’autre. POST indique que d’une page à l’autre, le script a demandé à notre navigateur de lui transmettre des informations.

Grâce à Firebug, nous allons dans le sous onglet Post pour y retrouver la liste des différentes variables. On identifie 21 variables, dont certaines très intéressantes pour nous. On identifie très rapidement la variable indiquant la page courante et la page précédente ainsi que d’autres paramètres. Tout en bas Firebug nous montre la structure originale de la requête POST.

Maintenant que nous avons compris comment le script rechercher.do reçoit des commandes de notre navigateur, nous allons écrire un script qui va simuler l’envoie de commande par notre navigateur, et récupérer aussi vite que notre ordinateur le peu les données.

Récupérer les données

Pour la suite, vous devez avoir sur votre ordinateur un serveur virtuel permettant d’exécuter du PHP. Pour cela vous pouvez installer très simplement cela avec un des logiciels suivant: XAMPP, EasyPHP, MAMP ou LAMP.  Je n’expliquerai pas l’installation du serveur, ni comment nous exécutons un script PHP en local. Vous trouverez nombre de tutoriaux sur le net décrivant cela.

Pour faire court, lors de l’installation d’un de ces logiciels, un répertoire www est créé sur votre ordinateur (consulter la FAQ du logiciel pour savoir où). Il vous suffit de positionner un simple fichier texte en remplacant l’extension de ce fichier par .php. Ensuite rendez vous dans votre navigateur internet, et tapez http://localhost/mon_script.php pour que celui-ci s’exécute.

<?php 
	//URL de départ
	$url = 'http://clic-info.personnes-agees.gouv.fr/clic/repclic/rechercher.do';
	//boucle de navigation
	$NB_PAGES=112 ; 
	for($i=1; $i<=$NB_PAGES; $i++){
		$m=$i-1 ; if ($i==0) $m=1 ; 
	//Definition des variables POST
	$fields_string="typeRecherche=GEOGRAPHIQUE&idTerritoire=&type=2&typeDetaille=2&statut=&nivLabel=&nbHabitants=&activite=&oldPageNumberClicsTrouves=$m&pageNumberClicsTrouves=$i&linesPerPageClicsTrouves=5" ; 
	//open connection
	$ch = curl_init();
	/*On indique à curl de nous retourner le contenu de la requête plutôt que de l'afficher*/
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
	/*On indique à curl de ne pas retourner les headers http de la réponse dans la chaine de retour*/
	curl_setopt($ch, CURLOPT_HEADER, false);
	/*On indique à curl de suivre les redirections par le header http location*/
	curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
	//set the url, number of POST vars, POST data
	curl_setopt($ch,CURLOPT_URL, $url);
	curl_setopt($ch,CURLOPT_POST, 11);
	curl_setopt($ch,CURLOPT_POSTFIELDS, $fields_string);
	//execute post
	$result = curl_exec($ch);
	echo $result  ; 
	}
	//close connection
	curl_close($ch);
?>

Vous devriez à présent voir apparaître le contenu brut de la page comme ceci
crawler_3

Data Cleaning

Nous avons récupéré jusque là le contenu brut de la chaque page que nous voulions. Cependant, nous l’avons récupéré tel quel, et nous ne pouvons l’exploiter simplement. Ce que nous allons faire, c’est que nous allons enregistrer la page html obtenue au format HTML depuis le navigateur web (Fichier > Enregistrer sous… > datas.html ).

Nous allons maintenant retraiter toutes les données afin de les recueillir sous la forme d’un fichier CSV que nous pourrons facilement exploiter dans une base de donnes (MySQL) ou dans un tableur (Excel, Numbers, …). Pour cela nous allons utiliser la manipulation de l’arbre DOM en PHP et des requetes xpaths pour isoles les éléments qui nous intéresse. Malheureusement je ne vous expliquerez pas ici ce qu’est le DOM, ni tout sur xPath, ce serait bien trop long. Toutefois l’explicatione et l’utilisation de ces concepts se fait aisément pour notre sujet grâce à d’autres articles présents sur le web.

Dans le listing, chaque fiche d’information est représentée par un tableau de 4 colonnes. La première ligne contenant le titre est facilement identifiée par la classe tableHeader. Les autres lignes du tableau sont identifiées par la classe tableCell. Ce que l’on va faire, c’est d’abord trouver un pointeur sur chaque type de ligne grâce à xPath. Puis récupérer les cellules qui nous intéressent. Une fois ce contenu acquis, on va le retraiter pour nettoyer les données.

Voici ce que cela donne comme script:

//Variable les données traitées
$final="" ; 
//Variables de boucles
$entete=0 ; 	//Correspond à l entete de la fiche $entete
$content=0 ; 	//Correspond au contenu de la fiche $content
$te ; 
$tc ; 
$boucle=1 ; 	//Permet d'incrementer les boucle sur la dimension du tableau HTML (4 colonnes) 

$dom = new DOMDocument();
@$dom->loadHTMLFile("data.html");

$xpath = new DOMXPath($dom);
//nom et niveau des clics
$elements = $xpath->query("//td[@class='tableHeader']");
foreach($elements as $e){
  		$te[$entete][$boucle]=$e->nodeValue;
  		
  		$boucle++;
  		if($boucle>4){$boucle=1 ; $entete++;}
}

//data fiche
$elements = $xpath->query("//td[@class='tableCell']");
$boucle=1 ;
foreach($elements as $e){
  		$tc[$content][$boucle]=$e->nodeValue;
          
          if($e->nodeValue=='e-mail'){
             	foreach( $e->getElementsByTagName('a') as $l ) {
             		$tmp = $l->getAttribute('href') ; 
             		$tmp= explode('to:',$tmp) ; 
  				$tc[$content][$boucle]=$tmp[1];
			 }
         	}
         	if($e->nodeValue=='Site web' ){
             	foreach( $e->getElementsByTagName('a') as $l ) {
  				$tc[$content][$boucle]=$l->getAttribute('href');
			 }
         	}
  		$boucle++;
  		if($boucle>10){$boucle=1 ; $content++;}
}

//rendu final 
//Initialisation des entetes des colonnes du fichier CSV (a retirer si le fichier CSV est pour un import MySQL)
echo 'id;Nom;Adresse;Tel;Email;Site;Gestionnaire;Niveau;Statut;TelG;dep;cp
' ; 

//Construction et cleaning des fiches
$contentmax=$content ; 
$content=0 ; 
foreach($te as $e){
	if($content<$contentmax) {  		$tel = trim($tc[$content][2]); 		if(!empty($tel)){ 		   $tmp = explode(':', $tel); 		if(count($tmp)>1)
		   $tel = trim($tmp[1]) ; 
		}
		
		$telg = $tc[$content][9] ;
		if(!empty($telg)){
		   $tmp = explode(':', $telg);
		if(count($tmp)>1)
		   $telg = trim($tmp[1]) ; 
		}

 		$nom = utf8_decode($e[1]) ; $nom = str_replace('"','',$nom) ; 
 		$adresse = trim(utf8_decode($tc[$content][1].' '.$tc[$content][4].' '.$tc[$content][7])) ; $adresse =str_replace('"','',$adresse) ;
 		$email = trim($tc[$content][5]) ;
 		$gestionnaire = trim(utf8_decode($tc[$content][3])) ; $gestionnaire =str_replace('"','',$gestionnaire);
 		$statut = utf8_decode($tc[$content][6]) ; $tmp = explode(':' , $statut) ; $statut = trim($tmp[1]) ;  $statut = str_replace('"','',$statut) ;
 		$site = trim($tc[$content][8]) ; if(strlen($site)<6){$site="" ; }
 		$site = str_replace('localhost:8888/Gerontologie/', '', $site) ; 
 		$commune = trim($tc[$content][7]);
 		$dep = substr($commune, 0, 2) ; 
 		$cp = substr($commune, 0, 5) ; 
 				 
 		$niveau = $e[4] ; $tmp = explode(':' , $niveau) ; $niveau = trim($tmp[1]) ;  
		$id= $content+1 ; 
		
		//export CSV
                echo '"'.$id.'";"'.$nom.'";"'.$adresse.'";"'.$tel.'";"'.$email.'";"'.$site.'";"'.$gestionnaire.'";"'.$niveau.'";"'.$statut.'";"'.$telg.'";"'.$dep.'";"'.$cp.'"' ; 
	    $content++;
	}
}
echo $final  ; 

Copiez le contenu de votre navigateur dans un fichier texte.
Ouvert dans le Bloc-note, Notepad++ ou smultron, votre fichier devrait ressembler à quelque chose comme celacrawler_4

Pour un import dans une base de données il reste quelques mini-corrections à effectuer à la main pour corriger quelques fautes d’accents ou encore des erreurs déjà présentes dans les données source.

 

Intégration dans une base de données MySQL

L’import se fait en quelques étapes :

  1. Créez une nouvelle table dans votre base de données
  2. Supprimez de votre fichier, si elle est présente, la ligne de description des colonnes
  3. Allez dans le panneau d’import de votre table.
  4. Choisissez ” comme delimiter, et ; comme column separator
  5. Importez votre fichier et laissez faire.

Voilà, si vous avez réussi à faire cela vous avez quelques bases pour récupérer des données de façon plus automatique depuis un site internet pour alimenter vos réflexions et projets. Ce qui a été présenté ici est bien sûr très simplifié. Pour faire un véritable crawler nous pourrions aller beaucoup plus loin et écrire un programme autonome adoptant une démarche récursive. Mais si vous avez besoin de faire cela, prévoyez quelques week-ends devant vous 🙂