Web scraping con Python: capturas reales de datos
En el #desafíoPython llegamos a la tercera entrega donde ya estamos capturando datos reales para construir nuestro ranking de podcasts.
Este artículo es el tercero de la serie de #desafíoPython. Consulta la primera y segunda parte. Continúa en la cuarta entrega.
¿Qué hemos conseguido hasta ahora? ¶
Estamos inmersos en el hito 3 que veíamos en el planteamiento del proyecto.
El código está disponible en este repositorio de GitHub, con dos avances muy concretos para que te sea más fácil hacer el seguimiento:
- Versión 0.2: Scraping de datos desde ficheros locales guardados con el navegador.
- Versión 0.2.2: Scraping de los datos desde la URL de origen.
Existe una versión 0.2.1 pero es correcta en cuanto a la infraestructura, ya que no aparece la dependencia respecto a la librería requests
.
¿Qué dificultades nos hemos encontrado? ¶
El esquema del HTML de las páginas de ivoox es bastante sencillo de comprender, con lo que ha sido fácil capturar los datos que nos interesan de forma correcta.
Todo el trabajo hasta aquí se ha realizado sobre el fichero app.py
, en el próximo hito ya nos marcamos como objetivo estructurar el código de mejor forma.
Aislando la lectura de datos ¶
De una parte hemos aislado la lectura de los ficheros (en la versión 0.2) o la captura de las URL (versión 0.2.2) en las funciones read_file
y get_url_content
respectivamente. En ambas he creído conveniente tener una posibilidad de captura del error si el fichero no existe o si la URL no es obtenida como se espera.
Para la captura de recursos en internet utilizamos la librería requests, un auténtico estándar en el mundo de python.
Para eso nos hemos servido del método res.raise_for_status()
de la librería requests
como puedes ver en la documentación.
Si quieres añadirla a tu proyecto (está listada en el requirements.txt
) solo tienes que ejecutar:
pip install requests
A medida que vayamos avanzando nos deshacemos de la lectura de ficheros en local para sustituirla por las peticiones directas sobre la web, aunque para hacer testing siempre es mejor consumir recursos propios y por eso tenemos los ficheros de muestra en la carpeta sandbox/content
Análisis de los datos ¶
La mayor complejidad estaba en intentar tener un código lo más limpio y corto posible.
Al tener un histórico basado en programación procedural el primer intento por ejecutar el código que recorre el HTML era farragoso y confuso.
Intento sacar partido de las grandes características que ofrece python para programar en una línea. Son los afamados “oneliners” que puedes ver con ejemplos en el wiki oficial y en PythonTips.
El detalle de código puedes verlo en este commit y en este código:
for programs in resource.findAll('div', {'class': ['modulo-type-programa']}):
podcasts.append({
'title': ['%s' % name.attrs['content'] for name in programs.select('meta[itemprop='name']')][0],
'description': ['%s' % desc.attrs['content'] for desc in programs.select('meta[itemprop='description']')][0],
'url': ['%s' % url.attrs['content'] for url in programs.select('meta[itemprop='url']')][0],
'episodes': ['%s' % micro.getText().strip() for micro in programs.select('.microphone')][0]}
)
Aquí pasan muchas cosas, leyendo de arriba a abajo y de izquierda a derecha:
- Con un
for
capturamos cada bloque de información por podcast (lo vimos en el hito 3) - Añadimos a la lista
podcasts
un nuevo elemento de tipo diccionario, donde queremos tener ya los datos estructurados. - Para cada elemento del diccionario capturamos la cadena de texto que nos interesa.
- La recursividad del
for
en línea nos genera una lista de elementos. - Nos quedamos solo con la primera de las coincidencias.
Cuando tenemos for name in programs.select('meta[itemprop='name']')
realmente lo que estamos haciendo es capturar dentro de cada bloque con clase modulo-type-programa
los meta
que tienen el atributo itemprop='name'
.
Aunque la lectura sea aparentemente más sencilla, se hacen demasiadas cosas en cada línea. No fue fácil llegar hasta este nivel de simplificación.
Captura de errores ¶
Es una constante en cada uno de los métodos empleados. Con la pareja try\except se trata de generar un output del error que sea más legible y fácilmente identificable.
Por ejemplo, si el marcado de HTML cambiara en origen, nuestra función de scraping de los datos daría un error del tipo “IndexError”, porque la lista generada no tendría ningún valor (recuerda que capturamos el primer elemento de esa lista con [0]
).
Eso no dice nada, así que capturamos el error y mostramos en tiempo de ejecución que “No se puede captuar el contenido”, más legible y fácil de interpretar.
¿Cuando completaremos el hito 4 del proyecto? ¶
El siguiente hito estaba descrito de esta forma:
Hito 4. Replicar el comportamiento del hito 3 para capturar varias páginas y almacenarlas de forma sencilla.
Así que asumiremos que ya conocemos lo que queremos capturar en detalle: un ranking en base a la clasificación ofrecida por ivoox. Esta sería la primera página para los programas de Internet y tecnología.
Fijémonos aquí que las URL de ivoox tienen algunas particularidades (siempre existen, en todos los casos) donde la información viene construida como una cadena de texto: podcast-internet-tecnologia_sc_f445_1
Si pasamos a la segunda página del listado es: podcast-internet-tecnologia_sc_f445_2
Para el caso de la categoría “Desarrollo profesional” tenemos esto: podcast-desarrollo-personal_sc_f438_1
Así que todo parece indicar que tenemos un patrón que podríamos utilizar, considerando el guión bajo nuestro separador:
- podcast-nombre-categoria
- sc (no sabemos que significa)
- f (no sabemos que significa)
- Identificador de la categoría o subcategoría
- Número de página de resultados
Aprovecharemos esta información para generar el código que capture la información sin necesidad de llamar una por una a todas las rutas de las que queremos extraer el contenido.
Los objetivos serían estos:
- Capturar la lista de los 100 primeros podcasts de una categoría.
- Almacenarlos en un fichero de texto o un CSV para su posterior consulta.
- Refactorizar para poder almacenar más de una subcategoría.
- Estructurar la aplicación, si es menester, para que la funcionalidad esté mejor repartida.
Recursos para lograrlo ¶
Todos en referencia a como trabajar con archivos:
- El capítulo 8 y capítulo 14 de Automate Boring Stuff donde nos cuentan como leer y almacenar información de los ficheros en plano y CSV.
- Leer y escribir en ficheros de Python for beginners.
- Ficheros de texto en Python en Pitando.
¿Qué tal te ha ido? ¶
Cuéntanos que tal te ha ido a través de los comentarios o en twitter con el hastag #desafioPython.
Escrito por:
Daniel Primo
12 recursos para developers cada domingo en tu bandeja de entrada
Además de una skill práctica bien explicada, trucos para mejorar tu futuro profesional y una pizquita de humor útil para el resto de la semana. Gratis.