Leer un Sitemap XML con Python

Publicado el 07 agosto 2013 por Programacionymas
¿Cuántas veces has intentado crear un proceso que obtenga las urls del sitemap.xml de una página web? A continuación vamos a desarrollar una función en Python que nos permitirá extraer todas las urls almacenadas en un sitemap.xml.
Para comenzar, veamos ¿qué es un sitemap.xml? Según la página sitemap.org (página de referencia básica en este asunto):
Los Sitemaps son una forma fácil que tienen los webmasters para informar a los motores de búsqueda de las páginas que se pueden rastrear en sus sitios web. Un Sitemap, en su forma más sencilla, es un archivo XML que enumera las URL de un sitio junto con metadatos adicionales acerca de cada una de ellas.

O dicho de otra forma, es un listado con todas las urls accesibles de una página web, imagina las facilidades que proporciona el tener un sitemap para los motores de búsqueda, de ahí que luego ellos favorezcan las webs con este tipo de elementos.
Bueno, ya sabemos qué es un sitemap, pero ¿qué estructura tiene? Tal y como dice la definición de sitemap.org, este archivo no es más que un archivo XML con la siguiente estructura:
  1: <?xml version="1.0" encoding="UTF-8"?>
  2: <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  3: 
  4: <url>
  5:       <loc>http://www.example.com/</loc>
  6:       <lastmod>2005-01-01</lastmod>
  7:       <changefreq>monthly</changefreq>
  8:       <priority>0.8</priority>
  9: </url>
 10: 
 11: </urlset> 

Donde cada nodo <url> se corresponde directamente con una url de la web. Lo más importante del nodo <url> es el nodo hijo <loc>, del cual obtendremos la url a extraer.
Además de esta estructura, un sitemap puede emplearse como sitemap índice, es decir, un sitemap principal que contiene a otros sitemaps finales. La estructura de este índice es la siguiente:
  1: <?xml version="1.0" encoding="UTF-8"?>
  2: <sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  3: 
  4:    <sitemap>
  5:       <loc>http://www.example.com/sitemap1.xml.gz</loc>
  6:       <lastmod>2004-10-01T18:23:17+00:00</lastmod>
  7:    </sitemap>
  8: 
  9:    <sitemap>
 10:       <loc>http://www.example.com/sitemap2.xml.gz</loc>
 11:       <lastmod>2005-01-01</lastmod>
 12:    </sitemap>
 13: 
 14: </sitemapindex>

De nuevo, observamos la existencia del nodo <loc>, que en este caso apunta a los sitemaps incluidos en el índice, en lugar de a las urls de la web.
Por tanto, para extraer las urls de un sitemap.xml, deberemos detectar si el sitemap a leer es un archivo de índice o no, cosa que haremos gracias al nodo principal del archivo XML, el cual será <sitemapindex> para archivos índice, y <urlset> para archivos sitemap finales.
A continuación se muestra el código en Python para la lectura de un archivo sitemap.xml, detectando si el archivo es un archivo índice o no. El resultado será un array con todas las urls detectadas.
Mencionar el uso del toolkit LXML para simplificar la lectura y manipulación del archivo XML.
  1: import urllib2
  2: from lxml import etree
  3: from StringIO import StringIO
  4: 
  5: def sitemap_reader(sitemap_url):
  6:     """
  7:     Devuelve una lista con las urls incluidas en el sitemap de la url indicada
  8:     """
  9:     # Crea la lista que almacenará las urls
 10:     urls = []
 11:     
 12:     # Variable que contiene el sitemap en formato xml
 13:     xml = False
 14:     
 15:     # Lee el contenido del sitemap proporcionado
 16:     response = urllib2.urlopen(sitemap_url)
 17:     content = response.read()
 18:     
 19:     # Comprueba si hay contenido que analizar
 20:     if '' != content:
 21:         # Intenta generar el xml a partir del contenido extraído
 22:         try:
 23:             xml = etree.parse(StringIO(content))
 24:         except:
 25:             # Error al procesar el sitemap.xml
 26:             xml = False
 27:         
 28:         # Si se procesó correctamente el XML, procesar el sitemap
 29:         if False != xml:
 30:             # Comprobar si el sitemap es un índice (nodo raíz = "sitemapindex")
 31:             if 'sitemapindex' == xml.getroot().xpath('local-name()'): # Sitemap index
 32:                 # Extrae todos los sitemaps incluidos en el índice (en el nodo "loc")
 33:                 for sitemap_hijo in xml.xpath('//*[local-name() = "loc"]'):
 34:                     # Procesa los sitemaps hijos de forma recursiva
 35:                     new_urls = sitemap_reader(sitemap_hijo.text)
 36:                     # Une todas las urls detectadas en el array final
 37:                     urls = urls + new_urls
 38:             else: # Sitemap final
 39:                 # Extrae las urls almacenadas en el sitemap y las almacena
 40:                 for url in xml.xpath('//*[local-name() = "loc"]'):
 41:                     urls.append(url.text)
 42:     # Devuelve las urls detectadas, pero elimina antes las urls duplicadas
 43:     return list(set(urls))
 44: 
 45:     
 46: # Modo de empleo
 47: urls = sitemap_reader('http://www.example.com/sitemap.xml')

Y vosotros, ¿tenéis un método para leer los sitemaps.xml?
Espero que os sea de ayuda en vuestros proyectos.