Automatización Web

La información es poder. Éste es un principio tan básico en las finanzas que se penaliza duramente a quien hace uso privilegiado de ella. Pero en cambio es perfectamente lícito analizar de forma novedosa información ampliamente disponible para obtener una ventaja en los mercados. Sin embargo aunque esta información se encuentre públicamente en Internet no siempre habrá una hermosa API en JSON o XML, lista para su consumo por algoritmos, con lo cual tener unas nociones básicas de automatización web nos va a resultar de gran ayuda en nuestro trabajo.

En realidad el epígrafe “automatización web” (o web automation, en inglés) va más allá de la mera recolección de datos, un denostado concepto conocido como web scraping, e incluye también la posibilidad de interactuar con las propias páginas web, acreditándose, rellenando formularios, proporcionando datos y activando servicios. Es decir, interacción bi-direccional de forma autónoma.

Un ejemplo práctico serían los conectores que hemos desarrollado en Ágora Asesores Financieros para encontrar las cotizaciones diarias de ciertos fondos exóticos (no disponibles en las cañerías de datos de un Bloomberg o un Factset) y actualizarlas selectivamente en la nube donde se encuentran las carteras de nuestros clientes, ahorrándonos así mucho tiempo, esfuerzo y errores.

El lenguaje de programación Python nos proporciona una serie de vías sencillas para efectuar la automatización web. Vamos a dar un breve recorrido introductorio por ellas con grados crecientes de funcionalidad.

1. A Pelo

El módulo básico para interacción con Internet en Python es requests. Sólo con ello ya tenemos lo suficiente para trabajar. Por ejemplo, vamos a obtener la cotización del ETF de X-Trackers sobre el Euro Stoxx 50 desde Morningstar.

import requests as req

res = req.get("http://www.morningstar.es/es/etf/snapshot/snapshot.aspx?id=0P0000HNXD")
text = res.text
start = ">EUR\xa0"
end = "<"
start_pos = text.index(start)
end_pos = text.index(end, start_pos)
print(text[start_pos+len(start):end_pos])

Primero descargamos la página web entera desde Morningstar, y a continuación encontramos el valor que se encuentra entre las dos cadenas de texto contenidas en las variables start y end.

No tiene mayor secreto, pero tampoco es un método muy robusto y las expresiones necesarias para aislar cadenas de texto se nos pueden complicar muy rápidamente. El uso de expresiones regulares nos puede salvar temporalmente; por ejemplo lo anterior se convertiría en:

import requests as req
import re

res = req.get("http://www.morningstar.es/es/etf/snapshot/snapshot.aspx?id=0P0000HNXD")
print(re.findall(">EUR\W([^\<]*)<", res.text)[0])

Breve, sin duda, pero las expresiones regulares pueden ser por su lado endiabladamente complejas de descifrar. Y tampoco solucionamos el problema fundamental de fragilidad: a nada que cambie un poco el texto de la página sin que cambie su estructura, o que por ejemplo aparezca el texto “EUR” en otra casilla superior, el sistema fallará.

2. Con Estructura

Por suerte tenemos otra opción: en lugar de navegar por texto llano, podemos navegar por la estructura lógica propia de la página web que estamos visualizando y que viene definida por el código HTML. Python cuenta el módulo BeautifulSoup que nos va a permitir navegar la estructura usando expresiones de CSS una vez lo hayamos instalado.

> pip install bs4

Nuestro código se convierte entonces en:

import requests as req
from bs4 import BeautifulSoup as soup

res = req.get("http://www.morningstar.es/es/etf/snapshot/snapshot.aspx?id=0P0000HNXD")
html = soup(res.text, "html.parser")
print(html.select("#overviewQuickstatsDiv table td.text")[0].text)

Nos queda limpiar la parte “EUR” si queremos, y listos. La búsqueda CSS se puede interpretar como “Encuentra el elemento identificado como overviewQuickstatsDiv, y dame el contenido de la primera celda de clase text que encuentres dentro de su tabla”. Mientras no cambie la estructura de la página, algo bastante infrecuente, nuestra búsqueda tendrá éxito.

3. Con Sesiones

En ocasiones los servicios web no estarán disponibles directamente, sino que tendremos que identificarnos (en términos de Internet, iniciar una sesión) y realizar una serie de pasos antes de completar nuestra tarea. Ningún problema, Python está también está dispuesto a echarnos una mano aquí.

Si bien el propio módulo requests cuenta con soporte para sesiones, el manejo de formularios, cookies y estados se nos puede complicar rápidamente, así que es aconsejable utilizar alguna librería de grado superior que encapsulen esas labores y conviertan nuestro periplo en un simple paseo por el parque digital, siguiendo enlaces, rellenando formularios y pulsando botones.

Mechanize ha sido tradicionalmente una librería muy recomendable, pero por desgracia se ha quedado un tanto obsoleta al sólo brindar soporte para Python 2.x. En cambio con RoboBrowser contaremos con pleno soporte para Python 3.x.

> pip install robobrowser

Para estos ejemplos más elaborados he creado un proyecto-laboratorio en GitHub llamado ScrapHacks que puede clonar en su máquina local y realizar sus propios experimentos. ¡De nada!

from lxml import etree

url = "https://www.quefondos.com/es/planes/ficha/?isin=N2676"
resp = req.get(url)
html = etree.HTML(resp.text)

value = html.xpath("//*[@id=\"col3_content\"]/div/div[4]/p[1]/span[2]")

Para el caso del manejo de sesiones vale la pena que revise el archivo pricescrap.py, donde combinamos RoboBrowser con una librería alternativa a BeautifulSoup llamada Lxml, interesante porque permite usar expresiones XPath además de CSS.

