martes, 4 de agosto de 2009

¿Cómo se hace... un crawler?


Un crawler, también llamado robot o araña de la Web (spider), es un programa diseñado para explorar de manera automática la Web, descargando las páginas que visita.


Para ello, comienza a partir de un subconjunto de páginas dadas como semilla (seed), y que será el conjunto de páginas que deberá visitar el crawler.


De estas páginas, el crawler recupera los enlaces que contienen a otras páginas, añadiéndolos al conjunto de páginas por visitar (crawl frontier) para recorrerlas en su proceso.


Este proceso será indefinido (ad nauseum, ad infinitum) o hasta una condición de parada, dependiendo del algoritmo implementado.


La forma de construir un sencillo crawler es seguir el algoritmo básico descrito en su definición, y refinarlo con las necesidades particulares. A continuación se muestra paso a paso cómo construir un sencillo crawler en C#:


Paso 1: Referencias y globales

Lo primero será añadir las librerías a utilizar, que vienen por defecto referenciadas en el Visual Studio:



using System.IO;
using System.Net;


Y un par de variables globales a la clase. En primer lugar una tabla hash que nos permita saber si una Url a visitar ha sido previamente visitada:

Hashtable visitedUrls = new Hashtable();


Y por otro lado un dataset que nos permita almacenar las Urls visitadas con el Html recuperado:

DataSet mUrls = new DataSet();


Podríamos haber creado un dataset tipado o crearle manualmente la tabla y columnas, que es por lo que se ha optado ya que no es el objetivo del artículo:


mUrls.Tables.Add("TABLE_URLS");
mUrls.Tables["TABLE_URLS"].Columns.Add("COLUMN_URL");
mUrls.Tables["TABLE_URLS"].Columns.Add("COLUMN_HTML");



Paso 2: Obtener Html

A continuación nos creamos un método para obtener el Html a partir de una Url:



public String GetHtml(String url)
{
try
{
String sHtml = String.Empty;
HttpWebRequest req =
(HttpWebRequest)WebRequest.Create(url);
HttpWebResponse resp =
(HttpWebResponse)req.GetResponse();
if (resp.ContentType.ToLower().IndexOf("text/html")
> -1)
{
Stream istrm = resp.GetResponseStream();
StreamReader rdr = new StreamReader(istrm);

sHtml = rdr.ReadToEnd();
}
resp.Close();
return sHtml;
}
catch (Exception ex)
{
return "ERROR: " + ex.ToString();
}
}


Es muy importante que notéis que estamos únicamente obteniendo páginas Html (

if (resp.ContentType.ToLower().IndexOf("text/html") > -1)
) Si deseásemos obtener cualquier otro tipo de página (pe. un Kml de Google Maps, una imagen o un pdf), deberíamos ver qué tipo Mime nos devuelve (pe. desde un navegador, viendo código fuente), y ponerlo aquí.


Paso 3: Obtener enlaces

Ya tenemos cómo obtener el Html de una página, ahora debemos obtener los enlaces que contiene. Para ello tenemos dos opciones, nos podemos construir una expresión regular y aplicarla al código, o podemos utilizar las librerías Microsoft.mshtml para tratamiento del html. A continuación se presentan ambos métodos:


Método 1: Expresión regular


Añadiremos una referencia a la librería de manejo de expresiones regulares que utilizaremos para poder obtener las urls contenidas en los elementos a href:


using System.Text.RegularExpressions;


Nos creamos una expresión regular que empareje con la definición de la etiqueta href de Html:


private Regex hrefRegex = new Regex
("href\\s*=\\s*(?:(?:\\\"(?[^\\\"]*)\\\")|(?[^\\s]* ))");


Y un método para obtener las Urls y devolverlas al flujo principal del crawler:


