Recrutement pour création d'un moteur de recherche

juki_webmaster Messages postés 947 Date d'inscription mercredi 19 novembre 2003 Statut Membre Dernière intervention 5 avril 2008 - 19 févr. 2004 à 15:46
einsteinkta Messages postés 1 Date d'inscription jeudi 14 mai 2009 Statut Membre Dernière intervention 27 juillet 2011 - 27 juil. 2011 à 13:46
Bonjour .
Je travaille en parallele sur la création d'un moteur de recherche pour www.invitia.com
Si ya klk1 ou klk personnes qui voudrais m'aider à concevoir un robot capable dexaminer les balises meta d'un site et les stoquer dans une basse sql merci de me contacter .
Je suis desoler mais la remuneration est nulle .

3 réponses

nadia_boulakakez Messages postés 5 Date d'inscription jeudi 26 mai 2005 Statut Membre Dernière intervention 9 août 2005
21 nov. 2005 à 08:52
nadia
bonjour j'ai qqe trucs :

............................................................................. 1ere étape .........................................................


Autant le dire tout de suite, le but de cette série n'a pas la prétention de faire de l'ombre à Google... Loin s'en faut.

Le but ici est de se construire un petit moteur de recherche basique, capable de faire remonter les pages de notre site correspondant au(x) critère(s) de recherche (seul le singulier s'appliquera dans un premier temps).
Il nous faut pour cela adopter une politique d'indexation du contenu de nos pages, c'est l'objet du présent tutoriel.

Le poids des mots...


Nous effectuerons nos tests sur cinq tutoriels issus du site JDN Développeurs, certains se trouveront à la racine de notre site de test, tandis que d'autres seront placés dans des sous-répertoires.

Il sera souhaitable, dans le futur, de se construire une interface permettant d'automatiser l'indexation pour un site ou un répertoire donnés, capable de gérer également les sous-répertoires.

Pour ce premier tutoriel de la série, cette interface n'est pas disponible, aussi c'est "en dur" que sera inscrit le nom du fichier HTML à indexer.

De nombreux traitements sont à effectuer sur ce fichier HTML. Le but est en effet d'obtenir à la fin de ce tutoriel une liste de mots issus de ce fichier, d'au moins trois lettres, avec leur fréquence d'apparition, le tout dans une base de données.


Celle-ci comportera (sous réserve de modification ultérieure) 5 champs :
(table "moteur")
- "id", la clef primaire...
- "mot"
- "occurence", stocke le nombre d'apparition du mot dans le fichier concerné
- "origine" emplacement du fichier concerné
- "titre_page"


Revenons sur les deux derniers champs : "origine" et "titre_page". Le premier permet d'identifier de manière unique le fichier auquel se rapporte un mot et son nombre d'occurences, le second stocke le titre de la page.

De plus, nous accordons l'équivalent de 10 occurences pour un mot qui apparaît dans le champ "title" du fichier HTML, c'est un choix arbitraire mais paramétrable. Nous estimons en effet qu'un mot apparaissant entre les tags <TITLE></TITLE> possède une connotation particulière qu'il convient de prendre en compte.


Le script d'indexation

Nos tests sont effectués sous Windows avec EasyPhp. Par défaut le temps maximal alloué à l'exécution d'un script est de 30 secondes (vérifiez avec un "phpinfo()"). Dans l'état actuel de notre script (non optimisé pour l'instant) cela peut s'avérer insuffisant, même en local donc.
Ne parlons pas des hébergeurs mutualisés où ce type de script risque d'être mal vu.

Ce "moteur" n'est pas prévu pour indexer en ligne le contenu d'un site. Il faudra passer par une phase intermédiaire, en local. Cette étape aboutira à la création (ou la mise à jour) de la table "moteur".


La première étape est donc d'augmenter ce temps maximal d'exécution :

set_time_limit(300);


Il nous faut ensuite récupérer le contenu du fichier sous une forme où nous pourrons lui appliquer quelques-unes des nombreuses fonctions dédiées à la gestion des chaînes de caractères.


$contenu = join('', file("chaines_python.html"));

"chaines_python.html" est le fichier source de notre tutorial.
La fonction "file" est l'équivalent de "readfile" à ceci près qu'elle renvoie le contenu du fichier ligne par ligne, dans un tableau. Démonstration avec les premières lignes de notre fichier HTML :

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Manipuler les cha&icirc;nes de caract&egrave;res en Python</title>
</head>

Notre tutoriel pr&eacute;c&eacute;dent sur le Python &eacute;tait
[http://developpeur.journaldunet.com/tutoriel/pyt/011211pyt_intro.shtml une

Si nous avions uniquement le code suivant :

$contenu = file("chaines_python.html");
for ($i=0;$i<=7;$i++)
echo("ligne[$i] : $contenu[$i]");

... Nous aurions obtenu le code source HTML suivant...

ligne[0] : <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
ligne[1] : <html>
ligne[2] : <head>
ligne[3] : <title>Manipuler les cha&icirc;nes de caract&egrave;res en Python</title>
ligne[4] : </head>
ligne[5] :
ligne[6] : Notre tutoriel pr&eacute;c&eacute;dent sur le Python &eacute;tait
ligne[7] : ]une

... Illustrant ainsi la propriété de la fonction "file()".

Or nous avons besoin de transformer tout ceci en chaînes de caractères afin d'utiliser d'autres fonctions Php, ce sera le rôle de la fonction "join()", capable de fusionner une chaîne de caractères (vide ici) et un tableau ("contenu").

Nous obtenons alors une chaîne de caractères semblables à notre fichier HTML.


