Clase 9: Usos de APIs

Web scraping y acceso a datos desde la web


Cristián Ayala
Director DESUC

27 de junio de 2024

Motivación

  • Utilizar puertas provistas por servicios web para acceder a su información.

    • Wikipedia, Spotify, Mastodon.
  • Revisar paquetes ya desarrollados para acceder a servicios populares.

Paquetes para la clase de hoy

Grupo de paquetes interrelacionados:

  • WikipediR: Empaqueta la API de MediaWiki para bajar datos de Wikipedia.

  • spotifyr: Empaqueta la API de Spotify para bajar datos.

  • rtoot: Permite interactuar con la API de Mastodon.

Wikipedia

El software sobre el que está montada Wikipedia —MediaWiki— cuenta con una API para acceder al contenido almacenado en sus bases de datos.

Veamos la documentación y como crear llamados en la sandbox disponible

Wikipedia: API

Revisemos la API de Wikipedia.

  • Revisión de acciones disponibles.
    • Search: Buscar páginas.

    • Parse: Obtener contenido de una página.

    • Query: Obtener información de páginas.

Captura de página sobre web scraping en español.

Wikipedia: API buscar API: Search 1

Construcción de GET para la acción Search.

req_wiki_api <- request('https://es.wikipedia.org') |> 
  req_url_path('w/api.php') |> 
  req_headers('Accept-Encoding' = 'gzip')

req_wiki_search <- req_wiki_api |>
  req_url_query(!!!list(action = 'query',
                        list = 'search',
                        srsearch = 'web scraping',
                        srlimit = 5,
                        format = 'json'))

resp_wiki_search <- req_perform(req_wiki_search)

# Leo el json que está dentro de la respuesta a la petición.
df <- resp_wiki_search |> 
  resp_body_json(simplifyVector = T)

df |> str()
List of 3
 $ batchcomplete: chr ""
 $ continue     :List of 2
  ..$ sroffset: int 5
  ..$ continue: chr "-||"
 $ query        :List of 2
  ..$ searchinfo:List of 1
  .. ..$ totalhits: int 25
  ..$ search    :'data.frame':  5 obs. of  7 variables:
  .. ..$ ns       : int [1:5] 0 0 0 0 0
  .. ..$ title    : chr [1:5] "Web scraping" "Screen scraping" "Scrapy" "HtmlUnit" ...
  .. ..$ pageid   : int [1:5] 5544299 1194524 9214341 4481985 6269318
  .. ..$ size     : int [1:5] 12083 1116 2195 3652 2420
  .. ..$ wordcount: int [1:5] 1553 153 142 309 162
  .. ..$ snippet  : chr [1:5] "<span class=\"searchmatch\">Web</span> <span class=\"searchmatch\">scraping</span> o raspado <span class=\"sear"| __truncated__ "casos, es al contrario, como en los sistemas de captcha). La traducción aproximada de screen <span class=\"sear"| __truncated__ "por Scrapinghub Ltd., una empresa que ofrece productos y servicios de <span class=\"searchmatch\">web</span>-<s"| __truncated__ "páginas <span class=\"searchmatch\">web</span>, pero a veces se puede utilizar para <span class=\"searchmatch\""| __truncated__ ...
  .. ..$ timestamp: chr [1:5] "2024-06-12T18:29:53Z" "2019-07-13T13:59:58Z" "2021-10-08T07:29:04Z" "2024-03-08T03:46:36Z" ...

Wikipedia: API buscar API: Search 2

Transformación de los resultados a una tibble. La información que interesa está en query ↘️ search.

df$query$search |> as_tibble()
# A tibble: 5 × 7
     ns title            pageid  size wordcount snippet                timestamp
  <int> <chr>             <int> <int>     <int> <chr>                  <chr>    
1     0 Web scraping    5544299 12083      1553 "<span class=\"search… 2024-06-…
2     0 Screen scraping 1194524  1116       153 "casos, es al contrar… 2019-07-…
3     0 Scrapy          9214341  2195       142 "por Scrapinghub Ltd.… 2021-10-…
4     0 HtmlUnit        4481985  3652       309 "páginas <span class=… 2024-03-…
5     0 Beautiful Soup  6269318  2420       162 "Por lo tanto, esta b… 2024-01-…

Wikipedia: API contenido API: Parse 1

Obtención del contenido de página. GET para la acción Parse.

req_wiki_parse <- req_wiki_api |>
  req_url_query(!!!list(action = 'parse',
                        page = 'Web scraping', # contenido de título de página
                        prop = 'text', # html de retorno
                        format = 'json'))

resp_wiki_parse <- req_perform(req_wiki_parse)

df_wiki_parse <- resp_wiki_parse |> 
  resp_body_json(simplifyVector = T)

df_wiki_parse |> str(3)
List of 1
 $ parse:List of 3
  ..$ title : chr "Web scraping"
  ..$ pageid: int 5544299
  ..$ text  :List of 1
  .. ..$ *: chr "<div class=\"mw-content-ltr mw-parser-output\" lang=\"es\" dir=\"ltr\"><p><i><b>Web scraping</b></i> o <b>raspa"| __truncated__

Wikipedia: API contenido API: Parse 2

El texto de contenido en parse ↘️ text ↘️ *.

df_wiki_parse$parse$text$`*` |> 
  read_html() |> html_text() |> 
  str_trunc(width = 1e3) |> cat()
Web scraping o raspado web es una técnica utilizada mediante programas de software para extraer información de sitios web.[1]​ Usualmente, estos programas simulan la navegación de un humano en la World Wide Web ya sea utilizando el protocolo HTTP manualmente, o incrustando un navegador en una aplicación.
El web scraping está muy relacionado con la indexación de la web, la cual indexa la información de la web utilizando un robot y es una técnica universal adoptada por la mayoría de los motores de búsqueda. Sin embargo, el web scraping se enfoca más en la transformación de datos sin estructura en la web (como el formato HTML) en datos estructurados que pueden ser almacenados y analizados en una base de datos central, en una hoja de cálculo o en alguna otra fuente de almacenamiento. Alguno de los usos del web scraping son la comparación de precios en tiendas, la monitorización de datos relacionados con el clima de cierta región, la detección de cambios en sitios webs y la integración d...

Wikipedia: API información API: Query 1

Información de varias páginas con Query.

req_wiki_query <- req_wiki_api |>
  req_url_query(!!!list(action = 'query',
                         titles = 'Web scraping|Python', # Varios títulos a la ves.
                         prop = 'info|categories|iwlinks', # Información a obtener.
                         format = 'json'))

resp_wiki_query <- req_perform(req_wiki_query)

df_wiki_query <- resp_wiki_query |> 
  resp_body_json(simplifyVector = T)

df_wiki_query |> str(3)
List of 2
 $ continue:List of 2
  ..$ clcontinue: chr "2330|Wikipedia:Artículos_con_identificadores_BNF"
  ..$ continue  : chr "||info|iwlinks"
 $ query   :List of 1
  ..$ pages:List of 2
  .. ..$ 2330   :List of 12
  .. ..$ 5544299:List of 11

Wikipedia: API información API: Query 2

df_wiki_query$query$pages |> 
  enframe() |> 
  unnest_wider(value)
# A tibble: 2 × 13
  name     pageid    ns title     contentmodel pagelanguage pagelanguagehtmlcode
  <chr>     <int> <int> <chr>     <chr>        <chr>        <chr>               
1 2330       2330     0 Python    wikitext     es           es                  
2 5544299 5544299     0 Web scra… wikitext     es           es                  
# ℹ 6 more variables: pagelanguagedir <chr>, touched <chr>, lastrevid <int>,
#   length <int>, categories <list>, iwlinks <list>

Wikipedia: usando WikipediR 1

WikipediR: lo mismo, pero más fácil.

  • page_info: información sobre una página específica.

  • page_content: contenido de una página específica.

  • page_links: links disponibles en una página específica.

library(WikipediR)

info_wiki <- page_info(
  language = 'es', 
  project = 'wikipedia', 
  page = 'Web_scraping|Python') 
str(info_wiki, 3)
List of 2
 $ batchcomplete: chr ""
 $ query        :List of 2
  ..$ normalized:List of 1
  .. ..$ :List of 2
  ..$ pages     :List of 2
  .. ..$ 2330   :List of 17
  .. ..$ 5544299:List of 17
 - attr(*, "class")= chr "pageinfo"

Wikipedia: usando WikipediR 2

Resultado de la búsqueda.

info_wiki$query$pages |> 
  enframe() |> 
  unnest_wider(value)
# A tibble: 2 × 18
  name     pageid    ns title     contentmodel pagelanguage pagelanguagehtmlcode
  <chr>     <int> <int> <chr>     <chr>        <chr>        <chr>               
1 2330       2330     0 Python    wikitext     es           es                  
2 5544299 5544299     0 Web scra… wikitext     es           es                  
# ℹ 11 more variables: pagelanguagedir <chr>, touched <chr>, lastrevid <int>,
#   length <int>, protection <lgl>, restrictiontypes <list>, talkid <int>,
#   fullurl <chr>, editurl <chr>, canonicalurl <chr>, displaytitle <chr>

Wikipedia: usando WikipediR 3

Captura de página sobre web scraping en español.

cont_wiki <-  page_content(language = 'es', 
                           project = 'wikipedia', 
                           page_name = 'Web_scraping')

str(cont_wiki) # Una lista parse con 4 elementos dentro
List of 1
 $ parse:List of 4
  ..$ title : chr "Web scraping"
  ..$ pageid: int 5544299
  ..$ revid : int 160710540
  ..$ text  :List of 1
  .. ..$ *: chr "<div class=\"mw-content-ltr mw-parser-output\" lang=\"es\" dir=\"ltr\"><p><i><b>Web scraping</b></i> o <b>raspa"| __truncated__
 - attr(*, "class")= chr "pcontent"

Wikipedia: usando WikipediR 4

Texto sobre web scraping.

cont_wiki$parse$text$`*` |> # Texto del requerimiento
  read_html() |> html_text() |> 
  str_trunc(width = 1e3) |> cat()
Web scraping o raspado web es una técnica utilizada mediante programas de software para extraer información de sitios web.[1]​ Usualmente, estos programas simulan la navegación de un humano en la World Wide Web ya sea utilizando el protocolo HTTP manualmente, o incrustando un navegador en una aplicación.
El web scraping está muy relacionado con la indexación de la web, la cual indexa la información de la web utilizando un robot y es una técnica universal adoptada por la mayoría de los motores de búsqueda. Sin embargo, el web scraping se enfoca más en la transformación de datos sin estructura en la web (como el formato HTML) en datos estructurados que pueden ser almacenados y analizados en una base de datos central, en una hoja de cálculo o en alguna otra fuente de almacenamiento. Alguno de los usos del web scraping son la comparación de precios en tiendas, la monitorización de datos relacionados con el clima de cierta región, la detección de cambios en sitios webs y la integración d...

Spotify

Uno de los mayores proveedores de servicio de música del mundo. Tiene disponible una API para la integración de su servicio con otras aplicaciones. Podemos usarla para obtener información.

Spotify dashboard

Spotify: API 1

Revisemos la API de Spotify. Acciones de interés.

Spotify: API 2

Búsqueda de género metal usando la API directamente.

Pedí el token de acceso usando spotifyr.

req_spotify_api <- request('https://api.spotify.com') |> 
  req_url_path('v1') |> 
  req_headers(Authorization = str_glue("Bearer {spotifyr::get_spotify_access_token()}"),
              Accept = 'application/json"',
              'Accept-Encoding' = 'gzip')

req_spotify_met <- req_spotify_api |> 
  req_url_path_append('search') |> 
  req_url_query(q = 'genre:metal', 
                market = 'CL', 
                type = 'artist', 
                limit = 8)

resp_spotify_met <- req_spotify_met |> req_perform()

df_spotify_met <- resp_spotify_met |> 
  resp_body_json(simplifyVector = T)

df_spotify_met$artists$items |> 
  as_tibble() |> unpack(followers, names_repair = 'minimal') |>
  select(id, name, popularity, total)
# A tibble: 8 × 4
  id                     name                  popularity    total
  <chr>                  <chr>                      <int>    <int>
1 6XyY86QOPPrYVGvF9ch6wz Linkin Park                   84 25596223
2 0L8ExT028jH3ddEcZwqJJ5 Red Hot Chili Peppers         82 20841924
3 2ye2Wgw4gimLv2eAKyk1NB Metallica                     81 27371357
4 5eAWCfyUhZtHHtBdNk56l1 System Of A Down              79 10804883
5 3qm84nBOXUEQ2vnTfUTTFC Guns N' Roses                 78 30909210
6 58lV9VcRSjABbAbfWS6skp Bon Jovi                      78 13365004
7 6Ghvu1VvMGScGpOUJBAHNH Deftones                      78  5562651
8 6wWVKhxIU2cEi0K81v7HvP Rammstein                     79  9797108

