bytes.cat

La wiki d'FP d'informàtica

Eines de l'usuari

Eines del lloc


Barra lateral

ASIX Administració de Sistemes Informàtics i Xarxes
Tots els mòduls del cicle
MP01 Implantació de sistemes operatius
Totes les UFs del modul
MP02 Gestió de bases de dades
Totes les UFs del modul
MP03 Programació bàsica
Totes les UFs del modul
MP04 Llenguatges de marques i sistemes de gestió d'informació
Totes les UFs del modul
MP05 Fonaments de maquinari
Totes les UFs del modul
MP06 Administració de sistemes operatius
Totes les UFs del modul
MP07 Planificació i administració de xarxes
Totes les UFs del modul
MP08 Serveis de xarxa i Internet
Totes les UFs del modul
MP09 Implantació d'aplicacions web
Totes les UFs del modul
MP10 Administració de sistemes gestors de bases de dades
Totes les UFs del modul
MP11 Seguretat i alta disponibilitat
Totes les UFs del modul
MP12 Formació i orientació laboral
Totes les UFs del modul
MP13 Empresa i iniciativa emprenedora
Totes les UFs del modul
MP14 Projecte
Totes les UFs del modul
DAM Desenvolupament d’aplicacions multiplataforma
Tots els mòduls del cicle
MP01 Sistemes informàtics
Totes les UFs del modul
MP02 Bases de dades
Totes les UFs del modul
MP03 Programació bàsica
Totes les UFs del modul
MP04 Llenguatges de marques i sistemes de gestió d'informació
Totes les UFs del modul
MP05 Entorns de desenvolupament
Totes les UFs del modul
MP06 Accés a dades
Totes les UFs del modul
MP07 Desenvolupament d’interfícies
Totes les UFs del modul
MP08 Programació multimèdia i dispositius mòbils
Totes les UFs del modul
MP09 Programació de serveis i processos
Totes les UFs del modul
MP10 Sistemes de gestió empresarial
Totes les UFs del modul
MP11 Formació i orientació laboral
Totes les UFs del modul
MP12 Empresa i iniciativa emprenedora
Totes les UFs del modul
MP13 Projecte de síntesi
Totes les UFs del modul
MPDual Mòdul Dual / Projecte
DAW Desenvolupament d’aplicacions web
Tots els mòduls del cicle
MP01 Sistemes informàtics
Totes les UFs del modul
MP02 Bases de dades
Totes les UFs del modul
MP03 Programació
Totes les UFs del modul
MP04 Llenguatge de marques i sistemes de gestió d’informació
Totes les UFs del modul
MP05 Entorns de desenvolupament
Totes les UFs del modul
MP06 Desenvolupament web en entorn client
Totes les UFs del modul
MP07 Desenvolupament web en entorn servidor
Totes les UFs del modul
MP08 Desplegament d'aplicacions web
Totes les UFs del modul
MP09 Disseny d'interfícies web
Totes les UFs del modul
MP10 Formació i Orientació Laboral
Totes les UFs del modul
MP11 Empresa i iniciativa emprenedora
Totes les UFs del modul
MP12 Projecte de síntesi
Totes les UFs del modul
SMX Sistemes Microinformàtics i Xarxes
Tots els mòduls del cicle
MP01 Muntatge i manteniment d’equips
Totes les UFs del modul
MP02 Sistemes Operatius Monolloc
Totes les UFs del modul
MP03 Aplicacions ofimàtiques
Totes les UFs del modul
MP04 Sistemes operatius en xarxa
Totes les UFs del modul
MP05 Xarxes locals
Totes les UFs del modul
MP06 Seguretat informàtica
Totes les UFs del modul
MP07 Serveis de xarxa
Totes les UFs del modul
MP08 Aplicacions Web
Totes les UFs del modul
MP09 Formació i Orientació Laboral
Totes les UFs del modul
MP10 Empresa i iniciativa emprenedora
Totes les UFs del modul
MP11 Anglès
Totes les UFs del modul
MP12 Síntesi
Totes les UFs del modul
CETI Ciberseguretat en Entorns de les Tecnologies de la Informació
Tots els mòduls del cicle
CiberOT Ciberseguretat en Entorns d'Operació
Tots els mòduls del cicle
opendata_pandas

Extracció de dades obertes amb la llibreria de Python Pandas.

Objectiu

En aquesta guia veurem com obtenir dades obertes d'un servei web i guardar-les, per tal d'utilitzar-les a la nostra aplicació. Aquesta tècnica es coneix com a web scrapping.

Per aconseguir-ho, en primer lloc provarem Pandas en un programa de Python local i/o amb Jupyter Notebook.

Posteriorment, incorporarem el codi d'exemple de Pandas en una aplicació web feta amb Django. Una bona opció és utiltizar la funcionalitat del seeder que ens ofereix Django per inserir automàticament els equips.

Només s'hauria d'usar aquesta guia per a obtenir dades obertes i públiques; sinó ens podriem trobar amb problemes ètics i legals.

Per què triem Pandas ?

  • Ens proporciona una col·lecció molt útil, inexistent a Python, el DataFrame. És una taula bidimensional que podem indexar amb la/les columna/es que més ens interessin en cada moment (com a SQL).
  • Pandas detecta automàticament inconsistències de tipus i contingut en les dades. Aconseguim avantatges similars als llenguatges de tipat fort (C, Java).
  • Ens permet tractar grans volums de dades (superiors a 10.000 lines) amb molts mètodes per filtrar la informació.
  • Tenim mètodes que ens permeten importar / exportar dades de fitxers i adreçes web amb formats com CSV, JSON, SQL, HTML de manera eficient. És cert que faltaria la importació de MongoDB.
  • Si organitzem les dades del Dataframe convenientment podem crear gràfics amb llibreries com Matplotlib o Seaborn, amb menys codi que si el posessim en llistes o diccionaris.
  • També facilita la feina si usem llibreries de Machine Learning.

Instal·lació llibreria Pandas

Amb venv:

$ python3 -m venv env
$ source env/bin/activate
(env) $ pip install pandas

Amb Anaconda:

$ conda activate env
(env) $ conda install -n env -c anaconda pandas

Exemple codi local

Exemple: Volem obtenir el llistat dels equips de la lliga de futbol femení 2022-2023, que hem trobat a:

https://www.sport.es/deportes/futbol/liga-femenina-futbol/clasificacion-liga.html

En aquest cas potser acabem abans copiant i enganxant en un full de càlcul els resultats; però a la hora de la veritat no sempre funciona.

A més a més, ens interessa automatitzar les tasques.

Exemple codi local, arrencat en un fitxer IPYNB

El codi que mostro a continuació el podeu veure arrencat en directe a:

https://colab.research.google.com/drive/1vKHXmy5e9KFGv3EnRHwJpzeHTP5zp6I5#scrollTo=ROmuDJnMryJL

JuPyTeR (Julia, Python i R) Notebook és un servei que permet executar codi i llibreries Python al navegador o en qualsevol IDE, fins i tot llibreries per generar gràfics. Els seus fitxers tenen la extensió .IPYNB

Pas 1. Seleccionar la font de dades.

En el nostre cas, dins de la pàgina indicada la taula que ens interessa té un atribut HTML «summary» amb el valor «Clasificación». El mètode read_html ha de conèixer la pàgina i atributs html per identificar la taula que volem extreure.

Pas 2. Obtenir les dades amb Pandas

import pandas as pd
from io import StringIO
url: str = None
# Web scrapping: obtenció de dades externes al nostre sistema.
if url:
  print("Obtaining data")
  url: str = "https://www.sport.es/deportes/futbol/liga-femenina-futbol/clasificacion-liga.html"
  classif: pd.DataFrame = pd.read_html(url, attrs={"summary": "Clasificación"})
  # Per algún motiu que desconec la informació que m'interessa me la posa a 
  # la primera posició d'una llista.
  classificacio_df = classif[0]
else:
  print("Data already obtained")

Pas 3. Filtrar dades i obtenir resultats.

# Seleccionem la llista dels noms dels equips.
classificacio_df['EQUIPO'] = classificacio_df['EQUIPO'].astype("string")
list_aux = classificacio_df['EQUIPO'].values.tolist()
llista_equips = sum(list_aux, [])
 
def print_data():
  #print(classificacio_df)
  print(classificacio_df.dtypes)
  print(classificacio_df['EQUIPO'])
  print(llista_equips)
 
print_data()

Codi integrat a la aplicació web amb Django

Pas 4. Inserim el codi que hem comprovat a la aplicació django, dins de l'script seeder.

En primer lloc, posem el codi que hem provat abans:

     def handle(self, *args, **options):
 
        print("Obtenim dades equips, des d'un portal Open Data")
        # https://www.sport.es/deportes/futbol/liga-femenina-futbol/clasificacion-liga.html
        # Web scrapping: obtenció de dades externes al nostre sistema.
        url: str = "https://www.sport.es/deportes/futbol/liga-femenina-futbol/clasificacion-liga.html"
        classif: pd.DataFrame = pd.read_html(url, attrs={"summary": "Clasificación"})
        # Per algún motiu que desconec la informació que m'interessa me la posa a 
        # la primera posició d'una llista.
        classificacio_df = classif[0]
        # Seleccionem la llista dels noms dels equips.
        classificacio_df['EQUIPO'] = classificacio_df['EQUIPO'].astype("string")
        list_aux = classificacio_df['EQUIPO'].values.tolist()
        llista_equips = sum(list_aux, [])
        print(llista_equips)
        print("Dades equips obtingudes correctament.")
 
        #print(jugador=Jugadora.objects.get(dorsal=5))
        titol_lliga = options['titol_lliga'][0]
        ...