public String[] GetLinks(String html, System.Uri baseUri)
{
//baseUri es necesario ya que se obtiene del href la ruta
//del enlace, que puede ser absoluta o relativa
MatchCollection hrefmatches = hrefRegex.Matches(html);
String[] sLinks = new String[hrefmatches.Count];
Int32 i = 0;

foreach (Match match in hrefmatches)
{
string newURL = match.Groups["url"].Value;

if (baseUri != null)
{
Uri newUri = new Uri(baseUri, newURL);
sLinks[i] =
new String(newUri.ToString().ToCharArray());
}
else
sLinks[i] = new String(newURL.ToCharArray());
i++;
}
return sLinks;
}



Método 2: Microsoft.mshtml


En primer lugar añadiremos la referencia a la librería:

using mshtml;


Y a continuación, y con el mismo interfaz anterior, construimos el método para obtener las urls:


public static String[] GetLinks(String html,
System.Uri baseUri)
{
//baseUri no es necesario ya que se nos devuelven las
//rutas absolutas de los enlaces
String[] Links = null;
html = html.ToLower();

IHTMLDocument2 oDoc = GetHtmlDoc(html);

Int32 iNLinks = oDoc.links.length;
Links = new String[iNLinks];

Int32 i = 0;
foreach (HTMLAnchorElement oAnchor in oDoc.links)
Links[i++] = oAnchor.href;

return Links;
}


Paso 4: Almacenar Urls y Html

Aquí creamos un método auxiliar que nos permita almacenar el Html recuperado de la Url visitada actualmente. Hemos optado por almacenarlo en un DataSet, pero se podría almacenar directamente a BBDD o cualquier cosa que necesitemos:


private void AddUrlToDataSet(String url, String html)
{
url = url.ToLower().Trim();
html = html.ToLower().Trim();
if ((mUrls.Tables[TABLE_URLS].Select("URL='" +
url + "'").Length == 0))
{
DataRow oRow = mUrls.Tables[TABLE_URLS].NewRow();
oRow[COLUMN_URL] = url;
oRow[COLUMN_HTML] = html;
mUrls.Tables[TABLE_URLS].Rows.Add(oRow);
}
}


Paso 5: Algoritmo de Crawling

Una vez tenemos los métodos necesarios, podemos escribir el algoritmo principal del crawler. Aquí vamos a mostrar un algoritmo sencillo que a partir de una Url inicial (semilla o seed), retornaría todas las páginas del dominio de la Url dada y hasta un nivel máximo de profundidad proporcionado como argumento.



private void Crawl(String baseUrl, String currentUrl,
Int32 iCurrentDepth, Int32 iMaxDepth)
{
Uri uriBase = new Uri(baseUrl);

if (iCurrentDepth <= iMaxDepth)
{
if (!visitedUrls.ContainsKey(currentUrl))
{
visitedUrls.Add(currentUrl, 1);

String shtml = String.Empty;
Uri uriCurrent = new Uri(currentUrl);

if ((uriBase.Host == uriCurrent.Host) &&
(currentUrl.StartsWith(baseUrl)))
{
shtml = GetHtml(currentUrl);
if (shtml != String.Empty)
AddUrlToDataSet(currentUrl,
shtml.ToLower());
}
if (shtml != String.Empty)
{
String[] sLinks = HtmlMngr.GetLinks(shtml);
for (Int32 i = 0; i < sLinks.Length; i++)
{
if (sLinks[i] != null)
Crawl(baseUrl, sLinks[i], urlClass,
iCurrentDepth + 1);
}
}
}
}
}


Paso 6: Consideraciones finales

La construcción de un crawler es sencilla, pero su uso intensivo y de manera eficiente requiere tener en consideración una serie de retos y cuestiones claves que se expondrán en detalle en otro artículo, pero principalmente se debería hacer especial énfasis en el uso ético y responsable del mismo, siguiendo las políticas de privacidad dictaminadas en los servidores para cada una de las páginas contenidas y que posiblemente visitaremos, y que vienen definidas en el fichero robots


En cualquier caso deseamos que el ejemplo aquí expuesto sea de su interés y utilidad, y haga un uso responsable del mismo.

No hay comentarios:

Publicar un comentario en la entrada