Cómo obtener todos los link de una web con LinkSpider | C#

Intermedio

Como developers es habitual que tengamos tareas donde debemos analizar sitios web para extraer información, una de las tareas más comunes es tener que extraer los vínculos de una página web o de un sitio web entero.

Si bien hay mil formas de lograrlo una habitual es buscar componentes que ya hagan el trabajo por nosotros, me puse en esa tarea y en ralidad quede decepcionado.

Muchos de los componentes que existen por allí tienen muchas ventajas pero tambien algunas desventajas importan tes:

  • No funcionan
  • Funcionan como servicios en línea
  • Son tan robustos que requiero mucho trabajo para hacer una tarea sencilla
  • Son tan estructurados y rígidos que si la web que analizas no esta bien contruida simplementen no funcionaran, algunos afirman ser muy tolerantes en estos casos, pero cuando colocas tus web del mundo real... olvidalo: no funciona.
  • No son muy veloces, no estan pensados para trabajar con varios hilos en paralelo.
  • No están listos para trabajar en escenarios que requieran uso de async | await

Por estos y de seguro otros problemas con los que tuve que lidiar decidí ponerme manos a la obra y crear mi propia librería
LinkSpider

Link Spider Logo

Link Spider es una librería de clases portable (PCL) de alto rendimiento capaz de buscar por links en una página web o incluso en un sitio web completo.

Al ser portable es compatible con varios escenarios

  • Windows Desktop Application
  • Web Application [ASP.Net WebForms o MVC]
  • Windows Store Apps
  • Windows Phone Apps
  • Universal Apps

Adicionalmente cuenta con funcionalidades para crear el archivo sitemap.xml que es requerido por los motores de búsqueda para hacer descubrimiento de páginas en tu sitio web.

Adicionalmente este proyecto incluye una utilidad para generar por línea de comandos el sitemap.xml de cualquier sitio web.

La librería funciona con tareas en paralelo para lograr máximo performance en todos los escenarios y cuenta con soporte para trabajar con async | await.

Cómo buscar los vínculos en una página o sitio web con la clase LinkSpider

LinkSpider hace esto de una manera muy sencilla:

 LinkSpider ls = new LinkSpider(webUrl);  
ls.WeaveSinglePage();  

Hecho esto ya podemos acceder a un listado con todos los link de esta página, si queremos explorar todos los link se enlazan en el sitio (no solo la Url pasada por parámetro) lo podemos hacer así:

 LinkSpider ls = new LinkSpider(webUrl);  
ls.WeaveWeb();  

WeaveWeb dispara internamente varias tareas de tal forma que puede revisar varias páginas del sitio web de forma paralela, aumentando la velocidad de análisis de manera vertiginoza.

LinkSpider organiza los links encontrados en una serie de listas:

FullUrlList

Es la lista completa de links apuntando en el mismo dominio.

ExternalUrlList

Es la lista de links apuntando a dominios distintos.

BrokenUrlList

Esta es una lista especial, dentro de la lista de links apuntando al mismo dominio pueden haber algunos links que esten rotos, estos se encuentran en esta lista. Sin embargo esta lista solo se llena cuando se explora el sitio por completo, no se usa cuando se analiza una sola página.

Ejemplo de uso

 LinkSpider ls = new LinkSpider(webUrl);

Console.WriteLine("Starting to Weave Web");  
Console.WriteLine("Exploring and Bulding...");

if (_singleWebPage)  
    ls.WeaveSinglePage();
else  
    ls.WeaveWeb();

Console.WriteLine("An incredible Web has been Weaved");  
Console.WriteLine("Total Links: {0}", ls.FullUrlList.Count());  
Console.WriteLine("Total External Links: {0}", ls.ExternalUrlList.Count());  
Console.WriteLine("Total Broken Links: {0}", ls.BrokenUrlList.Count());  

Soporte para async | await

Si bien LinkSpider realiza todo el trabajo posible en paralelo ofrece ademas soporte para async | await para ello solo basta con usar las versiones async de los métodos expuestos anteriormente:

 await ls.WeaveSinglePageAsync();  
await ls.WeaveWebAsync();  

Filtros de búsqueda

Explorar un sitio web es una tarea dispendiosa y dependiendo del sitio puede que innecesariamente tediosa, en ocasiones no requerimos analizar toda la estructura del sitio, posiblemente necesitamos excluir algunas links que incluyan URLs con un patrón determinado.

Por ejemplo en el caso de los blog puede que no nos interese explorar las Url que contengan el patrón "/tag/" porque sabemos que allí se encuentran links de manera redundante pues estos mismos link pueden estar en otras páginas dentro del blog.

Otro ejemplo es querer excluir las poáginas de administración del sitio "/admin/" .

En estos casos incluir un filtro de búsqueda será de gran ayuda, en especial para disminuir el tiempo de análisis del sitio.

 LinkSpider ls = new LinkSpider(webUrl);  
ls.URLExplorationFilter.Add("/tag/");  
ls.URLExplorationFilter.Add("/admin/");  
ls.WeaveWeb();  

Creando archivos sitemap.xml con la clase SitemapTarantula

Una vez ya tienes una lista de sitios es muy fácil crear un archivo plano con esta información, lo que no necesariamente es tan fácil es crear un sitemap.xml por lo cual Link Spider incorpora la clase SitemapTarantula la cual te ayudará en esta tediosa tarea:

 LinkSpider ls = new LinkSpider(webUrl);  
ls.WeaveWeb();

var tarantula = new SitemapTarantula(ls.FullUrlList);  
string sitemap = tarantula.CreateStringSiteMap();  

Where sitemap contains a well formed xml document like this:

 <?xml version="1.0" encoding="utf-16"?>  
<urlset xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"  
        xsi:schemaLocation="https://www.sitemaps.org/schemas/sitemap/0.9 https://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
        xmlns="https://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://juank.io/</loc>
    <changefreq>daily</changefreq>
    <priority>0.5</priority>
  </url>
  <url>
    <loc>https://juank.io/estrategias-codigo-portable-c-1-pcl/</loc>
    <changefreq>daily</changefreq>
    <priority>0.5</priority>
  </url>
</urlset>  

Generando un XDocument

Si por alguna razón requieres un objeto XDocument para manipular o explorar el sitemap.xml puedes hacerlo de la siguiente forma:

 LinkSpider ls = new LinkSpider(webUrl);  
ls.WeaveWeb();

var tarantula = new SitemapTarantula(ls.FullUrlList);  
XDocument sitemapXdoc = tarantula.CreateXMLDocumentSitemap();  

Soporte para encoding UTF-8

Dado que en CLR el tipo string siempre es Unicode el encoding resultando siempre será como lo viste en el ejemplo de arriba: UTF-16

 <?xml version="1.0" encoding="utf-16"?>  

Sin embargo si por alguna razón lo requieres UTF-8 debes tener en cuenta dos cosas

  1. Que el texto del xml generado en efecto sea
 <?xml version="1.0" encoding="utf-8"?>  
  1. Cuando guardes el archivo asegurarte de usar el encoding correcto

Solución 1 - Generando byte[]

Para solucionar el problema de manera directa basta con llamar al método CreateByteArrSiteMapUTF8 este genera un byte[] con la cadena codificada en UTF-8 lista ara que la guardes en un archivo.

Solución 2 - Controlando la generación del sitemap y el guardado de archivo

Este método consiste en pasar un parámetro opcional al llamar al método CreateStringSiteMap()

 string sitemap = tarantula.CreateStringSiteMap(changeDeclarationTextToUTF8: true);  

Esto soluciona el texto el encabezado, ahora hay que solucionar la codificación real al guardar el archivo, para ello se puede hacer algo como

 string sitemap = tarantula.CreateStringSiteMap(changeDeclarationTextToUTF8: true);  
File.WriteAllText("sitemap.xml", sitemap, Encoding.UTF8);  

Conclusion

LinkSpider es una libreria que realmente te facilitará el trabajo, creeme que no consegirás nada mejor ni más fácil de usar.

Tienes ideas para nuevas funcionalidades? o quieres aportar código fuente?

Las ideas son bienvenidas! puedes hacer tus sugerencias directamente en el Repositorio de Link Spider en GitHub

Para aportar código fuente puedes hacerlo de la misma forma!! todos los aportes son bienvenidos.