Després del codi que crea la lliga si no existeix, comento el codi de creació d'equips del Faker i poso aquest, que crea i guarda els equips pel seu nom:

        for nom_equip in llista_equips:
            equip = Equip(nom=nom_equip)
             #print(equip)
            equip.save()
            lliga.equips.add(equip)
 
        # Seleccionarem algunes jugadores.
            print("Creem jugadores de l'equip "+nom_equip)
            ...
Codi complet del seeder i el model
Aquí podreu veure un exemple del codi de l'script i també el model.py que s'utilitza.
/management/commands/crea_lliga.py
from django.db import models
 
# Observació: Django genera per defecte camps id autonumèrics
# com a clau primària. Hi ha formes de canviar aquest 
# comportament però per ara ens sembla OK.
 
# Hauria de posar una relació ManyToMany per tal que un mateix equip
# pugui compatir en diverses lligues en un any (Champions)
# equips = models.ManyToManyField(Equip)
class Equip(models.Model):
    nom = models.CharField(max_length=100)
    ciutat = models.CharField(max_length=100,null=True)
    estadi = models.CharField(max_length=100,null=True)
    #lliga = models.ForeignKey(Lliga, on_delete=models.CASCADE)
    def __str__(self):
        return self.nom
 
 
class Lliga(models.Model):
    nom_temporada = models.CharField(max_length=100)
    equips = models.ManyToManyField(Equip)
    def __str__(self):
        return self.nom_temporada
    class Meta:
        verbose_name_plural = 'Lligues'
 
 
class Jugadora(models.Model):
    nom = models.CharField(max_length=100)
    cognom = models.CharField(max_length=100)
    dorsal = models.IntegerField()
    equip = models.ForeignKey(Equip, on_delete=models.CASCADE)
 
    def __str__(self):
        return f'{self.dorsal} - {self.nom}'
    class Meta:
        verbose_name_plural = 'Jugadores'
 
 
class Partit(models.Model):
    class Meta:
        unique_together = ["local","visitant","lliga"]
    local = models.ForeignKey(Equip,on_delete=models.CASCADE,
                    related_name="partits_local",null=True,blank=True)
    visitant = models.ForeignKey(Equip,on_delete=models.CASCADE,
                    related_name="partits_visitant",null=True,blank=True)
    lliga = models.ForeignKey(Lliga,on_delete=models.CASCADE)
    detalls = models.TextField(null=True,blank=True)
    inici = models.DateTimeField(null=True,blank=True)
    def __str__(self):
        return "{} - {}".format(self.local,self.visitant)
    def gols_local(self):
        return self.event_set.filter(
            tipus=Event.EventType.GOL,equip=self.local).count()
    def gols_visitant(self):
        return self.event_set.filter(
            tipus=Event.EventType.GOL,equip=self.visitant).count()
 
 
class Event(models.Model):
    # Solució subòptima i provisional.
    # tipus_event = models.CharField(max_length=100)
    # el tipus d'event l'implementem amb algo tipus "enum"
    class EventType(models.TextChoices):
        GOL = "GOL"
        AUTOGOL = "AUTOGOL"
        FALTA = "FALTA"
        PENALTY = "PENALTY"
        MANS = "MANS"
        CESSIO = "CESSIO"
        FORA_DE_JOC = "FORA_DE_JOC"
        ASSISTENCIA = "ASSISTENCIA"
        TARGETA_GROGA = "TARGETA_GROGA"
        TARGETA_VERMELLA = "TARGETA_VERMELLA"
    partit = models.ForeignKey(Partit,on_delete=models.CASCADE)
    temps = models.TimeField()
    tipus = models.CharField(max_length=30,choices=EventType.choices)
    jugador = models.ForeignKey(Jugadora,null=True,
                    on_delete=models.SET_NULL,
                    related_name="events_fets")
    equip = models.ForeignKey(Equip,null=True,
                    on_delete=models.SET_NULL)
    # per les faltes
    jugador2 = models.ForeignKey(Jugadora,null=True,blank=True,
                    on_delete=models.SET_NULL,
                    related_name="events_rebuts")
    detalls = models.TextField(null=True,blank=True)
    def __str__(self):
        return f'{self.EventType} - {self.jugador}'
