Creando un Web Crawler con .NET

Hola!

Este artículo tratará sobre dos temas: Las capacidades de red del Framework de .NET (sin entrar en WCF) y las expresiones regulares. Y para mostrar un ejemplo sencillo de cómo utilizar ambas, vamos a desarrollar un Web Crawler, que no es nada más que una aplicación que tomando como punto de partida una URL, examina la página de dicha URL buscando enlaces, y los sigue para completar un mapa de la web. Este proceso se lleva a cabo de forma recursiva, y para evitar que se quede corriendo indefinidamente le indicaremos la profundidad máxima que puede explorar.

Además, como las páginas pueden tener enlaces comunes, iremos marcando las páginas que ya se han visitado, e incluso podríamos usar un sistema de puntos o pesos para ver qué páginas son las que más enlaces reciben. Esto a una escala mayor, y con un sistema de pesos más complejo (aparte de un análisis del contenido, cosa que no haremos) es básicamente lo que realizan los robots de los buscadores para alimentar sus bases de datos.

Primero vamos a olvidarnos de cómo obtener el HTML de las páginas web (que veréis que es muy sencillo) y vamos a centrarnos en cómo procesarlo para obtener los enlaces y las rutas de los mismos. Aquí es donde entran en juego las expresiones regulares. Utilizaremos dos: Una para detectar los enlaces en el HTML (realmente vamos a buscar los href=»…», que aparecen también en CSS, y otros tags), y la otra para procesar URLs.

private Regex urlRegex = new Regex(@"^(?<s1>(?<s0>[^:/?#]+):)?(?<a1>"
                + @"//(?<a0>[^/?#]*))?(?<p0>[^?#]*)" 
                + @"(?<q1>?(?<q0>[^#]*))?" 
                + @"(?<f1>#(?<f0>.*))?");
 
private Regex hrefRegex = new Regex("href\s*=\s*(?:(?:\"(?<url>[^\"]*)\")|(?<url>[^\s]* ))");

Una vez que tenemos listas las expresiones regulares, podremos obtener todos los href presentes en la página actual. Para seguir avanzando en profundidad, simplemente debemos pedir el recurso al que apunta, y repetir el proceso. De ahí que utilicemos la recursión.

El segundo tema a tratar es cómo pedir las páginas: .NET provee de clases en el namespace System.Net que nos permiten hacer peticiones HTTP de forma sencilla. Nosotros usaremos System.Net.WebRequest, System.Net.HttpWebRequest y System.Net.HttpWebResponse

Un posible uso de estas clases es el siguiente:

HttpWebRequest req = (HttpWebRequest) WebRequest.Create(currentURL); 
HttpWebResponse resp = (HttpWebResponse) req.GetResponse();
if(resp.ContentType.ToLower().IndexOf("text/html") > -1)
{
    Stream istrm = resp.GetResponseStream();
    StreamReader rdr = new StreamReader(istrm);
    string pageHtml = rdr.ReadToEnd();
 
    ...
}
resp.Close();

Podéis ver los detalles de la implementación del crawler en el código del adjunto.

Saludos! 

