vendredi 26 janvier 2007

Java SE: Chercher sur Google

Soit, vous devez chercher sur internet au travers de votre programme Java.
Pour cela vous serez peut-être amené à penser que le tres populaire moteur de recherche Google puisse correspondre à vos besoins, et vous avez totalement raison !

En effet Google met à votre disposition une somptueuse, que dis-je, une resplendissante API qui vous permettra de lancer de jolies requetes au moteur de recherche et de recolter le tout via les standards SOAP & WSDL.
Ce n'est pas moi qui le dit, c'est eux:
Google SOAP Search API

With the Google Web APIs service, software developers can query billions of web pages directly from their own computer programs. The Google web search API uses the SOAP and WSDL standards.
Mais, en cliquant sur le lien, nous apprenons qu'il n'y a plus de clé disponible pour bénéficier de ce superbe service :(
En vérité, c'est un pretexte que j'utilise pour ne pas utiliser cette API, en effet, en acceptant les termes et conditions d'utilisations, vous acceptés de ne pas partager votre clé, or si je veux partager mon programme, ca risque d'être difficile..

Nous allons donc utiliser la méthode alarache, bien bourrin, dans un style mad max tres épuré :)

Tout d'abord il faut savoir que google fournit plusieurs interfaces pour envoyer des requetes à sa chere base de donnée (la classique, celle pour les mac, celle pro-linux, celle pro-ceci, et l'autre pro-cela).
Nous, nous cherchons une interface qui renvoie un code légé afin de ne pas encombrer la bande passante avec des balises html inutiles pour notre petit programme.
Ce qu'offre l'interface de recherche pour pda: http://www.google.fr/pda/

Maintenant, analysons la requete envoyée par le navigateur à google:

GET /pda/search?mrestrict=chtml&output=pda&q=monde+de+merde&btnG=Rechercher&site=search HTTP/1.0
User-Agent: Opera/9.01 (Windows NT 5.1; U; fr)
Host: www.google.fr
Accept: text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1

Sur la premiere ligne, le mot clé 'GET' indique au serveur web distant que nous voulons acceder à la page /pda/search avec les parametres suivants:
mrestrict = chtml  // indique que nous désirons recevoir un html "simplifié" pour unitée mobile
output = pda // demande l'utilisation de l'interface pda de google
q = monde+de+merde // La phrase que nous voulons rechercher est 'monde de merde' (voir la classe américaine pour comprendre :)
btnG=Rechercher et site=search // facultatif

HTTP/1.0 // sert à indiquer au serveur web que ceci est une requete http 1.0 (logique non?)
User-Agent: Opera/9.01 (Windows NT 5.1; U; fr) // Ici, on bluff le serveur web en lui disant que nous utilisons Opera 9 sous windows pour acceder à la page !
Host: www.google.fr // indique que la page que nous voulons est sur le serveur www.google.fr
Accept: * // facultatif, indique que le navigateur accepte toutes les entetes mime qui suivent

Maintenant que nous avons tout ca, nous allons envoyer notre requete via un petit programme Java:

import java.net.*;
import java.io.*;
import java.util.*;
public class GoogleQuery{
public static void main(String leopard[]){

// La fameuse requete (star parmis les star)
String requete = "GET /pda/search?mrestrict=chtml&output=pda&q=monde+de+merde&btnG=Rechercher&site=search HTTP/1.0\r\n";
requete += "User-Agent: Opera/9.01 (Windows NT 5.1; U; fr)\r\n";
requete += "Host: www.google.fr\r\n";
requete += "Accept: text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1\r\n\r\n";

Socket s = null;
String chaine = ""; // Pour acceuillir le résultat
int c; // recuperation octet par octet

try{
// Ouverture d'un socket vers google sur le port 80 (web)
s = new Socket("www.google.fr",80);

InputStream ent = s.getInputStream(); // IS servant à recevoir le résultat de la requete
OutputStream sor = s.getOutputStream(); // OS servant à envoyer la requete

// Conversion de la requete String en tableau d'octet
byte tab[] = requete.getBytes();

// Envoi de la requete vers google
sor.write(tab);

// Récuperation du résultat brut
while((c=ent.read()) != -1){
chaine += (char)c;
}

System.out.println(chaine); // Affiche du résultat sur la console
}catch(Exception e){
System.out.println(e); // Si il y a une erreur, on l'affiche
}finally{
try{
s.close(); // Dans tout les cas, on ferme le socket.
}catch(IOException ioe){}
}

}
}


L'execution de ce programme devrait afficher du code html, résultat de la requete vers google.

Pour récuperer nos liens, il va falloir parser (analyse syntaxique) tout cela !
Une fois n'est pas coutume, il existe plusieurs manieres de faire, vous pourriez utiliser par exemple les API DOM/SAX, mais il faudrait que le code soit certifié XHTML strict !
Une autre maniere de faire serait l'utilisation des expressions régulieres, mais nous y reviendrons plus tard.
Pour le moment, nous allons utiliser ces bon vieux indexOf et substring :

D'abord nous devons trouver un repere, pour cela analysons un lien renvoyé par google:
<a accesskey="1" href="http://monde.de.merde.free.fr/">Monde de Merde - Migration$lt;/a>

Le parametre accesskey n'est présent que dans les liens concernants le résultat de la recherche!
Nous utiliserons donc ce mot comme repere pour nos méthodes.
Voici l'algorithme du parser syntaxique:

posa <- position accesskey
tant que posa est différent de -1
ajouter au tableau la sous-chaine entre href=" et "
ajouter au tableau la sous-chaine entre > et </a>
posa <- position acceskey suivant
fin tant que

Voici la traduction en Java:

String resultat[][] = new String[40][2];
int taille_logique = 0;

int posa = chaine.indexOf("accesskey");
int poshref = 0;
int posfinhref = 0;
int posgta = 0;
int poslta = 0;
while(posa != -1)
{
poshref = chaine.indexOf("href=",posa);
posfinhref = chaine.indexOf("\"",poshref+6);
resultat[taille_logique][0] = chaine.substring(poshref+6,posfinhref);

posgta = chaine.indexOf(">",posfinhref);
poslta = chaine.indexOf("</a>",posgta);
resultat[taille_logique++][1] = chaine.substring(posgta+1,poslta);

posa = chaine.indexOf("accesskey",posa+10);

if(taille_logique==40) break;
}

Pour afficher le résultat, il suffit de parcourir le tableau:

for(int i=0; i<taille_logique; i++){
System.out.println(resultat[i][1]+" = "+resultat[i][0]);
}