En este laboratorio práctico, presentaremos los fundamentos básicos para descargar, procesar, transformar y almacenar localmente datos disponibles de diversas fuentes en la Web. Para ello, nos basaremos en algunas de las bibliotecas más utilizadas en Python, tales como requests o lxml. En la sección final se incluyen referencias adicionales para aquellos interesados en profundizar más en estos aspectos.
Este laboratorio está basado en recuperación y procesado de datos en formato XML. El siguiente laboratorio tendrá por objeto presentar un ejmplo de flujo de trabajo similar con datos en formato JSON, utilizado por numerosas APIs de datos abiertos en Internet, así como datos en formato CSV.
Todo proyecto de ingeniería y análisis de datos comienza por la recopilación, preparación y almacenamiento de datos en un repositorio (local, distribuido o en la nube) para su análisis posterior. En computación, este proceso se suele denominar ETL, por sus siglas en inglés Extract-Transform-Load (Extraer-Transformar-Almacenar).
Así pues, este proceso involucra tres fases:
En este laboratorio, abordaremos el primer paso, aprendiendo cómo recopilar datos disponibles en la Web, representados en formato XML.
Existen multitud de fuentes de datos disponibles públicamente en la Web. Los tres tipos más usuales de fuentes de datos que podemos encontrar son:
Existen muchos tipos de formatos de representación de datos (ya hemos mencionado algunos en la lista anterior). En este laboratorio, nos vamos a concentrar en un formato muy popular, los documentos XML. Para el siguiente ejemplo, vamos a utilizar el fichero feed.xml, disponible en la web de Dive into Python 3 [2]. Asumiremos que el archivo ya lo hemos descargado y almacenado localmente.
Una forma sencilla de automatizar la descarga sería utilizar el módulo urllib
, o si el archivo es muy grande usar el módulo requests
para tener un mejor control del proceso de descarga.
In [58]:
import urllib
urllib.urlretrieve ("http://www.diveintopython3.net/examples/feed.xml", "feed_down.xml")
Out[58]:
A continuación se muestra una fracción del contenido inicial del fichero de ejemplo XML que vamos a emplear.
<?xml version='1.0' encoding='utf-8'?>
<feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'>
<title>dive into mark</title>
<subtitle>currently between addictions</subtitle>
<id>tag:diveintomark.org,2001-07-29:/</id>
<updated>2009-03-27T21:56:07Z</updated>
<link rel='alternate' type='text/html' href='http://diveintomark.org/'/>
<entry>
<author>
<name>Mark</name>
<uri>http://diveintomark.org/</uri>
</author>
<title>Dive into history, 2009 edition</title>
<link rel='alternate' type='text/html'
href='http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition'/>
<id>tag:diveintomark.org,2009-03-27:/archives/20090327172042</id>
<updated>2009-03-27T21:56:07Z</updated>
<published>2009-03-27T17:20:42Z</published>
<category scheme='http://diveintomark.org' term='diveintopython'/>
<category scheme='http://diveintomark.org' term='docbook'/>
<category scheme='http://diveintomark.org' term='html'/>
<summary type='html'>Putting an entire chapter on one page sounds
bloated, but consider this &mdash; my longest chapter so far
would be 75 printed pages, and it loads in under 5 seconds&hellip;
On dialup.</summary>
</entry>
<entry>
<author>
<name>Mark</name>
<uri>http://diveintomark.org/</uri>
</author>
<title>Accessibility is a harsh mistress</title>
<link rel='alternate' type='text/html'
href='http://diveintomark.org/archives/2009/03/21/accessibility-is-a-harsh-mistress'/>
<id>tag:diveintomark.org,2009-03-21:/archives/20090321200928</id>
<updated>2009-03-22T01:05:37Z</updated>
<published>2009-03-21T20:09:28Z</published>
<category scheme='http://diveintomark.org' term='accessibility'/>
<summary type='html'>The accessibility orthodoxy does not permit people to
question the value of features that are rarely useful and rarely used.</summary>
</entry>
<entry>
<author>
<name>Mark</name>
</author>
<title>A gentle introduction to video encoding, part 1: container formats</title>
<link rel='alternate' type='text/html'
href='http://diveintomark.org/archives/2008/12/18/give-part-1-container-formats'/>
<id>tag:diveintomark.org,2008-12-18:/archives/20081218155422</id>
<updated>2009-01-11T19:39:22Z</updated>
<published>2008-12-18T15:54:22Z</published>
<category scheme='http://diveintomark.org' term='asf'/>
<category scheme='http://diveintomark.org' term='avi'/>
<category scheme='http://diveintomark.org' term='encoding'/>
<category scheme='http://diveintomark.org' term='flv'/>
<category scheme='http://diveintomark.org' term='GIVE'/>
<category scheme='http://diveintomark.org' term='mp4'/>
<category scheme='http://diveintomark.org' term='ogg'/>
<category scheme='http://diveintomark.org' term='video'/>
<summary type='html'>These notes will eventually become part of a
tech talk on video encoding.</summary>
</entry>
</feed>
Como podemos ver, XML es un lenguaje de marcado de documentos similar a HTML. Tenemos etiquetas de apertura y cierre, tales como <entry></entry>
. Las etiquetas delimitan campos de datos, cuyo nombre corresponde con el identificador de la entiqueta (por ejemplo, en el caso anterior el campo es entry). A su vez, las etiquetas pueden contener atributos:
<link rel='alternate' type='text/html'
href='http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition'/>
Finalmente, el contenido situado entre cada etiqueta de apertura y cierre es el texto de ese campo:
<author>
<name>Mark</name>
</author>
A las herramientas o funciones que pueden leer e interpretar datos en un formato de representación documental específico (como XML, HTML, etc.) se les de denomina analyzadores sintáticos o parsers. Las bibliotecas que automatizan la lectura en interpretación de datos en formatos específicos siempre proporcionan funciones con este cometido.
Existen además, mecanismos para validar el contenido de un documento en formato XML, tales como la especificación de un documento DTD o de un esquema XML. En determinados entornos, esto forzará a la función que interpreta los datos compruebe si se sigue estrictamente el esquema de representación, devolviendo errores en caso contrario.
Para este laboratorio, utilizaremos la biblioteca Python lxml
, una de las más extendidas y que mejor rendimiento proporciona para lectura y escritura de datos en formato XML o XHTML.
Esencialmente, existen dos aproximaciones para cargar y procesar datos en formato XML con esta biblioteca (al igual que ocurre con otras similares):
En primer lugar, debemos cargar la biblioteca la clase lxml.etree
que permite crear árboles de elementos XML en memoria.
In [20]:
from lxml import etree
El siguiente paso será leer nuestro fichero de datos, que ya hemos descargado.
In [51]:
with open('feed.xml') as f:
tree = etree.parse(f)
print "XML version is: %s" % tree.docinfo.xml_version
print "XML encoding used is: %s" % tree.docinfo.encoding
# Obtener elemento root del documento
root_elem = tree.getroot()
# Get copy of dict of namespace prefixes in document
ns_map = tree.getroot().nsmap.copy()
print "Diccionario de prefijos espacio de nombres:"
print ns_map
In [38]:
# Algunas operaciones básicas
# Iterar sobre todos los hijos de un elemento en el árbol
for child in root_elem.iterchildren():
print child.tag # Como vemos, los espacios de nombres también se analizan
In [49]:
# Búsqueda directa de elementos
for child in root_elem.iterfind(".//{%s}author" % ns_map[None]):
for gchildren in child.getchildren():
print "%s: %s" % (gchildren.tag, gchildren.text)
In [53]:
# Encontrar todos los elementos con cierto atributo
for elem in tree.findall('//{%s}*[@href]' % ns_map[None]):
print "%s href: %s" % (elem.tag, elem.get('href'))
Si la opción de cargar el árbol del documento en memoria no es viable, entonces podemos utilizar el análisis de código dirigido por eventos. En este caso, el iterador producirá un nuevo evento cada vez que encuentre una etiqueta de apertura o cierre del tipo especificado. Veamos un ejemplo.
In [57]:
with open('feed.xml') as f:
# Si no pasamos el argumento 'events', por defecto solo se generan eventos al cierre
# de cada etiqueta (eventos "end")
for event, element in etree.iterparse(f, events=("start", "end")):
print("%5s, %4s, %s" % (event, element.tag, element.text)) # Podemos acceder a la API de cada elemento
Es importante recalcar que en el momento de recibir un evento de tipo start, puede que el texto, y los nodos hijos del elemento aún no hayan sido analizados. Solo el evento de tipo end garantiza que el elemento ha sido analizado por completo.
Sin embargo, este procedimiento va cargando datos de todos los elementos del árbol en memoria, para mantenerlos accesibles. Por lo tanto, si queremos realmente ahorrar memoria hay que ir borrando los elementos que ya no necesitemos, llamando al método clear()
sobre dicho elemento. Esto borrará ese elemento y todos el subárbol que cuelga por debajo del mismo.
Ejemplo [5]:
elem.clear()
while elem.getprevious() is not None:
del elem.getparent()[0]
En este laboratorio hemos presentado los fundamentos para la descarga y extracción de datos de documentos XML en la Web. En particular, se han cubierto los siguientes aspectos:
En el próximo laboratorio, abordaremos la carga de datos representados con otros formatos populares: CSV y JSON.
In [ ]: