Aprende a programar como el presidente
Tags: python, pandas, beginners, webscraping
Published: 2024-03-28 23:58:09

¿Queres aprender a programar como un presidente lo hace? Dejame decirte que es tu día de suerte.

![Wojak in PC](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j9ajlqavnxksuugw28f2.png)

Aclaro desde ya que puede que haya un poco de título en el clickbait, pero algo de **programación** y **presidentes** hay.

Si como yo pasas mucho tiempo en Twitter y consumís **¿contenido político?** 🤔 probablemente hayas visto este tuit (o este X?) del presidente en enero de este año:

{% embed https://twitter.com/JMilei/status/1748047057024176373 %}

Y la verdad es que quedé un tanto sorprendido de ver que el presidente de Argentina tuiteara una captura de lo que pareciera ser Jupyter Notebook o Google Colab.

Para quienes no sepan a qué me refiero cuando hablo de _notebooks_, piénsenlo como una libreta o un cuaderno, donde en lugar de renglones tenemos _celdas_ las cuales pueden contener texto o líneas de código (puede ser python, R o Julia) que pueden ser ejecutadas y hacer cositas.

La idea de este post es intentar llegar al mismo resultado del tuit utilizando python y explicar un poco el proceso (si, ya sé, escribo esto con un par de meses de delay, pero todavía tenes tiempo de aprender a programar y tuitear como lo haría el presidente antes de las elecciones del 2027 y ya le sacas ventaja).

Antes de empezar, pequeño disclaimer, quiero aclarar que este post está pensado más bien para personas que no saben programar o están recién empezando, con la idea de que, si les despierta curiosidad, luego puedan profundizar ya sea en el lenguaje o las áreas que se van a mencionar o tratar de manera general en los siguientes párrafos. Como digo esto, también hay otras alternativas para llegar a los mismos resultados, ya sea utilizando alguna API o librería para extraer los datos directamente desde Youtube, alguna otra herramienta de web scraping o incluso maneras distintas de codificarlo. Ahora si, arrancamos y de base tenemos dos preguntas:

1. ¿Qué datos necesitamos?
Por lo que vemos a simple vista, se están analizando la cantidad de visitas y likes por video, aunque tenemos otros datos que podrían llegar a ser de interés como la duración del mismo.
2. ¿De dónde sacamos los datos?
Lo primero que tenemos que hacer es identificar **de dónde salen** los datos que se muestran en el tuit, en este caso lo tenemos fácil ya que refieren al evento del World Economic Forum en Davos de este año, más precisamente a los videos subidos a Youtube de este evento. Respecto a esto, hay una buena noticia y es que los videos presentados, y los que se muestran en el tuit de Milei, están todos agrupados en la siguiente playlist [Davos 2024: World Leaders & Special Addresses | World Economic Forum | #wef24](https://www.youtube.com/playlist?list=PL7m903CwFUgkUeRB0cGc8WSNNEZ4_zAqe)

Habiéndonos hecho este par de preguntas ya podemos empezar a escribir código. En mi caso, voy a usar python como lenguaje de programación y la siguientes librerías para extraer y manipular los datos:

- **re:** para utilizar expresiones regulares y extraer patrones de interés en cadenas de texto (por ejemplo, los likes aparecen como "72 k vistas", yo quiero quedarme con el número 72 y en base a eso calcular cuántos likes tiene el video, o sea 72 * 1000, para quedarme con el 72 utilizo expresiones regulares)
- **json:** para manipular contenido en ese formato extraído directamente de la página de Youtube
- **pandas:** una de las librerías de python más comunes para análisis de datos. La voy a utilizar para trabajar con dataframes (estructuras de datos de 2 dimensiones, similares a una tabla de SQL o a una hoja de Excel/Google Sheet), lo cual me va a permitir manipular los datos ya sea para aplicarles formato, cambiarles el tipo o directamente crear nuevos
- **selenium:** librería utilizada para automatización de testing web, en este caso la voy a utilizar para hacer una suerte de web scraping (forma linda de decir que voy a sacar datos de una página web en este caso Youtube) y extraer los datos de manera automatizada
- **matplotlib**: librería para generar gráficos

En el caso de re y json vienen por defecto cuando instalamos python en nuestra PC, en cambio pandas y selenium tenemos que instalarlas por nuestra cuenta. En CMD, Powershell, Bash, etc, ejecutamos los siguientes comandos:
```
pip install pandas
pip install selenium
pip install matplotlib
```
Si estamos dentro de una notebook de Jupyter o Google Colab hay que agregar un "%" antes de "pip". Una vez instaladas estas librerías, ya se puede empezar a escribir código.

Lo primero que hay que hacer es importar las librerías que se van a utilizar y aprovechamos para inicializar el webdriver de Selenium (que en pocas palabras es el robotito que va a hacer el trabajo de extraer la información que le indiquemos):
```
import re
import json
import pandas as pd
import matplotlib.pyplot as plt
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


chromeOptions = webdriver.ChromeOptions()
chromeOptions.add_argument("--headless")

driver = webdriver.Chrome(options=chromeOptions)
```
Defino 2 funciones (bloques de código que realizan una tarea en específico) que me van a servir para manipular datos que vienen de manera "sucia" o incompatible con el formato que buscamos:
```
def extract_duration(json_data):
    duration_iso = json_data["duration"]
    duration_seconds = 0

    if duration_iso.startswith("PT"):
        duration_iso = duration_iso[2:]
        hours_index = duration_iso.find("H")
        minutes_index = duration_iso.find("M")
        seconds_index = duration_iso.find("S")

        if hours_index != -1:
            duration_seconds += int(duration_iso[:hours_index]) * 3600
            duration_iso = duration_iso[hours_index + 1 :]

        if minutes_index != -1:
            duration_seconds += int(duration_iso[:minutes_index]) * 60
            duration_iso = duration_iso[minutes_index + 1 :]

        if seconds_index != -1:
            duration_seconds += int(duration_iso[:seconds_index])
    return duration_seconds

def convert_likes(likes_str):
    likes_str = likes_str.upper()
    if " K" in likes_str:
        like_count = float((likes_str).split(" K")[0]) * 1000
    else:
        like_count = float(likes_str)
    return like_count
```
_La primera función se la pedí a ChatGPT y no hice ninguna iteración, probablemente haya una mejor manera de resolver el problema de incompatibilidad._

Ahora si, podemos definir el "recorrido" que va a hacer el webdriver, tomando como punto de partida la URL de la playlist
![Playlist URL](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fjw9dfel8t6wvr8hoeg8.png)
```
PLAYLIST_URL = (
    "https://www.youtube.com/playlist?list=PL7m903CwFUgkUeRB0cGc8WSNNEZ4_zAqe"
)

driver.get(PLAYLIST_URL)

""" 
Con las siguientes líneas de código, lo que hacemos es buscar mediante xpath 
todos los elementos que coincidan con id="video-title", y de los mismos extraemos
el título del video y el link al mismo. 
Estos datos los almacenamos en una lista de diccionarios (estructura de datos de tipo clave-valor).
"""
videos_elements = driver.find_elements("xpath", '//*[@id="video-title"]')
videos_tuple = [
    (e.get_attribute("title"), e.get_attribute("href")) for e in videos_elements
]
videos = [
    {"title": e.get_attribute("title"), "link": e.get_attribute("href")}
    for e in videos_elements
]
```
Xpath podemos decir que es un lenguaje que nos permite buscar dentro del html de la página web. En las siguientes capturas muestro una manera sencilla de obtener el xpath de un elemento de la web

![Cómo obtener xpath](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f6r2jw0jhlyzfrry5r90.png)

En caso de que quieran ahondar en web scraping o este tipo de automatizaciones recomiendo que busquen más información al respecto.

Ahora lo que vamos a hacer es recorrer esta lista e ingresar de manera individual a cada video mediante su link para extraer sus visitas y likes
```
df_rows = []
for v in videos:
    driver.get(v.get("link"))
    WebDriverWait(driver, 15).until(
        EC.presence_of_element_located((By.XPATH, '//*[@id="video-title"]'))
    )
    like_button = driver.find_element(
        "xpath",
        '//*[@id="top-level-buttons-computed"]/segmented-like-dislike-button-view-model/yt-smartimation/div/div/like-button-view-model/toggle-button-view-model/button-view-model/button/div[2]',
    )
    views = driver.find_element("xpath", '//*[@id="info"]/span[1]')

    script_element = driver.find_element(
        "xpath", '(//script[@type="application/ld+json"])[2]'
    )
    script_text = script_element.get_attribute("textContent")
    data = json.loads(script_text)

    resource_id = re.search(r"v=([^\&]+)", v.get("link")).group(1)
    like_count = convert_likes(like_button.text)
    duration = extract_duration(data)
    view_count = float(data["interactionCount"])
    video_title = v.get("title")
    df_rows.append([resource_id, view_count, like_count, duration, video_title])

driver.quit()
```
Una vez que tenemos esta información almacenada en una lista de listas la almacenamos en un dataframe de Pandas
```
df_youtube_statistics = pd.DataFrame(
    data=df_rows, 
    columns=["resource_id", "view_count", "like_count", "duration", "title"]
)
```
Y por último realizamos el ordenamiento según la cantidad de visitas al igual que el presidente
```
df_youtube_statistics.sort_values(
    by="view_count", ascending=False, ignore_index=True, inplace=True
)
```
Con esto tendremos los datos de la siguiente manera

![Pandas dataframe output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7e5yj6obh7mrj3fm60ib.png)
Ahora que estamos cancheros podemos mejorar (?) el análisis y agregar alguna columna calculada, como por ejemplo la tasa de likes
```
df_youtube_statistics["likes_ratio"] = round((df_youtube_statistics["like_count"] / df_youtube_statistics["view_count"]) * 100, 2)
df_youtube_statistics["title"] = df_youtube_statistics["title"].apply(lambda x: x.split("|")[0])
df_youtube_statistics.sort_values(by="likes_ratio", ascending=False, inplace=True)
```
![Output con columnas calculadas](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eqvolgodmmec4i5crre8.png)
O incluso agregar algunos gráficos para ver los resultados de manera más amigable
```
df_youtube_sorted_by_views = df_youtube_statistics.sort_values(by='view_count', ascending=False)
df_youtube_sorted_by_likes = df_youtube_statistics.sort_values(by='like_count', ascending=False)
df_youtube_sorted_by_ratio = df_youtube_statistics.sort_values(by='likes_ratio', ascending=False)

fig, axs = plt.subplots(1, 3, figsize=(18, 5))  # 1 fila, 3 columnas

df_youtube_sorted_by_views.plot.bar(x="title", y="view_count", ax=axs[0], color='skyblue')
axs[0].set_title('View Count')

df_youtube_sorted_by_likes.plot.bar(x="title", y="like_count", ax=axs[1], color='lightgreen')
axs[1].set_title('Like Count')

df_youtube_sorted_by_ratio.plot.bar(x="title", y="likes_ratio", ax=axs[2], color='orange')
axs[2].set_title('Like Ratio')

plt.tight_layout()

plt.savefig('youtube_statistics.png')

plt.show()
```
![Campeones de la calle online](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ejeal4zu75znihxnshb.png)

Viendo los resultados de los gráficos podemos concluir que la calle online es argentina papá

![Bandera Argentina](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wxvsedv33zsmrcf6udon.png)

Ahora hablando en serio, ojalá este post haya servido como una suerte de "guía" para experimentar con python y en el mejor de los casos haya despertado curiosidad para seguir aprendiendo y profundizando en las herramientas que fuimos viendo. El código se encuentra en un repositorio de Github para que puedan descargarlo y hacer las pruebas ustedes mismos

{% embed https://github.com/akalautaro/datos-con-python %}

En mi perfil de Github pueden encontrar repositorios con proyectos personales y algunos challenges de data engineering principalmente

{% embed https://github.com/akalautaro %}

Ojalá se copen haciendo cosas con python 🤠