Spotify: spotifyr 1

Envuelve los llamados a la API de Spotify en funciones de R. Puede verse su referencia

Búsqueda análoga a la anterior:

library(spotifyr)

df_met <- get_genre_artists('metal', limit = 8)
df_met |> 
  select(id, name, popularity, followers.total)
# A tibble: 8 × 4
  id                     name                  popularity followers.total
  <chr>                  <chr>                      <int>           <int>
1 6XyY86QOPPrYVGvF9ch6wz Linkin Park                   84        25596223
2 0L8ExT028jH3ddEcZwqJJ5 Red Hot Chili Peppers         82        20841924
3 6Ghvu1VvMGScGpOUJBAHNH Deftones                      78         5562651
4 2ye2Wgw4gimLv2eAKyk1NB Metallica                     81        27371357
5 1Ffb6ejR6Fe5IamqA5oRUF Bring Me The Horizon          81         5636152
6 5eAWCfyUhZtHHtBdNk56l1 System Of A Down              79        10804883
7 6FBDaR13swtiWwGhX1WQsP blink-182                     77         8259069
8 6deZN1bslXzeGvOLaLMOIF Nickelback                    77         6813256

Spotify: spotifyr 2

Cambiamos los criterios de búsqueda para encontrar podcasts.

df_pod <- search_spotify(q = 'podcast', type = 'show', market = 'CL', limit = 10)

df_pod |> 
  select(id, name, total_episodes, description)
# A tibble: 10 × 4
   id                     name                        total_episodes description
   <chr>                  <chr>                                <int> <chr>      
 1 1KPSWSt5L85YeoD936e4Ui ¿Cómo Están Los Weones?                 32 ¿Cómo Está…
 2 4OtyxH4rxMjxXwk0cUKoQ8 Primerizas El Podcast                   58 Quieres ap…
 3 0vh85smSITYmlWbtcABtzC Expertas en Nada                       100 Una charla…
 4 5T6nge86sgwXW0TnpNoLTY Tu Desarrollo Personal                 180 Desarrollo…
 5 2xV7Vx7NQgAC8vMt2vZNnE El Podcast de Marian Rojas…             43 Marian Roj…
 6 6uiXpyl749yOE2vs8sCrdW Paranormal                             165 Canal de i…
 7 129qqeXnPDkEHVfQCmxIQa Cariño Podcast                          59 Paula y Ro…
 8 6U02wirujU8zNsDQcbPJhS Tomás Va A Morir                       224 3 Amigos q…
 9 2EVTmMTfBLV9U8WOv8YPzL PSICOLOGÍA DEL ÉXITO                    29 Un espacio…
10 5ycEEouNP3fB4emhae3tGE Decretum Podcast                       200 Bienvenido…

Spotify: spotifyr listas

Exploremos las características de canciones gracias a la lista top 50 canciones en Chile.

df_top <- get_playlist('37i9dQZEVXbL0GavIqMTeb') # ID de la lista.
df_top_track <- df_top$tracks$items |> as_tibble()

suppressMessages(
  # Seleccionar y limpiar solo alguna de las variables disponibles
  df_top_track_sel <- df_top_track |> 
    mutate(track.id, track.name, track.popularity, track.album.release_date, 
           name = map(track.album.artists, 'name'),
           .keep = 'none') |> 
    unnest_wider(col = name, names_sep = '_')
)

df_top_track_sel |> head()
# A tibble: 6 × 7
  track.id      track.name track.popularity track.album.release_…¹ name_1 name_2
  <chr>         <chr>                 <int> <chr>                  <chr>  <chr> 
1 5Uptvz6j1sjD… SI NO ES …               85 2024-05-23             Cris … <NA>  
2 1pymWRCuZfCd… REAL GANG…               87 2024-05-23             Trueno <NA>  
3 3yLoXi85lSf0… MUJER FINA               71 2024-05-30             Jere … <NA>  
4 6XjDF6nds4DE… Gata Only                94 2024-02-02             Floyy… Cris …
5 21hsqOOUfdSj… No Ponga …               62 2024-06-20             Cris … <NA>  
6 6WatFBLVB0x0… Si Antes …               76 2024-06-21             KAROL… <NA>  
# ℹ abbreviated name: ¹​track.album.release_date
# ℹ 1 more variable: name_3 <chr>

