Mostrando entradas con la etiqueta webbrowser. Mostrar todas las entradas
Mostrando entradas con la etiqueta webbrowser. Mostrar todas las entradas

sábado, 27 de abril de 2024

Campeones de Gráficos.

 Basado en el proyecto de la RapsberryPi Org "Charting Champions".

¿Qué es lo que haremos?

Descubriremos el poder de las listas en Python creando un gráfico interactivo de la medallas olímpicas conseguidas por los diferentes países.

Los juegos olímpicos empezaron en el año 1896: miles de atletas representaron a centenares de naciones de todo el mundo. Los juegos olímpicos modernos se inspiraron en las olimpiadas que se realizaban en Grecia en la antigüedad.

Lo que haremos:

- Usar listas para almacenar los datos.

- Crear un gráfico usando la librería Pygal.

- Cargar los datos haciendo que tu programa los lea desde un archivo.


Creando un gráfico.

Lo primero que haremos será instalar la librería que vamos a utilizar para crear el gráfico, la librería Pygal. Para presentar los gráficos por defecto se utiliza el formato SVG (escalable vector graphics), que incluso nos servirá para incluirlo en una página web.

Información: INSTALANDO PYGAL

En WINDOWS:

En la consola de comandos teclea lo siguiente y pulsa la tecla Enter.

pip install pygal

Espera que la instalación se complete y luego continua con el proyecto.

En un MAC:

En una ventana de terminal teclea lo siguiente y pulsa la tecla Enter.

pip3 install pygal

Espera que la instalación se complete y luego continua con el proyecto.

En LINUX, incluyendo el sistema operativo de la Rapsberry Pi.

En una ventana de terminal teclea lo siguiente y pulsa la tecla Enter.

pip3 install pygal

Espera que la instalación se complete y luego continua con el proyecto.
Para comenzar el proyecto importaremos algo de código de la librería pygal que utilizaremos para dibujar el gráfico. Empieza creando un nuevo archivo, por ejemplo main.py, y teclea el siguiente código:

from pygal import Bar

Después vamos a crear un gráfico de barras para mostrar los resultados. Añade el siguiente código y luego ejecútalo:

from pygal import Bar
import webbrowser

# Creando un gráfico
grafico = Bar(title="Medallas Olímpicas")
grafico.render_to_file('prueba.svg')
webbrowser.open('prueba.svg')

Si todo ha ido bien se abrirá tu navegador predeterminado y te mostrará la siguiente imagen:


grafico creado sin datos


Vamos a pararnos un momento para explicar el código. Aunque en teoría para renderizar el gráfico simplemente se podría usar grafico.render()  a la fecha de este post no lo he conseguido en la versión que uso de Debian o Ubuntu. No obtengo ningún error, pero tampoco se renderiza el gráfico. Así que he optado por pasar el gráfico renderizado a un archivo con grafico.renter_to_file('prueba.svg') y luego abrirlo con el navegador para visualizarlo. Para ello he importado la librería webbrowser y luego he utilizado el método open para abrir el grafico creado en el navegador. No obstante también se permite abrir directamente el grafico en el navegador usando grafico.render_in_browser(), si prefieres pero seguramente tendrás que instalar una librería dependiente (lxlm).

Otro inciso antes de seguir es que además de diagramas de barras también podemos usar muchos más tipos de gráficos como líneas, gráficos circulares, etc. Para más información consulta la documentación


Añadiendo algunos datos.


Para almacenar los datos vamos a utilizar las listas de Python. Puedes crearlas poniendo los datos separados por comas entre corchetes [ ]. 

Vamos a crear cuatro listas con datos para mostrar en nuestro gráfico.

Cada lista guardará el nombre de la nación y el número de medallas que ha ganado.

# Añadiendo datos
us = ['Estados Unidos', 2655]
gb = ['Gran Bretaña', 931]
fr = ['Francia', 772]
sp = ['España', 169]
Cuando guardas algo en una lista, cada uno de los elementos tiene un índice. Un índice es un número que nos dice la posición del elemento en la lista. Los índices comienzan en el 0 en vez de en el 1.

Puedes obtener cualquier elemento de una lista a partir de su índice. Por ejemplo, mi_lista[3] nos devolverá el cuarto elemento de esa lista, porque recuerda que el índice comienza en el cero. 

Utiliza los índices de tu lista y grafico.add() para mostrar los datos. El nombre del país que está en el primer elemento de la lista (indice 0) se utilizará como etiqueta de la categoría y la cantidad de medallas que es el segundo elemento de la lista (índice 1) determinará la altura de la barra.

