Learning Python

Continuando con la serie, hoy abordamos el scrapping y los logs; comencemos.

Haciendo scraping de un RSS.

Comencemos por el principio, ¿qué es eso del scraping? Scraping es la técnica por la cual se consigue extraer de una web (como por ejemplo un foro, un blog o la página del tiempo) contenido mediante un programa informático haciendo uso de los elementos web (HTML, XML, etc.).

Explicado de manera vaga que es el scraping, toca instalar dependencias para poder recolectar la información de nuestro URL_FEED. Ejecuta en el terminal el siguiente comando:

pipenv install beautifulsoup4==4.9.0 lxml==4.5.0 requests==2.23.0

Recuerda que no te vale ejecutar este comando en el terminal de Python si aún sigues dentro del terminal de Python (el que comienza con >>>) deberás cerrarlo. Para cerrarlo escribe exit().

Ahora crea desde el IDE un nuevo fichero llamado scra.py en src (soy una graciosa, lo sé :P)

Y escribe el siguiente código:

"""Collector of articles."""
import requests
from bs4 import BeautifulSoup


def collect_feed(url) -> dict:
   page = requests.get(url)
   if page.status_code == 200:
       soup = BeautifulSoup(page.text, 'xml')

       for item in soup.find_all('item'):
           print(item.title.text)

Aquí hemos realizado la importación de las librerías que hemos instalado mediante pipenv (requests y BeautifulSoup) y hemos creado una función, la cual atacará a una url y tras obtener datos leerá su contenido, buscará todos los objetos llamados item e imprimirá el título de cada elemento. Puedes contrastar los cambios aplicados en el repositorio.

Supongo que te preguntarás de dónde sale item. Recomiendo que veas la url de la cual se leen datos, accede a: https://www.apsl.net/blog/feed y entenderás un poco mejor qué es lo que estamos haciendo.

Podrás observar que es un documento XML compuesto por multitud de elementos llamados <item>...</item>. Esos bloques son los que estamos leyendo, y para cada uno solo extraemos el texto de la sección <title>...</title> con item.title.text.

Probemos nuestro código, para ello en el terminal abrimos python y realizamos las importaciones de nuestras clases (a partir de ahora a un fichero de python le llamaremos clase) para testearlas.

python                        
Python 3.7.3 (default, Dec 20 2019, 18:57:59)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from settings import Settings
>>> from scra import collect_feed
>>> collect_feed(Settings.URL_FEED)
Configuración server django con nginx, gunicorn y postgreSQL
Configuración inicial para un servidor
Once upon a time in Barcelona: Tech success case using Cloud
How to parse huge SOAP messages optimizing memory with Python
Remote First : haciendo el trabajo más humano a través de la tecnología
Sistema de extracción de información mediante gramáticas y NLTK, parte 2
Sistema de extracción de información mediante gramáticas y NLTK, parte 1
Data Science: Revenue Management, situación y problema
Data Science: La potencia sin control no sirve de nada
Canto coral, on boarding e informática
¡Nuevo artículo científico!
Meet the team: Juan Moreno, CTO de Desarrollo
Amazon AWS abrirá centros de datos en España
Tarugo4 - post resaca
Percona Open Source Database Conference 2019
Meet the team: Antoni Aloy, CEO y fundador de APSL
RootedCon Valencia 2019
REACT NATIVE EU 2019
APSL at the PyDay Mallorca 2019
Lazy Loading - Hagamos las webs más rápidas y menos pesadas
>>>

Si obtuviste este resultado o uno de similar ¡enhorabuena! Quiero destacar en especial la invocación a la función mediante la variable global definida en el fichero .env, collect_feed(Settings.URL_FEED) como los RSS están estandarizados podríamos cambiar la dirección de APSL por otra cualquiera y obtendremos resultados. Te invito a que pruebes de cambiarla por, por ejemplo, https://www.muylinux.com/feed