La prochaine étape "nettoie" cette chaîne à l'aide des fonctions "strip_tags", "strtolower" et "htmlspecialchars". Celles-ci permettent respectivement d'éliminer les tags HTML et Php, de tout passer en minuscule, et de convertir les caractères spéciaux en code HTML :

$contenu = strip_tags($contenu);
$contenu = strtolower($contenu);
$contenu = htmlspecialchars($contenu, ENT_QUOTES);


L'utilisation du paramètre facultatif "ENT_QUOTES" dans la dernière fonction permet d'encoder à la fois les doubles (par défaut) et les simples quotes.

Grâce à la communauté Php, et plus particulièrement à Nexen.net sur cet exemple précis, nous allons réutiliser deux scripts qui vont nous permettre d'isoler les mots issus de notre chaîne de caractères :

(Fonction split_words )

function split_words($string)
{
$retour = array();
$delimiteurs = " .!?,:(){}[]%\/";
$tok = strtok($string, " ");
while (strlen(join(" ", $retour)) != strlen($string))
{
array_push($retour, $tok);
$tok = strtok ($delimiteurs);
}
return array_non_empty($retour);
}


Notons certaines des fonctions Php utilisées ici :
- strtok : Découpe une chaîne de caractères selon les délimiteurs qui lui sont passés en paramètre.
- array_push : "Entasse" des éléments dans un tableau.


Cette première fonction retourne la chaîne passée en paramètre dans un tableau de mots en conservant l'ordre de la chaîne. Elle utilise une seconde fonction pour supprimer les entrées vides d'un tableau :

(Fonction array_non_empty )
function array_non_empty($array)
{
$retour = array();
foreach ($array as $a)
{
if ((!empty($a)) && (strlen($a) >= 3))
{
array_push($retour, trim($a));
}
}
return $retour;
}

Nous avons rajouté dans cette fonction le test sur la longueur de l'élement, qui doit être au moins égal à trois caractères (nous ne souhaitons pas stocker des mots plus petits) ainsi que le "trim" afin de supprimer les espaces susceptibles d'entourer un mot (au début ou à la fin).
Notez l'utilisation de l'instruction "foreach".
Nous ne rentrons pas dans le détail de toutes les fonctions employées, elles sont bien documentées sur le site Php.net, une traduction en français est d'ailleurs disponible.


Avec :

$contenu = split_words($contenu);