# Añadiendo datos
us = ['Estados Unidos', 2655]
gb = ['Gran Bretaña', 931]
fr = ['Francia', 772]
sp = ['España', 169]

grafico.add(us[0], us[1])
grafico.add(gb[0], gb[1])
grafico.add(fr[0], fr[1])
grafico.add(sp[0], sp[1])

Ejecuta el código para ver el gráfico. 


gráfico creado con algunos datos

Si obtienes un mensaje de ERROR del tipo IndexError, es que tu código esta intentando obtener un valor desde un indice que no existe en la lista (por ejemplo us[2]). Para solucionarlo:

- comprueba cada línea grafico.add() y comprueba que solo estás utilizando los indices 0 y 1.

- comprueba las líneas donde creaste las listas. Verifica que cada lista tenga solo dos elementos, separados por una coma.

Ahora, introduciremos dos nuevos paises, añadiendo nuevas listas y luego las cargaremos en el gráfico usando grafico.add().

# Añadiendo datos
us = ['Estados Unidos', 2655]
gb = ['Gran Bretaña', 931]
fr = ['Francia', 772]
sp = ['España', 169]
ch = ['China', 634]
al = ['Alemania', 797]

grafico.add(us[0], us[1])
grafico.add(gb[0], gb[1])
grafico.add(fr[0], fr[1])
grafico.add(sp[0], sp[1])
grafico.add(ch[0], ch[1])
grafico.add(al[0], al[1])

Vuelve a ejecutar el código para actualizar el gráfico.


gráfico con más datos añadidos


Cargando los datos desde un archivo.


¡Nuestro gráfico tiene buena pinta! Pero, casi 150 naciones han competido en los juegos olimpico de verano. Para hacer un gráfico con todos, vamos a cargar sus datos desde un archivo lo que nos hará ahorrar un montón de tiempo en teclear datos.

Si vas a la carpeta del proyecto en Github encontrarás un archivo csv llamado medals.csv. Ábrelo y echa un vistazo a los datos que contiene.


imagen del archivo csv

Como ves en cada línea aparece el nombre del equipo, y el número de medallas que ha ganado hasta los últimos juegos olímpicos de verano de Tokio 2020. (Oro, plata, bronce y total de medallas)

Información: Archivos CSV
Los archivos CSV (Comma Separated Values) o archivos con valores separados
por comas, contienen datos en filas y columnas como en una tabla. Cada lí-
nea es una fila, en la que los datos (columnas) están separados por comas.

Pais, Abreviatura, Oro, Plata, Bronce, Total

United States,USA,1070,841,744,2655
Soviet Union,URS,395,319,296,1010
Great Britain,GBR,292,324,315,931
Germany,GER,239,267,291,797

Para hacer el gráfico tenemos que volcar la información de cada línea al programa que estamos haciendo, al igual que hicimos con las listas anteriormente.

Para ello vuelve al archivo donde tienes el código del programa y añade el siguiente código que nos permitirá cargar los datos del archivo en una variable, para ello utilizaremos "with open() as". Después mediante un bucle for imprimiremos los datos de cada línea.

El usar el bucle for hace que se repita el código. Así que cargaremos cientos de líneas con los equipos participantes y sus datos ¡con unas pocas líneas de código!

Añade el siguiente código.

with open('medals.csv', 'r') as lineas:
    for linea in lineas:
        print(linea)


¿Cómo se lee un archivo con Python?

Para leer un archivo de texto en Python debes abrir el archivo y leer su contenido. 

Cuando abras un archivo, utiliza los comandos with con as. Con esto te asegurarás de que una vez ejecutado el código que contiene, el archivo se cerrará automáticamente. El argumento dentro de open() en este caso 'medals.csv' es el nombre del archivo de texto que queremos abrir. 

Una vez que hemos cargado el archivo, tenemos dos opciones. La primera es volcarlo íntegramente en una variable y una segunda que es usar un bucle for para recorrer el archivo línea por línea.

La primera opción no la hemos usado en este caso pero sería algo como esto:

with open(nombre_archivo) as f:
  archivo_texto = f.read()
  # Hacer algo con el texto

La segunda opción es la que hemos usado nosotros para iterar sobre cada línea del código:

with open(nombre_archivo) as f:
  for línea_archivo in f:
    # hacer algo con la línea.

Si ejecutas ahora el código verás como cada línea tiene seis valores separados por comas.

United States,USA,1070,841,744,2655

Soviet Union,URS,395,319,296,1010

Great Britain,GBR,292,324,315,931

Germany,GER,239,267,291,797

France,FRA,231,256,285,772

People's Republic,of China,CHN,263,199,174,636

Italy,ITA,222,195,215,632

Australia,AUS,162,170,209,541

Hungary,HUN,182,156,177,515

Sweden,SWE,149,177,181,507

Japan,JPN,169,150,180,499

Cada cadena que imprime el bucle se compone de seis valores separados por comas. La función grafico.add() necesita cada una de esas piezas pero como entradas separadas. 

La función split() divide una cadena en una lista, al igual que las listas que hicimos al principio del post. Si usamos split(',') se creara una lista poniendo un nuevo elemento cada vez que encuentre una coma, es decir separa el texto en diferentes valores usando para ello la coma. 

Consejo: split() puede dividir una cadena en una lista usando como separador cualquier texto que se desee, no tiene porque ser una coma, puede ser un punto, una letra e incluso una palabra.

Modifica un poco el código para que recoja esto que hemos comentado:

with open('medals.csv', 'r') as lineas:
    for linea in lineas:
        dato = linea.split(',')
        print(dato)

Si lo ejecutas verás lo siguiente:

>>> %Run main.py
['United States', 'USA', '1070', '841', '744', '2655\n']
['Soviet Union', 'URS', '395', '319', '296', '1010\n']
['Great Britain', 'GBR', '292', '324', '315', '931\n']
['Germany', 'GER', '239', '267', '291', '797\n']
['France', 'FRA', '231', '256', '285', '772\n']
["People's Republic", 'of China', 'CHN', '263', '199', '174', '636\n']
['Italy', 'ITA', '222', '195', '215', '632\n']
['Australia', 'AUS', '162', '170', '209', '541\n']
['Hungary', 'HUN', '182', '156', '177', '515\n']
...

Como te habrás dado cuenta cada línea finaliza con "\n" al final. "\n" es normalmente invisible cuando se imprime un texto, por eso no lo has visto antes. Es un salto de línea, le dice al ordenador cuando imprime un texto, que se ha alcanzado el final de la línea.

Sin embargo como para hacer nuestro gráfico necesitamos el primer valor, el nombre del equipo, y el último, el número total de medallas, tenemos que quitar el salto de línea "\n". Para ello usaremos el método strip() que utilizado sin argumentos elimina los espacios en blanco y saltos de línea que pudiera haber tanto al principio como al final de la cadena.

El código sería de momento este:

with open('medals.csv', 'r') as lineas:
    for linea in lineas:
        linea = linea.strip()
        dato = linea.split(",")
        print(dato)

Salida:

>>> %Run main.py
['United States', 'USA', '1070', '841', '744', '2655']
['Soviet Union', 'URS', '395', '319', '296', '1010']
['Great Britain', 'GBR', '292', '324', '315', '931']
['Germany', 'GER', '239', '267', '291', '797']
['France', 'FRA', '231', '256', '285', '772']
["People's Republic", 'of China', 'CHN', '263', '199', '174', '636']
['Italy', 'ITA', '222', '195', '215', '632']
['Australia', 'AUS', '162', '170', '209', '541']

Antes de cargar los datos en el gráfico nos queda una cuestión. Todos los valores que nos pasa la cadena son de tipo string. Eso no nos importa para el nombre del equipo puesto que el primer argumento que le pasamos a grafico.add() es un texto para que lo use como etiqueta. Sin embargo para el número total de medallas necesitamos que el dato se un número entero. Podemos usar la función int() para pasar ese dato de tipo texto a número. 

Ten además en cuenta que el nombre del equipo es el primer elemento de la lista (indice 0)  y que el número total de medallas es el elemento seis (indice 5). Con esto ya podemos construir el grafico. El programa completo quedaría de la siguiente forma:

from pygal import Bar
import webbrowser

# Creando un gráfico
grafico = Bar(title="Medallas Olímpicas")

# Añadiendo datos
# us = ['Estados Unidos', 2655]
# gb = ['Gran Bretaña', 931]
# ch = ['China', 634]
# al = ['Alemania', 797]
# fr = ['Francia', 772]
# sp = ['España', 169]

# grafico.add(us[0], us[1])
# grafico.add(gb[0], gb[1])
# grafico.add(fr[0], fr[1])
# grafico.add(sp[0], sp[1])
# grafico.add(ch[0], ch[1])
# grafico.add(al[0], al[1])