> pip install lxml

En el archivo de ejemplo descargamos el precio de tres activos financieros con métodos similares a los anteriores, pero acto seguido navegamos a un servicio en la nube, iniciamos sesión con credenciales privadas y nos encargamos de volcar dichas actualizaciones de precios en carteras específicas de clientes.

SEO-spiders-2

4. Con Navegador

Aunque en el ejemplo anterior hablemos de “navegar” o usemos el término browser en nuestro código, es bueno que veamos que se trata de una metáfora. RoboBrowser no es un browser web en el sentido que lo son Chrome, Firefox o Safari, solo emula parte de sus funcionalidades. Pero carece de muchas otras, como por ejemplo la capacidad de ejecutar el código JavaScript asociado con las páginas.

Esto es importante. En ocasiones el código JavaScript es puramente decorativo, pero en otras su ejecución es fundamental para la correcta interpretación de la página. Tradicionalmente, la composición activa de la página se realizaba en el lado del servidor y al llegar al lado del cliente se trataba de un elemento estático, pero debido al uso de ciertos frameworks de desarrollo, como por razones de seguridad y de flexibilidad, cada vez más la composición activa de la página se realiza en el lado del cliente. En tales casos, al no poder interpretar JavaScript se fallará estrepitosamente.

Pero no tiremos la toalla tan pronto. Python cuenta con una excelente integración con Selenium, un proyecto que nos permitirá tomar el control del navegador de nuestra elección y actuar como si estuviésemos sentados en frente de la máquina, haciendo clicks en botones y rellenando casillas de forma que es prácticamente imposible diferenciar entre una sesión humana y una automatizada.

from selenium import webdriver

driver = webdriver.Chrome()
driver.set_window_size(1000, 1000)
driver.get("https://www.duolingo.com")
driver.find_element_by_id("sign-in-btn").click()
driver.find_element_by_id("top_login").send_keys(credentials["username"])
driver.find_element_by_id("top_password").send_keys(credentials["password"])
driver.find_element_by_id("login-button").click()

Permítanme aquí un pequeño paréntesis: soy un asiduo usuario del increíble servicio de Duolingo, con el cual he aprendido ya varios idiomas y espero aprender muchos más. Sin embargo, la App móvil no permite ver las lecciones gramaticales (por ejemplo la parte de “Tips and Notes” aquí a pié de página) que si bien son ignorables cuando un Español aprende Portugués, se vuelven imprescindibles si se pretende sobrevivir al aprendizaje de una lengua tan distante como el Ruso.

Cómo no es de caballeros quejarse de un regalo y yo soy en todo caso más de actuar, para este ejemplo me he creado un simple script llamado duolingoscrap.py que extraer y agrupar las lecciones gramaticales en un conveniente resumenEl reto es que el web de Duolingo es uno de esos que se interpretan en el lado cliente, con lo cual se hace fundamental el uso de Selenium, combinado con BeautifulSoup.

5. Con Control de Tráfico

Subiendo, subiendo vamos en nuestra pirámide. ¿Qué nos queda, ahora que somos capaces de navegar el web como un humano? Pues tal vez capacidades extrahumanas. En algunas ocasiones la interpretación del código JavaScript es tan increíblemente complicada que nos resulta imposible encontrar el elemento web que queremos extraer, frecuentemente con el deseo explícito de mantenerlo a salvo de ojos demasiado curiosos como los nuestros.

En tales casos lo que podemos hacer es observar directamente el tráfico que entra y sale de nuestra máquina para encontrar dicho elemento. Eso lo podemos lograr combinando Selenium con BrowserMobProxy como intermediario (proxy) entre nuestro navegador y el mundo. Se trata de un programa escrito en Java con lo cual necesitaremos tener un JRE funcionando en nuestra máquina, pero este wrapper nos permite trabajar con él como si se tratase de otra pieza en nuestro arsenal de Python.

from selenium import webdriver
from browsermobproxy import Server

browserMob = ".%sbrowsermob-proxy-2.1.4%sbin%sbrowsermob-proxy" % (os.path.sep, os.path.sep, os.path.sep)
server = Server(browserMob)
server.start()
proxy = server.create_proxy()

chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument("--proxy-server={0}".format(proxy.proxy))
driver = webdriver.Chrome(chrome_options = chrome_options)

proxy.new_har("safaribooks")
driver.get(url)
har = proxy.har
for entry in har['log']['entries']:
    # processing

¿Que queremos descargar una película oculta en el código? Prescindo de entender cuál es el proceso activación del vídeo, simplemente lo pongo en marcha y cuando en mi tráfico aparezca un elemento multimedia lo capturo y listos.

Este es precisamente el caso de uso del script safarihacks.py, que utiliza Selenium para iniciar sesión con una cuenta de prueba en la suculenta librería on-line SafariBooks para proceder a descargar en serie los libros en los catálogos de nuestro interés. En el caso de que sean cursos multimedia, el código se apoya en BrowserMobProxy para identificar y descargar los archivos de vídeo.

Finalmente, la guinda en el pastel la pone una integración con PDFReactor para convertir automáticamente los libros descargados al formato PDF.

Con estas técnicas básicas se pueden crear robots web (también llamados arañas) muy potentes. Diviértanse experimentando con las ideas en sus propios proyectos, y si este post les ha resultado de utilidad les agradecería si lo pudiesen recomendar y compartir en sus redes sociales. ¡Buena suerte y hasta la próxima!

Resumen

Python nos proporciona también los medios para convertir la automatización web en una tarea muy sencilla.

No se pierdan el proyecto https://github.com/isaacdlp/scraphacks con ejemplos prácticos:

Componentes útiles:

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s