Lecciones aprendidas haciendo webscrapping

Hoy quiero comentar las distintas lecciones que he aprendido tras concluir (con éxito) la obtención de las empresas participantes en #Fitur 2015, la Feria Internacional del Turismo de España, y que se celebra todos los años en Madrid, cita obligada para el sector del turismo.

Al final, he tardado algo más de 3 días en obtener la información, empleando unas 160 lineas de código. ¿160 líneas solamente? Si, más o menos! El tiempo total de desarrollo ha sido poco más de 12 horas, más el tiempo de procesado (unas 10-14 horas), repartidos en una semana más o menos.

Permitánme que no haga público el código fuente, pues se trata de información sensible, aun siendo pública, aunque si estas muy interesado, en ese caso, escríbeme un email.

El desafío

Para salir con éxito del desafío, nada que poner a trabajar mis habilidades en bases de datos, Python, HTML5 y CSS, pues el acceso a dicha información sólo es posible via web, e imposible manejando URLs.

La tarea se antojaba … divertida, poco convencional, poniendo trabas para acceder a la información, que está disponible, pero a pequeñas dosis (como debe ser!).

Lección 1: disponer de buenas herramientas

La primera de las lecciones aprendidas es que hay que disponer de las mejores herramientas para trabajar, y en cuestión de programación y datos, Python es una magnífica opción. Siempre me arrepentiré de no haberlo aprendido antes, pero me alegro de haber apostado decididamente por Python.

Si vas a trabajar con webscrapping, es fundamental conocer BeautifulSoap y Selenium, pues son dos grandes librerías que simplificar mucho.

Para la base de datos, esta vez he optado por algo novedoso pues he usado SQLite, algo que casi nunca hago. El resultado es positivo, si bien, he empleado parte del tiempo en desarrollar mi propia clase para SQLite, y del que te hablaré en breve.

Lección 2: conoce cómo depurar en Python

Sin lugar a dudas, todo proceso de programación requiere de comprobaciones, de ahí que conocer cómo se depura sea valor vital, tanto por tiempo a invertir como por cómo solventar errores. Ya expuse cómo usar el depurador de PyScripter, aunque ahora estoy usando también PyCharm.

Lección 3: escribe buen código

No me había percatado hasta hace poco de que seguir buenas pautas de escritura de código permite:

  • Que los códigos sean más legibles y entendibles
  • Escribir menos código

Ya lo he comentado en una entrada anterior, que Python tiene su famosas PEP 8 que todo desarrollador debería seguir, no por el bien del resto de programadores, sino por su propio bien!

Hace pocas semanas he tenido que trastear un viejo programa escribot en Visual Basic 6, y …. madre mía, cuando me hubiera gustado haberlo hecho bien. Aquí algunas de las cosas mal hechas: sobre variables, demasiadas variables globales, poca claridad en el texto, …. Menos mal que voy cambiando.

Lección 4: pequeños ejercicios que crees (erróneamente) que sirven de poco

Cuando aprendes a programar, siempre he considerado que una buena manera de hacerlo es mediante al resolución de pequeños (a veces, no tan pequeños) simples (a veces, son más complejos de lo que aparentan) problemas.

En este mismo blog he esrito multitud de entradas con problemas sencillos, que aparentemente no tienen dificultad, pero que están realizados para aprender a usar alguna librería fundamentalmente. La resolución de esos pequeños ejercicios son los que te permitirán solucionar problemas más grandes, y no al revés.

Pondré un ejemplo …. sencillo: comparación de cadenas. En lenguajes como el español donde hay signos de puntuación cobra especial relevancia, pues no es lo mismo “avión” que “avion“. Para las bases de datos, ambas son distintas. Pero ¿es posible quitar los dignos de puntuación de una cadena? Sí, más fácil de lo que parece, si se domina el módulo correcto.

import unicodedata

def elimina_tildes(s):
    return ''.join((c for c in unicodedata.normalize('NFD', s) if unicodedata.category(c) != 'Mn'))

test = u"avión"
print (test, elimina_tildes(test))

Y aquí en acción:

tildes

tildes

Este pequeño código facilita poder comparar evitando problemas con los signos de puntuación.

Lección 5: siempre hay problemas con los que no contabas, y que tendrás que solucionar

Efectivamente, parece que el trabajo es sencillo, pero de repente, un error inesperado que solventar en un algoritmo que está bien diseñado y programado. Y es que, muchas veces se solventan algoritmos pensando solo en los primeros registros a tratar, pero no sabemos qué pasará cuando vayamos por la repetición 1.000 o 5.000. Así, me encuentro con que para trabajar con direcciones, los primeros registros aparecen tal que así:

Málaga 29001 Málaga (ESPAÑA)

Y … sucede un error al tratar otro registro, tal que así:

 Via Palestro, 30 00185 Roma   (ITALIA)

O sea, el diseño de un algoritmo teóricamente sencillo que no lo es tal. La primera reacción ante el primer caso es dividir la cadena por espacion en blanco. Cómo puedes apreciar, no es tan directo extraer el municipio, la provincia y el país.

La modificación del algoritmo, a la nueva versión, retrasará tus planes iniciales ante un planteamiento erróneo: pensar que todo es MUNICIPIO NUMERO PROVINCIA (PAIS).

Lección 6: webscrapping no es solo INSERTs

Teóricamente, la ejecución del script programado sólo seria 1 vez. Digo, teóricamente, porque la realidad es bien distinta: subsanar cualquier error hace que tengas que volver a empezar, y entonces tendrás registros duplicados.

Así que, toca replantearte (otra vez) tu algoritmo inicial para verificar si antes de insertar un registro, este no ha sido previamente insertado. Una vez más, la tarea inicial aparentemente tan sencilla vuelve a complicarse (no demasiado esta vez).

Lección 7: velocidad de peticiones

Parece un poco absurdo, pero para no saturar un servidor, una técnica que suelo emplear es la de aplicar pausas. Para evitar parecer un robot, aquí una sencilla solución. Creamos una lista con los tiempos, y seleccionamos uno al azar. Facil, eh? Un 10% de las veces la espera es de 3 segundos, un 30% es de 2 segundos, y el resto, de un segundo.


tiempos1 = [1, 2, 3, 1, 1, 2, 1, 1, 1, 2]
time.sleep(random.choice(tiempos1))

Lección 8 y última: resolver retos es divertidos

No te quepa la menor duda de que me he divertido mucho, y he aprendido bastante, además del objetivo conseguido!

Por un lado, he aprendido que escribir buen código aumenta la productividad, reduce errores, y facilita su lectura posterior. También he aprendido que para evitar ser bloqueado, mejor aplicar pausas de tiempos aleatorios.

Por otro, SQLite es una base de datos divertida, que poco tiene que ver ni con MySQL ni con Microsoft Access, y que gracias a este proyecto, me he creado una clase que me facilitará la vida en futuros proyectos.

Espero que te puedan servir estas leeciones para cuando programes, y buen día!