Spotify: spotifyr canciones 1

Con el id de las canciones, podemos obtener características musicales de las canciones.

df_track_af <- get_track_audio_features(df_top_track_sel$track.id)

head(df_track_af)
# A tibble: 6 × 18
  danceability energy   key loudness  mode speechiness acousticness
         <dbl>  <dbl> <int>    <dbl> <int>       <dbl>        <dbl>
1        0.763  0.626     5    -5.64     0      0.0433       0.0859
2        0.831  0.643     4    -4.66     0      0.0568       0.388 
3        0.842  0.689    10    -3.56     1      0.0965       0.032 
4        0.791  0.499     8    -8.47     0      0.0509       0.446 
5        0.608  0.514     6    -7.86     0      0.188        0.385 
6        0.924  0.668    11    -6.80     1      0.0469       0.446 
# ℹ 11 more variables: instrumentalness <dbl>, liveness <dbl>, valence <dbl>,
#   tempo <dbl>, type <chr>, id <chr>, uri <chr>, track_href <chr>,
#   analysis_url <chr>, duration_ms <int>, time_signature <int>

Spotify: spotifyr canciones 2

Gráfico con la posición relativa de las canciones según 5 características.

df_top_track_sel_gg <- bind_cols(df_top_track_sel, 
                                 df_track_af) |> 
  arrange(track.popularity) |> 
  mutate(pos = row_number())

gg <- df_top_track_sel_gg |> 
  pivot_longer(cols = c(danceability, 
                        energy, 
                        speechiness, 
                        acousticness, 
                        liveness),
               names_to = 'variable', 
               values_to = 'valor') |> 
  ggplot(aes(x = variable, 
             y = valor, 
             colour = track.popularity)) +
  geom_violin(colour = 'gray80') + 
  geom_point(aes(size = pos),
             alpha = 0.3,
             show.legend = FALSE) + 
  scale_colour_steps(low = '#266591', high = '#f57500',
                      ) +
  scale_radius() +
  theme_minimal() +
  labs(title = 'Características de las 50 canciones en Chile',
       subtitle = 'Lista Top 50 — Chile, Spotify', 
       x = NULL)
gg

Mastodon: API

La API de Mastodon se puede consultar acá.

  • Es de uso abierto y gratuito.

  • Packete: rtoot

Mastodon: rtoot 1

Instalar versión en desarrollo:

install.packages("rtoot")
library(rtoot)

custom_instance <- 'lile.cl'

lile_instance <- get_instance_general(instance = custom_instance, anonymous = TRUE)

lile_instance |> str(2)
List of 16
 $ uri              : chr "lile.cl"
 $ title            : chr "Lile"
 $ short_description: chr "Servidor chileno de Mastodon cuyo nombre hace referencia a un ave nativa del país. Aspiramos a ser una comunida"| __truncated__
 $ description      : chr "Servidor experimental de Mastodon.\r\n\r\nEl nombre viene de una ave nativa del país: <a href=\"https://www.ave"| __truncated__
 $ email            : chr "contacto@lile.cl"
 $ version          : chr "4.2.7"
 $ urls             :List of 1
  ..$ streaming_api: chr "wss://lile.cl"
 $ stats            :List of 3
  ..$ user_count  : int 3112
  ..$ status_count: int 526862
  ..$ domain_count: int 27642
 $ thumbnail        : chr "https://lile.cl/system/site_uploads/files/000/000/005/@1x/877f7d9c81d94e15.png"
 $ languages        :List of 1
  ..$ : chr "es"
 $ registrations    : logi TRUE
 $ approval_required: logi TRUE
 $ invites_enabled  : logi FALSE
 $ configuration    :List of 4
  ..$ accounts         :List of 1
  ..$ statuses         :List of 3
  ..$ media_attachments:List of 6
  ..$ polls            :List of 4
 $ contact_account  :List of 24
  ..$ id             : chr "109311144305293045"
  ..$ username       : chr "lile"
  ..$ acct           : chr "lile"
  ..$ display_name   : chr "Lile"
  ..$ locked         : logi FALSE
  ..$ bot            : logi FALSE
  ..$ discoverable   : logi TRUE
  ..$ group          : logi FALSE
  ..$ created_at     : chr "2022-11-09T00:00:00.000Z"
  ..$ note           : chr "<p>Cuenta oficial de comunicaciones de lile.cl</p><p>-----</p><p>Pato chileno, también conocido como Cormorán L"| __truncated__
  ..$ url            : chr "https://lile.cl/@lile"
  ..$ uri            : chr "https://lile.cl/users/lile"
  ..$ avatar         : chr "https://lile.cl/system/accounts/avatars/109/311/144/305/293/045/original/00941123303f1ca5.jpeg"
  ..$ avatar_static  : chr "https://lile.cl/system/accounts/avatars/109/311/144/305/293/045/original/00941123303f1ca5.jpeg"
  ..$ header         : chr "https://lile.cl/system/accounts/headers/109/311/144/305/293/045/original/af94d90915dba11c.jpg"
  ..$ header_static  : chr "https://lile.cl/system/accounts/headers/109/311/144/305/293/045/original/af94d90915dba11c.jpg"
  ..$ followers_count: int 1036
  ..$ following_count: int 0
  ..$ statuses_count : int 44
  ..$ last_status_at : chr "2023-06-29"
  ..$ noindex        : logi FALSE
  ..$ emojis         : list()
  ..$ roles          :List of 1
  ..$ fields         :List of 4
 $ rules            :List of 5
  ..$ :List of 2
  ..$ :List of 2
  ..$ :List of 2
  ..$ :List of 2
  ..$ :List of 2
 - attr(*, "headers")= tibble [1 × 3] (S3: tbl_df/tbl/data.frame)