Recuerda salir de la consola de python y abrirla de nuevo, ya que el fichero .env requiere que reinicies ( exit() para salir de la consola de Python ).

El proyecto debería quedar así.

Incluir un sistema de logs

El uso del comando print() está totalmente desaconsejado para mostrar resultados a modo de logs, en su lugar tenemos que importar la clase logging propia de python, completaremos nuestra clase scra.py para que se vea como debería de ser un manejo correcto.

"""Collector of articles."""
import logging

import requests
from bs4 import BeautifulSoup


logging.basicConfig(format='%(asctime)-15s [%(levelname)s] %(filename)s - %(message)s', level='DEBUG')
logger = logging.getLogger(__name__)


def collect_feed(url) -> dict:
   logger.debug(f'Connecting to {url}...')
   page = requests.get(url)
   if page.status_code == 200:
       logger.debug('Connected!')

       soup = BeautifulSoup(page.text, 'xml')

       titles = []
       for item in soup.find_all('item'):
           titles.append(item.title.text)

       titles = [x for x in titles if x]
       if titles:
           logger.info(f'{len(titles)} articles collected.')
       else:
           logger.info('No articles collected.')

Puedes contrastar los nuevos cambios aplicados en el repositorio.

Existen distintos niveles de log, por norma general cuando escribamos logs podemos regirnos por:

  • DEBUG: Información detallada, interesante sólo cuando se pretende diagnosticar un problema.
  • INFO: Mensajes para dejar constancia de que el bloque ha sido ejecutado como era de esperar.
  • WARNING: Indica que algo inesperado ocurrió o que el evento podría causar problemas en el futuro. El programa puede continuar ejecutándose con normalidad.
  • ERROR: Indica un problema importante, la aplicación no ha podido finalizar el bloque.
  • CRITICAL: Indica un gran problema, suficientemente grande como para detener la ejecución de la aplicación.

Gracias a que existen distintos tipos de nivel de log, se puede determinar hasta dónde quieres logear mensajes. Esto es posible gracias al parámetro level de la función basicConfig. Se mostrarán todos los mensajes del nivel indicado y los que tengan un nivel de mayor criticidad.

Si realizamos las pruebas de nuevo desde la consola de python obtendremos algo muy diferente:

>>> from settings import Settings
>>> from scra import collect_feed
>>> collect_feed(Settings.URL_FEED)
2020-05-12 19:52:06,548 [DEBUG] scra.py - Connecting to https://www.apsl.net/blog/feed...
2020-05-12 19:52:06,551 [DEBUG] connectionpool.py - Starting new HTTPS connection (1): www.apsl.net:443
2020-05-12 19:52:06,756 [DEBUG] connectionpool.py - https://www.apsl.net:443 "GET /blog/feed HTTP/1.1" 301 0
2020-05-12 19:52:06,841 [DEBUG] connectionpool.py - https://www.apsl.net:443 "GET /blog/feed/ HTTP/1.1" 200 138102
2020-05-12 19:52:06,925 [DEBUG] scra.py - Connected!
2020-05-12 19:52:06,945 [INFO] scra.py - 20 articles collected.
>>>

Ya coge otro matiz el asunto, ya podemos ver que clases son las invocadas, tenemos una fecha que nos permite identificar con precisión cuando fueron los eventos, incluso tenemos el nivel de traza (se observan los niveles DEBUG e INFO).

Con el tiempo veremos cómo los logs serán nuestro gran aliado para detectar errores y saber donde se producen, de momento nos quedaremos con que son buena práctica y que debemos de esforzarnos por usarlos, pero con precaución, tampoco hay que abusar de ellos. El exceso de información puede acabar en desinformación!

El proyecto debería quedar así.


Y hasta aquí por hoy, se que ha sido un articulo algo breve, os compensaré en el próximo artículo, donde hablaremos de la persistencia de datos, tocaremos un poco el sistema de logging para mejorarlo y cambiaremos código en scra.py para adaptarlo al logging. Hasta la semana que viene #codelovers! :D