with open('medals.csv', 'r') as lineas:
    for linea in lineas:
        linea = linea.strip()
        dato = linea.split(',')
        grafico.add(dato[0], int(dato[5]))
        
        
grafico.render_to_file('prueba.svg')
webbrowser.open('prueba.svg')

Ejecuta el código y mira como se crea el gráfico:


gráfico final del proyecto

¿Qué podemos hacer a mayores?

Para profundizar algo más en la forma de hacer gráficos, podemos cambiar la forma de representar los datos. 

Podríamos por ejemplo:

Crear un gráfico circular -  Para crear un gráfico circular en vez de un gráfico de barras solo tenemos que cambiar el código de importación, para usar Pie en vez de Bar. Una vez importada la creación o instancia del gráfico la haremos usando Pie.

gráfico circular




Puedes encontrar los enlaces a este proyecto en esta dirección de Github.

sábado, 25 de febrero de 2023

Web Scraping

 ¿Que es el Web Scraping?

Es usar, en este caso, Python para extraer contenido  y datos de un sitio web. Para poder usarlo es necesario tener unos conocimientos mínimos de como se estructura el lenguaje HTML. Este se basa en etiquetas. Y precisamente esas etiquetas son las que vamos a utilizar para extraer esos datos. 

Vamos a verlo con un ejemplo.  Supongamos que nos interesa extraer el valor liquidativo de un fondo de inversión. Ese valor liquidativo se publica por ejemplo en la siguiente página web.


https://www.quefondos.com/es/fondos/ficha/index.html?isin=ES0164853014


pagina web donde se recoge el valor del fondo

Para poder extraer el valor liquidativo tenemos que instalar las librerías - BeautifulSoup - y - Request -. 

La librería BeautifulSoup nos sirve para extraer información de contenido en formato HTML o XML.

La librería Requests se utiliza en Python para hacer peticiones HTTP.

Usaremos también la librería webbrowser que ya está instalada por defecto en la biblioteca estándar de Python y que usaremos al final del programa para abrir el navegador y ver la página web de donde hemos extraído los datos. Si no tienes instaladas las librerías lo puedes hacer con:

$ pip install beautifulsoup4
$ pip install requests

 El programa empezará importando las librerías y estableciendo en una variable la página web desde donde vamos a descargar los datos.

main.py

#!/usr/bin/env python3

# Si no los tenemos, instalamos BeautifulSoup y requests
# pip install beautifulsoup4
# pip install requests

from bs4 import BeautifulSoup
import requests
import webbrowser # esta instalado por defecto, nos lleva a una pagina web desde python.

URL = "https://www.quefondos.com/es/fondos/ficha/index.html?isin=ES0164853014"

Vamos a utilizar requests para obtener un objeto donde se encuentre la página que queremos con todos sus elementos.

...
URL = "https://www.quefondos.com/es/fondos/ficha/index.html?isin=ES0164853014"
page = requests.get(URL)

Si ejecutáramos este programa y el servidor nos proporcionase la página de forma correcta, obtendríamos la siguiente respuesta.

<Response [200]>

Sin embargo lo que a nosotros nos interesa es el contenido de esa página, lo que conseguiremos usando page.content, y luego extraeremos el código HTML para poder buscar el dato que queremos con el argumento "html.parser".

...
URL = "https://www.quefondos.com/es/fondos/ficha/index.html?isin=ES0164853014"
page = requests.get(URL)
# Lo que nos interesa de la pagina es el contenido por lo que usamos page.content
# y luego que lo pase en formato html
soup = BeautifulSoup(page.content, 'html.parser')

Si imprimimos esta variable "soup" obtendremos el código HTML de la página.

Código HTML de la página. "soup" 

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

<html lang="es-ES">
<head>
<title>CAIXABANK SELECCION TENDENCIAS, FI PLUS (ES0164853014) · CAIXABANK ASSET MGMT</title>
<meta content="Fondo de inversión, Ficha, Informe, CAIXABANK SELECCION TENDENCIAS, FI PLUS, ISIN, ES0164853014, Gestora, CAIXABANK ASSET MGMT, Política de inversión, Valor liquidativo, Rentabilidad, Ranking, Riesgo, Rating VDOS, Comisiones, Gráfico, Evolución, Histórico" name="keywords"/>
<meta content="Consulta el informe de CAIXABANK SELECCION TENDENCIAS, FI PLUS (ISIN:ES0164853014). Gestionado por
CAIXABANK ASSET MGMT. Categoría VDOS: MIXTO FLEXIBLE" name="description"/>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
<meta content="no-cache" http-equiv="cache-control">
<meta content="no-cache" http-equiv="pragma">
<meta content="index, follow" name="robots">
...
<div class="w100">
<h4 id="bodytitle_h3_3">Última valoración</h4>
<p>
<span class="floatleft">Valor liquidativo: </span><span class="floatright">14,677700 EUR</span>
</p>
<p>
<span class="floatleft">Patrimonio (miles de euros):</span><span class="floatright">1.338.861,29</span>
</p>
<p>
<span class="floatleft">Fecha: </span><span class="floatright">16/02/2023</span>
</p>
</div>
...


Ahora viene la parte manual del tema y lo más importante. Lo que necesitamos es encontrar dentro de que etiquetas está el valor que queremos localizar. Dentro de todo el código HTML de la página el valor liquidativo está dentro de la etiqueta <div class="w100">. Sin embargo al buscar en la página por esta etiqueta van a aparecer muchos elementos, más de los que nos gustaría, pero por lo menos ya hemos hecho una primera acotación.

Aquí os muestro el código que queremos buscar dentro de la página. Para analizar una página web una buena herramienta que se puede utilizar, es dentro del propio navegador buscar en herramientas, "herramientas para desarrolladores". Eso nos facilitará ver el código de la parte que nos interesa capturar. En nuestro caso es el siguiente:

<div class="w100">
<h4 id="bodytitle_h3_3">Última valoración</h4>
<p>
<span class="floatleft">Valor liquidativo: </span><span class="floatright">14,574500 EUR</span>
</p>
<p>
<span class="floatleft">Patrimonio (miles de euros):</span><span class="floatright">1.328.576,22</span>
</p>
<p>
<span class="floatleft">Fecha: </span><span class="floatright">19/02/2023</span>
</p>
<p>
<span class="floatleft">1 día: </span><span class="floatright"><span class="igual">0,00%</span></span>
</p>
</div>

 Para ello usaremos:

result = soup.find_all('div', class_="w100")

En la variable result se almacenará en forma de lista cada una de las etiquetas <div> que a la vez contengan la clase con el nombre "w100". Una de ellas contendrá el valor liquidativo que estamos buscando. Pero como en el código hay varias de ellas tenemos que buscar algo que diferencie la que nosotros buscamos del resto. En este caso, dentro de la etiqueta <div class="w100"> la que contiene el valor liquidativo es la que dentro tiene esta etiqueta <h4 id="body_title_h3_3>. Lo que haremos será iterar sobre la lista result buscando con i.find() aquella que contenga esta ultima etiqueta. Cuando la encontremos, usaremos el mismo método para localizar la etiqueta que contiene el valor liquidativo. El código completo de la aplicación sería el siguiente:

main.py

#!/usr/bin/env python3

# Si no los tenemos, instalamos BeautifulSoup y requests
# pip install beautifulsoup4
# pip install requests

from bs4 import BeautifulSoup
import requests
import sys
import webbrowser  # esta instalado por defecto, nos lleva a una pagina web desde python.

URL = "https://www.quefondos.com/es/fondos/ficha/index.html?isin=ES0164853014"
page = requests.get(URL)
# Lo que nos interesa de la pagina es el contenido por lo que usamos page.content
# y luego que lo pase en formato html
soup = BeautifulSoup(page.content, 'html.parser')
# Para la busqueda se pasa la etiqueta html en la que esta contenido el dato
# result = soup.find_all('span', class_="floatright")
result = soup.find_all('div', class_="w100")

valores = {}
for i in result:
    # getText() - elimina todas las etiquetas html y nos deja solo el contenido de texto.
    # find() - busca la primera conformidad con el parámetro buscado.
    # print(i.getText())
    key = i.find('h4', id="bodytitle_h3_3")
    if key is not None:
        titulo = i.find('h4', id="bodytitle_h3_3")
        valor = i.find('span', class_='floatright')
        print(titulo)
        print(valor)
        print(f"{titulo.getText()}: {valor.getText()}")
    print("------------------------------------------------------")

# Nos muestra la página web que le pasemos la url.
webbrowser.open(URL)
Finalmente, la salida que buscamos sería la siguiente:

------------------------------------------------------
<h4 id="bodytitle_h3_3">Última valoración</h4>
<span class="floatright">14,574500 EUR</span>
Última valoración: 14,574500 EUR
------------------------------------------------------
Puedes encontrar el código completo del programa en el siguiente enlace.