Mastodon: rtoot 2

Listado de instancias de Mastodon

lile_peers <- get_instance_peers(instance = custom_instance, token = NULL, anonymous = TRUE)

head(lile_peers)
[1] "miku.social"                       "www.lexicomancia.com"             
[3] "bram.dev"                          "tube-maternelle.apps.education.fr"
[5] "donley.io"                         "eweg.be"                          

Instancias con “chile” en su nombre:

lile_peers[grepl('chile', lile_peers)]
[1] "epicyon.chilemasto.casa"   "friendica.chilemasto.casa"
[3] "chilemasto.casa"           "devschile.social"         

Buscar la instancia oficial:

lile_peers[grepl('mastodon.social', lile_peers)]
[1] "snowmastodon.social"      "mastodon.socialspill.com"
[3] "mastodon.social"          "swedishmastodon.social"  

Mastodon: rtoot 3

get_instance_trends(
  instance = custom_instance,
  token = NULL,
  limit = 10,
  anonymous = TRUE
)
# A tibble: 70 × 5
   name         url                               day        accounts  uses
   <chr>        <chr>                             <date>        <int> <int>
 1 tunetuesday  https://lile.cl/tags/tunetuesday  2024-06-25       45    51
 2 tunetuesday  https://lile.cl/tags/tunetuesday  2024-06-24        0     0
 3 tunetuesday  https://lile.cl/tags/tunetuesday  2024-06-23        0     0
 4 tunetuesday  https://lile.cl/tags/tunetuesday  2024-06-22        0     0
 5 tunetuesday  https://lile.cl/tags/tunetuesday  2024-06-21        0     0
 6 tunetuesday  https://lile.cl/tags/tunetuesday  2024-06-20        0     0
 7 tunetuesday  https://lile.cl/tags/tunetuesday  2024-06-19        2     2
 8 forrockssake https://lile.cl/tags/forrockssake 2024-06-25       40    45
 9 forrockssake https://lile.cl/tags/forrockssake 2024-06-24        0     0
10 forrockssake https://lile.cl/tags/forrockssake 2024-06-23        0     0
# ℹ 60 more rows

Mastodon: rtoot 5, stream

if(interactive()) {
  stream_timeline_public(
    timeout = 30,
    local = FALSE,
    file_name = 'slides/class_9/class_9_files/stream_lile_public.json',
    append = TRUE,
    instance = custom_instance,
    token = NULL,
    anonymous = FALSE,
    verbose = TRUE
  )
  
  parse_stream("slides/class_9/class_9_files/stream_lile_public.json")
}

En el próximo taller…

  • chromote.

  • repaso.

Presentación y código en GitHub:
https://github.com/caayala/web_scraping_soc40XX_2024
https://caayala.github.io/web_scraping_soc40XX_2024


¡Gracias!


Cristián Ayala
https://blog.desuc.cl/
http://github.com/caayala