... Nous obtenons donc un tableau de mots (dont le contenu pourrait être optimisé mais nous nous en tiendrons à cette version pour l'instant).

Puis :

$nb_mots = count($contenu);


... Nous renseigne sur le nombre de mots contenus dans le tableau. Nous pouvons désormais programmer un traitement pour chacun des mots.


Ce sera chose faite avec :

$contenu = array_count_values($contenu);


$host = "localhost";
$user = "root";
$pass = "";
$bdd = "jdn";


$idconnect= mysql_connect($host,$user,$pass) or die("Pb de connexion");
mysql_select_db("$bdd") or die("Pb de connexion");


while (list ($key, $val) = each ($contenu))
{
echo ("$key => $val
");
$req="insert into moteur (mot, poids, origine, titre_page) values ('$key', $val, 'origine', 'titre_page')";
$idreq=mysql_query($req);
if ($idreq == 0)
{
echo("Une erreur est survenue !
");
mysql_close($idconnect);
exit;
}


Nous utilisons ici tout d'abord la fonction array_count_values bien résumée ici par l'exemple donné par Php.net :

$array = array (1, "hello", 1, "world", "hello");
print_r(array_count_values ($array));

On obtient à l'affichage :
(
[1] => 2
[hello] => 2
[world] => 1
)

C'est exactement ce que nous recherchons pour notre "moteur de recherche", une liste de mots suivi du nombre d'occurences.

A la suite de cette fonction on trouve un script de connexion basique pour se connecter en local via Easyphp sur notre base de données prenommée ici "jdn".

Le principe est ensuite d'extraire chaque couple de notre tableau généré par la fonction array_count_values. Nous utilisons les fonctions très puissantes each et list pour cette tâche. Nous ne gérons pas pour l'instant l'origine du mot et le titre de la page, pour lesquels nous insérons systématiquement la même chaîne de caractères.

Voici le script de création de la table "moteur"...

CREATE TABLE moteur (
id int(4) unsigned NOT NULL auto_increment,
mot varchar(100) NOT NULL default '',
poids mediumint(9) NOT NULL default '0',
origine varchar(255) NOT NULL default '',
titre_page varchar(255) NOT NULL default '',
PRIMARY KEY (id)
)

... et les quatre premiers affichages produits par notre script (utiles à des fin de debug par exemple) :

manipuler => 1
les => 13
cha&icirc;nes => 2
caract&egrave;res => 11


Parallèlement à cet affichage, les valeurs en question sont insérées dans la base. Ainsi, au mot "manipuler" correspond un poids de "1", pour "les", celui-ci se monte à 13 occurences.

Nous verrons entre autres dans le prochain tutoriel de cette série comment compléter notre table avec les champs "titre_page" et "origine".

Bien sûr d'autres approches existent sur le même thème, afin de patienter jusqu'au prochain tutoriel vous pouvez consulter la rubrique dédiée aux outils de recherche sur JDN Solutions, ou décortiquer ces scripts présents sur Phpinfo.net.


.................................................................. étape 2 .............................................................................


Résumons l'épisode précédent : nous avons conçu un script Php capable d'indexer le contenu d'un fichier HTML référencé "en dur" au sein du code source. Une table SQL a été construite,elle se nomme "moteur" et voici pour rappel les champ qu'elle contient :

- "id", la clef primaire...
- "mot"
- "occurence", stocke le nombre d'apparition du mot dans le fichier concerné
- "origine" emplacement du fichier concerné
- "titre_page"


Si les trois premiers champs sont évidents, on peut rappeler l'utilité des deux derniers. Il s'agit pour le champ "origine" d'indiquer d'où vient le fichier. Deux fichiers peuvent en effet porter le même nom, il va donc falloir les dissocier du point de vue de leur arborescence.
Enfin, "titre_page" permettra d'identifier une page lorsqu'elles seront affichées comme résultats d'une recherche.

Le programme du jour est le suivant :

- Construire une interface rudimentaire afin d'indexer un fichier plus simplement (c'est à dire sans avoir besoin de rentrer son nom en dur dans le code source).
- Repérer les mots contenus dans les balises <TITLE></TITLE> et leur appliquer un poids "+ 10" pour marquer leur importance par rapport aux autres mots.
- Renseigner le champ "titre" de la table "moteur".

Note : les fonctions Php déjà utilisées lors du premier tutoriel ne referont pas l'objet ici d'une explication détaillée. Le code source complet (à ce stade du développement) sera fourni en fin de tutoriel



Construction de l'interface


Celle-ci doit nous permettre de parcourir l'arborescence de notre disque et de choisir un fichier à indexer. Cette solution ne représente toujours pas la panacée mais elle a le mérite d'être dynamique, c'est un pas en avant par rapport au script du premier tutoriel qui spécifiait en dur le nom du fichier à indexer.

Ce type de fonctionnalités se rencontre le plus souvent sur le web lorsque vous avez à uploader un fichier quelconque, nous nous inspirons de ce type d'interface.

Schématiquement nous allons désormais dissocier deux cas dans notre script Php :

Faut-il entamer les traitements Php et indexer le fichier ou afficher l'interface "parcourir" qui va permettre de choisir le fichier en question ?

Nous allons procéder de la sorte :

(find_title.php)
if (isset($passtwo))
{
// Traitements Php...


}
else
{
?>
// Tient en une ligne normalement :
<form name ="browse" action= "find_title.php"
method="post" enctype="multipart/form-data">

</form>
<?
}


Lorsque vous exécutez pour la première fois ce script, vous avez la possibilité de parcourir votre disque afin de désigner le fichier à indexer.
Une fois votre choix effectué le nom du fichier choisi apparaît dans le champ du formulaire. Ce dernier contient une variable de type "hidden", elle se nomme "passtwo" et a pour seul intérêt de permettre de détecter que le formulaire a été posté.
En effet, la page s'auto-envoie les données du formulaire, comme cela est définit dans le champ "action" de celui_ci.

Lorsque la page est soumise, le champ de type "hidden" est détecté, via la fonction isset(), les traitements Php vont pouvoir commencer.


Repérage des mots inclus dans les balises <TITLE></TITLE>


Nous allons, une fois encore, profiter d'un script déjà existant dans la bibliothèque de Nexen.net, tout en l'adaptant à nos besoins.


................................................ script ..........................................................................


<!--$Id: script-nexen.php,v 1.2 2003/12/01 11:38:43 publi Exp $-->


<!--$Id: connectlib.php,v 1.1 2001/11/20 09:04:45 sam Exp $-->


<!--$Id: commun.inc,v 1.3 2004/07/01 13:17:45 publi Exp $-->


<!--$Id: db.inc,v 1.3 2004/07/01 13:17:45 publi Exp $-->


<!--$Id: menu.inc,v 1.3 2004/08/21 02:50:17 publi Exp $-->


<?php


/*


*


*


* Avertissement : Cette librairie de fonctions PHP est distribuee avec l'espoir


* qu'elle sera utile, mais elle l'est SANS AUCUNE GARANTIE; sans meme la garantie de


* COMMERCIALISATION ou d'UTILITE POUR UN BUT QUELCONQUE.


* Elle est librement redistribuable tant que la presente licence, ainsi que les credits des


* auteurs respectifs de chaque fonctions sont laisses ensembles.


* En aucun cas, Nexen.net ne pourra etre tenu responsable de quelques consequences que ce soit


* de l'utilisation ou la mesutilisation de ces fonctions PHP.


*/


/****


* Titre : Titre d'une page HTML


* Auteur : naholyr


* Email : naholyr@yahoo.fr


* Url : neoscripts.free.fr


* Description : recupere le titre d'une page HTML.


Si aucun titre, retourne FALSE.


****/


function Get_Title($fichier){


static $fp, $ligne, $regs;


$fp = @fopen($fichier, "r");


if (!$fp) return FALSE;


$ligne = fgets($fp, 1024);


while (!eregi("<TITLE>(.*)</TITLE>", $ligne) and !feof($fp)) $ligne = fgets($fp, 1024);


if (eregi("<TITLE>(.*)</TITLE>", $ligne, $regs)) return $regs[1];


else return FALSE;


}


?>


............................................... fin script ........................................................................

A l'origine ce script ouvre un fichier et récupère, lorsque c'est possible, le contenu des balises <TITLE></TITLE>, via l'utilisation de la fonction eregi(). Celle-ci est semblable à la fonction ereg() à ceci près qu'elle ne tient pas compte de la casse.

Ces fonctions permettent de retrouver dans une chaîne de caractères les séquences de caractères désignées par une expression régulière (Pour plus d'informations sur les expressions régulières, reportez-vous à ce tutoriel qui les utilise).


Après modification, il ne reste dans notre cas que la première ligne suivante :

if (eregi("<TITLE>(.*)</TITLE>", $str_ini, $regs))
{
$str_titre = split_words($regs[1]);
// On obtient un tableau de mots
$nb_titre = count($str_titre);

for($j=0;$j<$nb_titre;$j++)
{
$str_tmp = strip_tags($str_titre[$j]);
$str_tmp = strtolower($str_tmp);
$str_tmp = htmlspecialchars($str_tmp, ENT_QUOTES);

// Suite des traitements Php...
}
}


La fonction eregi() lorsqu'elle est couplée à un troisième paramètre, permet de récupérer le contenu de ce qui a été trouvé dans un tableau, ici regs[1] contient ce que nous cherchons. Le "1" correspond au contenu de la première parenthèse contenant le masque de recherche, ici "(.*)".

Nous avons donc récupéré le titre de notre page HTML, dans une chaîne de caractères, il nous faut maintenant, comme lors de la première étape d'indexation, découper mot à mot ce résultat.
Nous appliquons donc les mêmes fonctions qu'à l'étape précédente afin d'encoder les mots du titre de la même manière que leurs homologues contenus dans le corps du texte.


Dernière étape (du jour) : gérer le poids des mots issus du titre.


Le principe à adopter ici est le suivant : comme nous l'avions stipulé lors du premier tutoriel, lorsqu'un mot est contenu dans le titre, nous estimons qu'il "pèse" plus lourd qu'un "simple" mot contenu dans le corps du texte. Aussi, nous avons décidé (mais libre à vous de faire autrement), d'ajouter systématiquement, pour tout mot issu des balises <TITLE></TITLE> l'équivalent de 10 occurences supplémentaires.


Etant donné que cette opération intervient après l'indexation du fichier, il y a de bonnes chances pour que les mots du titre soient déjà indexés. Il faut donc retrouver le poids (le nombre d'occurences) qu'il possède déjà, et augmenter celui-ci de 10 unités.
Si le mot n'existe pas déjà dans la base, on le crée avec un poids initial de 10.

Nous reprenons la boucle "for" précédente en lui ajoutant le code correspondant :

//On tente de récupérer le poids associé au mot traité
$req_poids ="select poids from moteur where mot = '$str_tmp'";
$idreq=mysql_query($req_poids);
if ($idreq == 0)
{
echo("erreur recup poids
");
mysql_close($idconnect);
exit;
}

//Si le mot existe déjà, on ajoute 10 à son poids
if (mysql_num_rows($idreq) != 0)
{
while ($row=mysql_fetch_array($idreq))
{
$poids_titre = $row[poids];
}

$poids_titre = $poids_titre + 10;

// Mise à jour du poids du mot concerné
$req_upd="update moteur set poids = $poids_titre where mot = '$str_tmp'";
$idreq_upd=mysql_query($req_upd);
if ($idreq_upd == 0)
{
echo("erreur maj poids
");
mysql_close($idconnect);
exit;
}
}

Rien de sorcier ici, nous appliquons uniquement les principes dictés précedemment.


Il nous reste enfin à mettre à jour le champ "titre_page" de la table "moteur". Nous récupérons le contenu de la variable $regs[1] dans lequel se trouve le titre (nous l'avons expliqué en début de page). La requête à appliquer est donc la suivante :


// Mise à jour du titre
$req_titre="update moteur set titre_page = '$regs[1]'";
$idreq_titre=mysql_query($req_titre);
if ($idreq_titre == 0)
{
echo("erreur maj titre
");
mysql_close($idconnect);
exit;
}


Si ces morceaux de code vous paraissent un peu décousus, c'est normal, aussi nous concluerons ce tutoriel avec l'intégralité du script Php, commenté. Il reprend l'intégralité de ce que nous avons vu jusqu'à maintenant.
Nous vous rappelons que le script de création de la table "moteur" se trouve dans le tutoriel précédent.
Afin que tout soit complet et que vous puissiez tester le tout chez vous, voici également le fichier HTML du tutoriel sur lequel nos tests sont effectués jusqu'à présent.

Au menu de la prochaine étape : la récursivité lors de la recherche de fichiers à indexer (entre autres).


.............................. intégralité du script .........................................................................................................


<?php


if (isset($passtwo))


{


set_time_limit(300);


function array_non_empty($array)


{


$retour = array();


foreach ($array as $a)


{


if ((!empty($a)) && (strlen($a) >= 3))


{


array_push($retour, trim($a));


}


}


return $retour;


}


function split_words($string)


{


$retour = array();


$delimiteurs = " .!?,:(){}[]%\/";


$tok = strtok($string, " ");


while (strlen(join(" ", $retour)) != strlen($string))


{


array_push($retour, $tok);


$tok = strtok ($delimiteurs);


}


return array_non_empty($retour);


}


$str_ini = join('', file($nomfichier));


// file renvoie un array ou chaque case est une ligne du fichier.


// join fusionne un array et une string, ici chaine vide : on transforme un array en string


$contenu = strip_tags($str_ini);


// Supprime les tags php et html d'une chaine de caracteres.


$contenu = strtolower($contenu);


$contenu = htmlspecialchars($contenu, ENT_QUOTES);


$contenu = split_words($contenu);


// On obtient un tableau de mots


// Associe chaque mot avec son nombre d'occurences


$occurences = array_count_values($contenu);


// Connection pour une base nommée "jdn", en local, via EasyPhp.


$host = "localhost";


$user = "root";


$pass = "";


$bdd = "jdn";


$idconnect=mysql_connect($host,$user,$pass) or die("Impossible de se connecter à la base de données"); // Le @ ordonne a php de ne pas afficher de message d'erreur


mysql_select_db("$bdd") or die("Impossible de se connecter à la base de données");


// Insère les mots et leurs occurences, on ne se preoccupe pas encore de l'origine ou du titre de la page


while (list ($key, $val) = each ($occurences))


{


//echo ("$key => $val
");


$req="insert into moteur (mot, poids, origine, titre_page) values ('$key', $val, 'origine', 'titre_page')";


$idreq=mysql_query($req);


if ($idreq == 0)


{


mysql_close($idconnect);


exit;


}


}


// Repérage du titre et comptage


if (eregi("<TITLE>(.*)</TITLE>", $str_ini, $regs))


{


$str_titre = split_words($regs[1]);


// On obtient un tableau de mots


$nb_titre = count($str_titre);


for($j=0;$j<$nb_titre;$j++)


{


// On applique au mot du titre traité les mêmes traitements que lors de l'indexation des mots


// Des fonctions seraient utiles ici (à venir).


$str_tmp = strip_tags($str_titre[$j]);


// Supprime les tags php et html d'une chaine de caracteres.


$str_tmp = strtolower($str_tmp);


// Passe tout en miniscule


$str_tmp = htmlspecialchars($str_tmp, ENT_QUOTES);


//On tente de récupérer le poids associé au mot traité


$req_poids="select poids from moteur where mot = '$str_tmp'";


$idreq=mysql_query($req_poids);


if ($idreq == 0)


{


echo("erreur recup poids
");


mysql_close($idconnect);


exit;


}


//Si le mot existe déjà, on ajoute 10 à son poids


if (mysql_num_rows($idreq) != 0)


{


while ($row=mysql_fetch_array($idreq))


{


$poids_titre = $row[poids];


}


$poids_titre = $poids_titre + 10;


// Mise à jour du poids du mot concerné


$req_upd="update moteur set poids = $poids_titre where mot = '$str_tmp'";


$idreq_upd=mysql_query($req_upd);


if ($idreq_upd == 0)


{


echo("erreur maj poids
");


mysql_close($idconnect);


exit;


}


}


else


{


// Le mot n'existait pas, on l'ajoute avec un poids de 10


$req_ins="insert into moteur (mot, poids, origine, titre_page) values ('$str_tmp', 10, 'origine', 'titre_page')";


$idreq_ins=mysql_query($req_ins);


if ($idreq_ins == 0)


{


mysql_close($idconnect);


exit;


}


}


}


}


else


echo("Balises titres non trouvées
");


// Mise à jour du titre


$req_titre="update moteur set titre_page = '$regs[1]'";


$idreq_titre=mysql_query($req_titre);


if ($idreq_titre == 0)


{


echo("erreur maj titre
");


mysql_close($idconnect);


exit;


}


}


else


{


// C'est la première fois que ce fichier est lancé, la fonction parcourir est affichée


?>


<formname="browse" action="find_title.php" method="post" enctype="multipart/form-data">








</form>


<?


}


?>


........................................................................fin script ......................................................................................


........................................................ fichier html du tutorial ..............................................................


Notre tutoriel précédent sur le Python était une introduction au langage : les chaînes de caractères n'avaient pas précisemment été abordées, nous allons rectifier cette situation aujourd'hui.

Les différents exemples que nous allons aborder sont simples d'approche et permettent de réutiliser tout de suite les caractéristiques de Python qu'ils mettent en avant.


(Si vous ne savez pas comment tester ces exemples, reportez-vous au tutoriel indiqué ci-dessus)

Démonstration par l'exemple

Une chaîne de caractères peut se stocker de deux manières, avec des simples ou doubles quotes, nous allons voir comment utiliser à notre avantage cette caractéristique :

str1 = 'Bonjour'
ou
str1 = "Bonjour"

Dans les deux cas un "print str1" produit bien évidemment le même affichage.

Question classique maintenant : comment faire si la chaîne de caractères possède un caractère spécial ? Réponse :

str1 = 'Aujourd\'hui le temps est au beau fixe'
ou plus malin
str1 = "Aujourd'hui le temps est au beau fixe"

Afin d'éviter un disgracieux échappement, lorsque cela est possible, nous pouvons faire en sorte d'encadrer le chaîne de caractères par le type de quote non présent dans la chaîne.

Les échappements sont néanmoins une étape incontournable si vous souhaitez par exemple sauvegarder dans l'état que vous avez choisi la pagination d'une chaîne de caractères :

Je souhaite que mon texte
soit sauvegardé dans l'état,
tous les espaces sont significatifs,
comment faire ?

Afin de sauvegarder le texte ci-dessus dans l'état, deux façons de faire. La première :

str = "Je souhaite que mon texte\n\
soit sauvegardé dans l'état,\n\
tous les espaces sont significatifs,\n\
comment faire ?"
print str


... Provoque l'affichage suivant :

Je souhaite que mon texte
soit sauvegardé dans l'état,
tous les espaces sont significatifs,
comment faire ?


Le même résultat est obtenu avec :

str = """Je souhaite que mon texte
soit sauvegardé dans l'état,
tous les espaces sont significatifs,
comment faire ?"""
print str


D'autres séquences d'échappement existent, vous les trouverez au milieu de cette page.

Dans un autre registre la concaténation s'effectue elle aussi de manière simple :

str1 = "a"+"b"+"c"
print str1

... Affiche "abc". Si vous ne réutilisez pas par la suite la variable str1, il est possible de s'en passer :

print "a"+"b"+"c"
ou
print "a" "b" "c"

... provoquent le même affichage.

Si maintenant vous souhaitez séparer ces trois caractères avec un espace :

print "a", "b", "c"

... Affiche "a b c".

La répétition ne pose pas de problème non plus :

print "abc" * 3

... Affiche : "abcabcabc"

Comme dans d'autres langages, il est possible d'accéder de manière indexée à certains caractères, exemples :

str = "Langage : Python"
print str[2]
print str[0:2]
print str[:4]
print str[4:]

... Ce qui provoque l'affichage suivant :

n
La
Lang
age : Python

On considère la chaîne comme un tableau dont le premier indice est 0.
Avec "print str[0:2]" on provoque l'affichage de deux caractères en partant de str[0].
":4" ramène les quatre premiers caractères tandis que "4:" ramène tous les caractères à partir du cinquième.

print str[-3]

... Affiche le troisième caractère en partant de la fin de la chaîne, "h".

Outre la classique fonction "len" :

print len(str)

... qui affiche 16 avec l'exemple précédent, il également possible de déterminer facilement si un caractère est présent ou non dans la chaîne :

str = "Langage : Python"
print "x" in str

... Affiche "0" si le caractère "x" ne se trouve pas dans la chaîne étudiée, et "1" sinon.

Attention, bien qu'il soit possible d'accéder de manière indexée au contenu d'une chaîne, celle-ci n'en est pas pour autant modifiable.

D'autres fonctions du même type existent, citons par exemple :

print str[0].isupper()
print str[1].isupper()

Qui pour l'exemple précédent affiche "1" puis "0" selon si le caractère étudié est en majuscule ou pas.

La liste de ces fonctions n'est pas exhaustive, nous vous renvoyons au manuel de Python si vous souhaitez en implémenter davantage.


......................................................... fin fichier ................................................................................


......................................................... fin étape 2 .............................................................................


///////////////////////////////////////////////////////// étape 3 //////////////////////////////////////////////////////////////////////////////////////////////////////////


Lors du dernier épisode, nous avions renseigné le champ "titre" de la table moteur et conçu une "interface" capable de parcourir notre disque dur à la recherche d'un fichier à indexer.


Nous allons modifier cette interface aujourd'hui afin de la rendre automatique et récursive . Après cette transformation il ne vous sera plus possible (dans l'état actuel du script) de choisir un fichier à indexer.
Cette situation est susceptible d'évoluer lorsque nous évoquerons les améliorations possibles à apporter à notre code.


Pas de panique si vous souhaitez conserver "l'interface précédente", son code était fourni en fin du tutoriel précédent.


Notre but aujourd'hui est d'une part de pouvoir lancer une indexation automatique et récursive de nos fichiers et d'autre part de renseigner le champ "origine", destiné à localiser chaque mot dans l'arborescence.



Les modifications à effectuer à notre script


Afin que l'indexation récursive fonctionne, notre script (renommé "moteur.php" au lieu de "find_title.php") doit être placé au "point de départ" de la récursivité souhaité. Notre jeu de tests (fourni en fin de tutoriel) pour cette indexation récursive est constitué de cinq fichiers HTML issus de tutoriels précédents, ils sont placés de la sorte :

- ...\EasyPHP\www\projet1\moteur\chaines_python.html
- ...\EasyPHP\www\projet1\moteur\exceptions_java.html
- ...\EasyPHP\www\projet1\moteur\rep1\chgt_registar.html
- ...\EasyPHP\www\projet1\moteur\rep2\flash_dyn_php.html
- ...\EasyPHP\www\projet1\moteur\rep2\selec_flash.html


Il faut donc placer notre script "moteur.php" au niveau le plus haut de notre arborescence :

- ...\EasyPHP\www\projet1\moteur\moteur.php


Le script de notre moteur va devoir subir quelques modifications, nous allons les voir ensemble.
Il va s'agir tout d'abord de concevoir (ou de trouver sur le net) une fonction qui liste récursivement le contenu de notre arborescence. Grâce à la documentation en ligne du site Php.net et plus précisemment aux commentaires ajoutés par des internautes au sujet de la fonction readdir() , nous récupérons une fonction nommé "echo_dir()" qui affiche récursivement le contenu d'une arborescence.

Afficher le nom des fichiers de notre jeu d'essai ne nous intéresse pas vraiment, aussi nous allons modifier cette fonction jusqu'à ce qu'elle devienne le squelette, la boucle principale de notre indexation, la voici modifiée :

function echo_dir($where)
{
$handle = opendir('.');
while ($file = readdir($handle))
{
if ($file != "." && $file != "..")
{
if (is_dir($file))
{
chdir($file);
$tmp = $where."/".$file;
echo_dir($tmp);
chdir("../");
}
else
{
if (($where != "") && ereg(".html",$file))
{
echo ("$where/$file
");
$src = $where."/".$file;
indexation($file, $src);
}
elseif (ereg(".html",$file))
{
echo ("/$file
");
$src = "/".$file;
indexation($file, $src);
}
}
}
}
closedir($handle);
}

echo_dir("");


Beaucoup de points à préciser ici... Procédons par ordre :
Nous lançons tout d'abord cette fonction avec le paramètre "" afin qu'elle démarre son traitement à la racine de l'arborescence.
Nous récupérons un pointeur sur le répertoire courant grâce à la fonction opendir().


La plupart des fonctions Php utilisées ici sont issues des fonctions "d'accès aux dossiers". Avec la boucle while nous nous assurons de traiter tous les fichiers du dossier courant, en effet, la boucle se poursuit tant que la fonction readdir() ne renvoie pas "false". En tant normal celle-ci renvoie le nom des entrées du répertoire.

Si les entrées renvoyées par readdir() n'est pas celle désignant le répertoire précédent ("..") ni celle du répertoire courant ("."), alors il est intéressant de la traiter.

La fonction "is_dir()" porte bien son nom, elle permet de vérifier si l'entrée traitée est un répertoire, auquel cas la récursivité rentre en jeu. Lorsqu'un répertoire est détecté, la fonction "chdir()" est utilisée afin de passer du répertoire courant au nouveau répertoire.
La variable $tmp permet de concaténer le répertoire courant avec la nouvelle entrée, ainsi l'arborescence se construit.

Si l'entrée traitée n'est pas un répertoire, c'est un fichier mais faut-il l'afficher, et si oui comment ? Le "else" traite deux cas :
- Le cas d'un fichier à indexer depuis la racine de l'arborescence
- Le cas d'un fichier à indexer depuis un répertoire.

Ces deux cas se matérialisent par les "echo", respectivement :

- echo ("$where/$file
");
- echo ("/$file
");


Ils représentent l'origine du fichier. Voilà ce que nous obtiendrons à l'écran après l'indexation de notre jeu d'essai :

/chaines_python.html
/exceptions_java.html
/rep2/selec_flash.html
/rep2/flash_dyn_php.html
/rep1/chgt_registar.html


La question "faut-il afficher / traiter" ce fichier est résolue par l'utilisation de la ligne suivante :

ereg(".html",$file)



Nous avons déjà utilisé précedemment cette fonction, elle permet de détecter si un fichier possède l'extension qui nous intéresse, ici ".html".

Pour résumer, la fonction echo_dir() nous permet de boucler récursivement autour des fichiers qui nous intéressent dans notre arborescence. Judicieusement placées, les lignes...


indexation($file, $src);



... Vont déclencher le processus d'indexation pour chacun des fichiers.


La fonction d'indexation et le champ origine


Quelle est donc cette fonction...

indexation($file, $src);

...C'est tout simplement le coeur du script du dernier tutoriel que nous avons "transformé" en fonction. Rien de bien sorcier :

function indexation($nomfichier, $origine)
{
$str_ini = join('', file($nomfichier));
$contenu = strip_tags($str_ini);

// -------- TRAITEMENTS PHP ---------

// Insère les mots et leurs occurences...
while (list ($key, $val) = each ($occurences))
{
$req="insert into moteur (mot, poids, origine, titre_page) values ('$key', $val, ' $origine ', 'titre_page')";
$idreq =mysql_query($req);
}
// -------- TRAITEMENTS PHP ---------
}


Voilà résumés les changements à opérer sur le script de l'épisode précédent. Le premier paramètre passé à la fonction est tout simplement le nom du fichier à indexer, tandis que le second va servir à implémenter le champ "origine" de la table "moteur".
Initialement égal à 'origine', ce champ reçoit désormais la valeur '$origine'.

Au chapitre des modifications nous avons également sorti de la boucle d'indexation les lignes relatives à la connection à la base de données, inutile qu'elles soient répétées.


Enfin, la fonction de mise à jour du titre a elle aussi été modifiée, de deux manières :

// Mise à jour du titre
$titre_tmp = htmlspecialchars($regs[1], ENT_QUOTES);
$req_titre="update moteur set titre_page = '$titre_tmp' where origine = '$origine' ";
$idreq_titre =mysql_query($req_titre);
if ($idreq_titre = = 0)
{
echo("erreur maj titre
");
mysql_close($idconnect);
exit;
}

Les expressions en gras sont celles qui ont été modifiées. Il nous fallait premièrement éviter que le script ne se plante sur un script contenant un caractère "simple quote" par exemple (nous tâchons d'améliorer notre script au fur et à mesure), c'était un oubli dans la version précédente.

Ne pas oublier également la condition " where " dans la mise à jour du titre !
Cette clause était inutile auparavant puisque nous traitions un seul fichier à la fois. Aujourd'hui, si nous ne précisons pas cette restriction lors de la mise à jour, le titre du dernier tutoriel indexé sera présent dans tous les champs de la base.

Voici notre script "moteur.php" mis à jour, ainsi que le jeu de test utilisé ici.
A titre indicatif l'indexation a pris environ 3mn pour un peu plus de 2000 champs insérés dans la table "moteur" sous windows 2000, le tout cadencé à 1Ghz.


............................... moteur.php .........................................................


<?php


set_time_limit(300);


// Connexion pour une base nommée "jdn", en local, via EasyPhp.


$host = "localhost";


$user = "root";


$pass = "";


$bdd = "jdn";


$idconnect=mysql_connect($host,$user,$pass) or die("Impossible de se connecter à la base de données"); // Le @ ordonne a php de ne pas afficher de message d'erreur


mysql_select_db("$bdd") or die("Impossible de se connecter à la base de données");


function array_non_empty($array)


{


$retour = array();


foreach ($array as $a)


{


if ((!empty($a)) && (strlen($a) >= 3))


{


array_push($retour, trim($a));


}


}


return $retour;


}


function split_words($string)


{


$retour = array();


$delimiteurs = " .!?,:(){}[]%\/";


$tok = strtok($string, " ");


while (strlen(join(" ", $retour)) != strlen($string))


{


array_push($retour, $tok);


$tok = strtok ($delimiteurs);


}


return array_non_empty($retour);


}


function indexation($nomfichier, $origine)


{


$str_ini = join('', file($nomfichier));


// file renvoie un array ou chaque case est une ligne du fichier.


// join fusionne un array et une string, ici chaine vide : on transforme un array en string


$contenu = strip_tags($str_ini);


// Supprime les tags php et html d'une chaine de caracteres.


$contenu = strtolower($contenu);


$contenu = htmlspecialchars($contenu, ENT_QUOTES);


$contenu = split_words($contenu);


// On obtient un tableau de mots


// Associe chaque mot avec son nombre d'occurences


$occurences = array_count_values($contenu);


// Insère les mots et leurs occurences, on ne se preoccupe pas encore de l'origine ou du titre de la page


while (list ($key, $val) = each ($occurences))


{


//echo ("$key => $val
");


$req="insert into moteur (mot, poids, origine, titre_page) values ('$key', $val, '$origine', 'titre_page')";


$idreq=mysql_query($req);


if ($idreq == 0)


{


mysql_close($idconnect);


exit;


}


}


// Repérage du titre et comptage


if (eregi("<TITLE>(.*)</TITLE>", $str_ini, $regs))


{


$str_titre = split_words($regs[1]);


// On obtient un tableau de mots


$nb_titre = count($str_titre);


for($j=0;$j<$nb_titre;$j++)


{


// On applique au mot du titre traité les mêmes traitements que lors de l'indexation des mots


// Des fonctions seraient utiles ici (à venir).


$str_tmp = strip_tags($str_titre[$j]);


// Supprime les tags php et html d'une chaine de caracteres.


$str_tmp = strtolower($str_tmp);


// Passe tout en miniscule


$str_tmp = htmlspecialchars($str_tmp, ENT_QUOTES);


//On tente de récupérer le poids associé au mot traité


$req_poids="select poids from moteur where mot = '$str_tmp'";


$idreq=mysql_query($req_poids);


if ($idreq == 0)


{


echo("erreur recup poids
");


mysql_close($idconnect);


exit;


}


//Si le mot existe déjà, on ajoute 10 à son poids


if (mysql_num_rows($idreq) != 0)


{


while ($row=mysql_fetch_array($idreq))


{


$poids_titre = $row[poids];


}


$poids_titre = $poids_titre + 10;


// Mise à jour du poids du mot concerné


$req_upd="update moteur set poids = $poids_titre where mot = '$str_tmp'";


$idreq_upd=mysql_query($req_upd);


if ($idreq_upd == 0)


{


echo("erreur maj poids
");


mysql_close($idconnect);


exit;


}


}


else


{


// Le mot n'existait pas, on l'ajoute avec un poids de 10


$req_ins="insert into moteur (mot, poids, origine, titre_page) values ('$str_tmp', 10, '$origine', 'titre_page')";


$idreq_ins=mysql_query($req_ins);


if ($idreq_ins == 0)


{


mysql_close($idconnect);


exit;


}


}


}


}


else


echo("Balises titres non trouvées
");


// Mise à jour du titre


// Pour eviter que le script ne se plante sur un titre comportant un '


$titre_tmp = htmlspecialchars($regs[1], ENT_QUOTES);


// Ne pas oublier la clause where, l'origine est propre au tutoriel en cours d'indexation uniquement !


$req_titre="update moteur set titre_page = '$titre_tmp' where origine = '$origine'";


$idreq_titre=mysql_query($req_titre);


if ($idreq_titre == 0)


{


echo("erreur maj titre
");


mysql_close($idconnect);


exit;


}


}


// Cette fonction est issue d'un commentaire d'un internaute sur le site Php.net pour la fonction readdir()


// Elle est ici modifiée pour gérer l'affichage correct d'une origine de type rep4/rep5/nom_fichier.html et provoquer l'indexation


function echo_dir($where)


{


$handle = opendir('.');


while ($file = readdir($handle))


{


if ($file != "." && $file != "..")


{


if (is_dir($file))


{


chdir($file);


$tmp = $where."/".$file;


echo_dir($tmp);


chdir("../");


}


else


{


if (($where != "") && ereg(".html",$file))


{


echo ("$where/$file
");


$src = $where."/".$file;


indexation($file, $src);


}


elseif (ereg(".html",$file))


{


echo ("/$file
");


$src = "/".$file;


indexation($file, $src);


}


}


}


}


closedir($handle);


}


echo_dir("");


?>


............................................................. fin moteur.php .................................................................


////////////////////////////////////////////////////////////////// fin étape 3 ///////////////////////////////////////////////////////////////////


................................................... 4 eme étape .................................................................................


Dans ce dernier volet, ou presque (une phase d'optimisation est à prévoir), nous allons rendre fonctionnel "notre moteur de recherche".

Rappelons brièvement les étapes précédentes :
1) Politique d'indexation de contenu, traitements des fichiers à indexer.
2) Interface d'indexation et poids des mots.
3) Indexation récursive et origine des mots.

Maintenant que notre contenu est indexé correctement (le "poids" et l'origine des mots sont insérés dans la base), le temps est venu de tirer profit du travail effectué.

Le but aujourd'hui est de rendre fonctionnel notre moteur. Cela signifie qu'il faut donc concevoir un formulaire destiné à recueillir le mot recherché par les internautes, et enfin présenter les résultats de cette recherche par ordre de pertinence.

Au programme également, la mise en ligne de notre moteur. Nous devons dans ce cas prévoir son passage du stade expérimental (tournant grâce à EasyPhp par exemple), à celui du stade de "production", en ligne.

Comme d'habitude, l'intégralité du code source de notre moteur dans sa dernière version sera disponible en fin de tutoriel.

Quelques corrections nécessaires


Nous l'avions déjà indiqué par le passé, notre moteur est susceptible d'évoluer à chaque étape. Ceci pour la simple raison que cette série de tutoriels résulte d'une construction progressive et non d'un découpage calqué sur une application déjà complète.

Il s'améliore d
0
juki_webmaster Messages postés 947 Date d'inscription mercredi 19 novembre 2003 Statut Membre Dernière intervention 5 avril 2008 3
21 nov. 2005 à 15:30
Ce topic remonte a des années lumiaire.
En ce laps de temps je me suis adapter au langage C, et j'ai put concevoir le robot.
merci pour ta reponse.
0
einsteinkta Messages postés 1 Date d'inscription jeudi 14 mai 2009 Statut Membre Dernière intervention 27 juillet 2011
27 juil. 2011 à 13:46
Bnjour
est ce que tu peut n'envoyer un cahier de charge pour que je t'aide sur ton projet
0
Rejoignez-nous