models.py
from django.db import models
 
# Observació: Django genera per defecte camps id autonumèrics
# com a clau primària. Hi ha formes de canviar aquest 
# comportament però per ara ens sembla OK.
 
# Hauria de posar una relació ManyToMany per tal que un mateix equip
# pugui compatir en diverses lligues en un any (Champions)
# equips = models.ManyToManyField(Equip)
class Equip(models.Model):
    nom = models.CharField(max_length=100)
    ciutat = models.CharField(max_length=100,null=True)
    estadi = models.CharField(max_length=100,null=True)
    #lliga = models.ForeignKey(Lliga, on_delete=models.CASCADE)
    def __str__(self):
        return self.nom
 
 
class Lliga(models.Model):
    nom_temporada = models.CharField(max_length=100)
    equips = models.ManyToManyField(Equip)
    def __str__(self):
        return self.nom_temporada
    class Meta:
        verbose_name_plural = 'Lligues'
 
 
class Jugadora(models.Model):
    nom = models.CharField(max_length=100)
    cognom = models.CharField(max_length=100)
    dorsal = models.IntegerField()
    equip = models.ForeignKey(Equip, on_delete=models.CASCADE)
 
    def __str__(self):
        return f'{self.dorsal} - {self.nom}'
    class Meta:
        verbose_name_plural = 'Jugadores'
 
 
class Partit(models.Model):
    class Meta:
        unique_together = ["local","visitant","lliga"]
    local = models.ForeignKey(Equip,on_delete=models.CASCADE,
                    related_name="partits_local",null=True,blank=True)
    visitant = models.ForeignKey(Equip,on_delete=models.CASCADE,
                    related_name="partits_visitant",null=True,blank=True)
    lliga = models.ForeignKey(Lliga,on_delete=models.CASCADE)
    detalls = models.TextField(null=True,blank=True)
    inici = models.DateTimeField(null=True,blank=True)
    def __str__(self):
        return "{} - {}".format(self.local,self.visitant)
    def gols_local(self):
        return self.event_set.filter(
            tipus=Event.EventType.GOL,equip=self.local).count()
    def gols_visitant(self):
        return self.event_set.filter(
            tipus=Event.EventType.GOL,equip=self.visitant).count()
 
 
class Event(models.Model):
    # Solució subòptima i provisional.
    # tipus_event = models.CharField(max_length=100)
    # el tipus d'event l'implementem amb algo tipus "enum"
    class EventType(models.TextChoices):
        GOL = "GOL"
        AUTOGOL = "AUTOGOL"
        FALTA = "FALTA"
        PENALTY = "PENALTY"
        MANS = "MANS"
        CESSIO = "CESSIO"
        FORA_DE_JOC = "FORA_DE_JOC"
        ASSISTENCIA = "ASSISTENCIA"
        TARGETA_GROGA = "TARGETA_GROGA"
        TARGETA_VERMELLA = "TARGETA_VERMELLA"
    partit = models.ForeignKey(Partit,on_delete=models.CASCADE)
    temps = models.TimeField()
    tipus = models.CharField(max_length=30,choices=EventType.choices)
    jugador = models.ForeignKey(Jugadora,null=True,
                    on_delete=models.SET_NULL,
                    related_name="events_fets")
    equip = models.ForeignKey(Equip,null=True,
                    on_delete=models.SET_NULL)
    # per les faltes
    jugador2 = models.ForeignKey(Jugadora,null=True,blank=True,
                    on_delete=models.SET_NULL,
                    related_name="events_rebuts")
    detalls = models.TextField(null=True,blank=True)
    def __str__(self):
        return f'{self.EventType} - {self.jugador}'

Resultat final, quan arrenquem la aplicació:

Si heu canviat el model, primer executeu:

(env) nomusuari@linux:~/lligafemprj$ python manage.py makemigrations lliga
(env) nomusuari@linux:~/lligafemprj$ python manage.py migrate

Per omplir les dades de l'script seeder:

(env) nomusuari@linux:~/lligafemprj$ ./manage.py crea_lliga <nom_lliga>
(env) nomusuari@linux:~/lligafemprj$ ./manage.py runserver
(env) nomusuari@linux:~/lligafemprj$ firefox http://127.0.0.1:8080/admin

Si anem a veure la taula de partits podem comprovar que s'han generat correctament, juntament amb els gols dels locals i visitants.

Conclusions

Publicar i fer ús de dades públiques d'interès general a nivell de cultura, salut, economia, … és un encert si en fem un bon ús i estem ben formats amb llenguatges com Python que ens faciliten aquesta tasca.

opendata_pandas.txt · Darrera modificació: 2023/07/04 23:23 per miquel_angel_amoros