21 comentarios sobre “Creando un Web Crawler con .NET”

  1. buenas… interesante la idea. Sabés si el httpwebrequest se peude usar «al revés»?
    o sea… tengi una página asp q hace un post a un cgi, no puedo llamar al cgi directamente porq el cgi hace un request del form, asi q si mando parametros por get no los toma.
    Hay forma de «engañar» al cgi haciendole creer q el post se lo hizo el asp pero enviarle HTML o algo parecido? (necesito no usar asp, sino llamar al cgi desde el aspx)

  2. Sí, podrías utilizar la propiedad Method de la clase HttpWebRequest, indicandole que es un POST, con lo que debería actuar como si se estuviese enviando un formulario.

    Además de eso, tendrás que rellenar las propiedades ContentType y ContentLength, obtener el stream de la petición, y enviar los datos como un array de bytes.

    Tienes un ejemplo bastante sencillo aquí:

    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemnethttpwebrequestclassmethodtopic.asp

  3. A mi, en cierta ocasión se me ocurrió intentar hacer un bot para OGame, que hiciera de forma automática o programada ciertas acciones. O de forma más genérica, un bot que pudiera realizar un conjunto de acciones sobre una(s) página(s).

    Obviando la dificultad de la estructura de la página, y de localizar en esta los controles que nos pueden interesar, había un tema muy curioso que tiene su dificultad aparte. La sesión. Según entiendo, mecanismos para preservar la sesión hay: Por cookie, por hidden field, y por parámetro en la querystring (parece que ogame utiliza este último). También se me ocurre que estaría el potencial problema de la autenticación.

    Se me ocurren estos problemas, que también podrían existir para el crawler que comentas. Claro que podrían quedar como mejoras a implementar en la versión 2.0, ¿verdad?

  4. Hola Tio_Luiso

    En principio la estructura de la página no es relevante para mandar un POST siempre y cuando conozcas los IDs de los controles, ya que los datos que envias son utilizando el ID del control como identificador.

    En cuanto a la sesión, si sabes cual es el mecanismo, siempre puedes ver en la respuesta qué identificador de sesión se te está asignando y usarlo de forma apropiada (bien sea por cookie, por querystring, o como sea)…

  5. No, si está claro que es posible. Sólamente algo más complicado. En cualquier caso, la parte chunga pienso que sería hacer un crawler / bot genérico. Uno para una aplicación concreta sería más sencillo.

    Para un crawler no sería necesario conocer nada más que los links que contiene cada página. La aplicación que yo tenía en mente, también tenía que extraer (vampirizar) contenido de la página. Por ejemplo, si en la página de mensajes tienes un mensaje que dice que el usuario xxxxxx te está atacando, y que llegarán en nosecuantos minutos, saber extraer la información pertinente para poder informar al usuario, o actuar de forma automática. Pero me estoy yendo de madre. En efecto, para un crawler no sería necesario.

    Una pregunta. Y si el sitio web tiene AJAX? Cómo podrías hacer crawling?

  6. Hola Tio_Luiso

    Buena pregunta 😉 Si el sitio web tiene AJAX, en principio se desencadenan las peticiones a raíz de pulsar en enlaces o botones, pero lo que hace es ejecutar javascript. Esto complica un poco las cosas, ya que probablemente tengas que ejecutar dicho javascript, y para ello, probablemente tengas que mantener una instancia de un WebBrowser con el HTML de la petición anterior, en la que «pulsarías» el link, y compararías el contenido del documento HTML antes y después de pulsar para ver las diferencias, para procesarlas.

    Vamos, que no sería sencillo, pero tampoco imposible.

  7. Hola
    A ver si me puedes ayudar.
    necesito pasar datos desde una aplicacion de escritorio (en C#) a una aplicacion web en asp, necesito pasarle los datos por post.

    He estado mirando las clases HTTPWEBREQUEST y HTTPWEBRESPONSE pero no tengo bien claro su uso.

    me podrias decir la forma de hacer lo que deseo?

    gracias

  8. En principio, lo único que hace falta es:

    – Crear el objeto HttpWebRequest (con el método estático Create de la clase WebRequest)
    – Establecer la propiedad Method de este objeto a «POST» (por defecto es GET)
    – Rellenar los datos del post, usando los nombres de los controles que quieres rellenar. Para eso, tienes que crear un string que contenga una ristra nombreControl=valorControl&nombreControl2=valorControl2&…, obtener los bytes de ese string usando un objeto ASCIIEncoding, y escribir esa ristra en el stream del objeto HttpWebRequest.
    – Establecer el valor de la propiedad ContentType del objeto HttpWebRequest a «application/x-www-form-urlencoded»
    – Establecer el valor de la propiedad ContentLength del objeto HttpWebRequest a la longitud del string de los datos.

    Un código de ejemplo:

    string datosPost=»nombreControl=»+valorControl;
    ASCIIEncoding encoding=new ASCIIEncoding();
    byte[] bytesDatosPost=encoding.GetBytes(datosPost);
    // Establecemos el ContentType
    miHttpWebRequest.ContentType=»application/x-www-form-urlencoded»;
    // Set the content length of the string being posted.
    miHttpWebRequest.ContentLength=datosPost.Length;
    Stream reqStream=myHttpWebRequest.GetRequestStream();
    reqStream.Write(bytesDatosPost,0,bytesDatosPost.Length);
    Console.WriteLine(«El valor de ‘ContentLength’ después de escribir los datos es {0}»,miHttpWebRequest.ContentLength);
    // Cerramos el stream.
    reqStream.Close();

    Espero que te sirva! Saludos!

  9. Hola, debo hacer un robot, capaz de ingresar a una pagina, ingresar el usuario y password. Luego debe seleccionar el valor para dos combobox e ingresar un valor a dos textbox mas. Despues de esto, llego a una pagina en la cual debo capturar los datos. Esto último (del combobox en adelante) lo debo hacer millones de veces. Para ser sincero no tengo idea como hacerlo, solo se que debo hacerlo en .NET :S.
    Cualquier tipo de ayuda es bienvenida…

  10. Hmmmmm….

    Yo lo que haría sería lo siguiente:

    Examina bien cómo está hecha esa aplicación. Seguramente el formulario de login y password enviará esos datos a otra página, y o bien genera una sesión, o genera una cookie para identificarte. Por lo tanto, usando el objeto WebRequest puedes utilizar el método POST para enviar los datos a la página que aparezca en el action del formulario de esa página.

    Comprueba si hay una cookie o una sesión creada, y para las pantallas siguientes utilizalas. Pero no automatices la selección de los combos y la inserción de los datos. Fíjate dónde los envía, y envíalos directamente a dicha pantalla. Para capturar los datos, supongo que tendrás que examinar el HTML que te devuelve la aplicación después de enviarle los datos, y capturarlos.

    Saludos!

  11. Hola, estoy intendando hacer una especie de xmltv, que recoja los links de una web determinada y lo transforme en xml, podrias darme alguna idea?

  12. Hola,

    me gustaria utilizar este programa pero para navegar por todas las páginas de wikipedia. En wikipedia los enlaces internos no van por href sino que entre corchetes ( [[enlace]]).

    Como puedo hacerlo? Como puedo hacer también que guarde un registro de el numero de paginas que ha encontrado, la pagina que tiene más enlaces, …

    Muchas gracias.

  13. Uf… ¿¿¿Para navegar por TODA la wikipedia???

    Tal y como está el código, va a recorrer todos los enlaces que encuentre, no solo los que estén en la wikipedia. Si sabes seguro que los artículos se referencian como indicas, basta con que cambies la expresión regular que se utiliza para buscar los enlaces.

    Tal cual está el código va anotando el número de veces que pasa por cada página (es decir, el número de enlaces que llegan a ella). Podrías crearte una clase que contenga todos los datos que quieres guardar de cada página, y cambiar la tabla hash que se usa en el código (es para .NET 1.1, sin generics) por un Dictionary.

  14. Muy interesante. A ver si me podeis ayudar. Visito regularmente una página que me presenta datos en forma de tabla, pero lo hace a través de scripts de java. El caso es que con .net y htmlelement del control webbrowser he conseguido examinar la página, pero sólo veo líneas como …writeline(…). Sin embargo en pantalla el control webbrowser si me presenta los datos en forma de tabla. ¿No hay forma de acceder a esos datos para gestionarlos desde mi aplicación?
    Gracias.

  15. Hmmm…

    Si lo que te está mostrando es un applet de Java, no podrás acceder al contenido. Si es HTML, debería dejarte a través del DOM de la página actual (es decir, el objeto HTMLDocument de System.Windows.Forms).

    Si sabes qué estructura tiene la página web, podrías llamar al método GetElementById si el texto se incluye en un elemento que tenga un Id. Si no tiene Id, puedes probar a usar GetElementsByTagName, y de ahí ir buscando el que te convenga.

    Si tuvieses un ejemplo de la página podría ayudarte más, pero así es difícil.

  16. Buenas,

    Lo que estoy intentando es hacer un webCrawler que tenga como base el GoogleReader. No tengo problemas para insertar la cookie y llegar a la página principal. Sin embargo, a partir de allí, los enlaces no están en modo href sino que los obtiene a través de funciones javascript que supongo estarán alojadas en el servidor.

    Cómo puedo solucionar este problema?

    Gracias de antemano.

  17. Hola Blas,

    Me temo que en tu caso, en el que tienes que ser capaz de interactuar con la página y con el javascript que tienes en ella, tendrás que utilizar un control WebBrowser. El HtmlRequest puede hacer peticiones HTML y obtener la respuesta del servidor (el stream de respuesta), pero no interpreta el HTML ni ejecuta javascripts.

    Saludos,

  18. Carlos U:

    Para que te busque un tag tienes que modificar la expresión regular hrefRegEx. Según el tag que quieras buscar tendras que usar expresiones regulares distintas, o bien hacer una expresión regular para todos los tags y decidir en función del nombre del tag qué hacer (esto sería más pesado, mucho más proceso).

    También puedes crear varias expresiones regulares, unirlas con un OR y darlas nombre, de manera que para cada token encontrado puedas saber exactamente cual es si tener que examinarlo (a partir del nombre que le has asignado).

Responder a aruiz Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *