Step-by-Step Guide to Use R and Selenium to Scrape Empleos Publicos (Part 2)

Using R, selenium and purrr to organize hundreds of HTML sections into one table.
Author

Mauricio “Pachá” Vargas S.

Published

August 21, 2025

Because of delays with my scholarship payment, if this post is useful to you I kindly ask a minimal donation on Buy Me a Coffee. It shall be used to continue my Open Source efforts. The full explanation is here: A Personal Message from an Open Source Contributor.

Continuing with the previous Selenium post, now I will add the URL for each job offer and export the result to an XLSX file.

This requires the writexl package to write XLSX files:

if (!require(writexl)) install.packages("writexl")

To read the HTML it is the same as before but the page may have changes:

library(RSelenium)
library(rvest)
library(dplyr)
library(purrr)
library(writexl)

rmDr <- remoteDriver(port = 4444L, browserName = "chrome")

rmDr$open(silent = TRUE)

url <- "https://www.empleospublicos.cl"

rmDr$navigate(url)

search_box <- rmDr$findElement(using = "id", value = "buscadorprincipal")
search_box$sendKeysToElement(list("Ministerio de Salud", key = "enter"))

The first offer listed is this:

<div class="items col-md-4 col-lg-4 postulacion otro otro eepp region7renta3calidad2 busqueda "><div class="item"><div class="top"><div class="label label-estado"><i class="fa fa-circle circulo-status1" aria-hidden="true"></i> Postulación hasta 30/09/2025 23:59:00</div><h3><a target="_blank" href="https://www.empleospublicos.cl/pub/convocatorias/convpostularavisoTrabajo.aspx?i=130648&amp;c=0&amp;j=0&amp;tipo=convpostularavisoTrabajo" onclick="ga('send', 'event', 'convocatorias', 'Medico (a) especialista en Anestesiología 44 horas | Servicio de Salud Maule / Hospital de Constitución', 'eepp');">Medico (a) especialista en Anestesiología 44 horas</a></h3><p>Servicio de Salud Maule / Hospital de Constitución</p></div><hr><div class="cnt"><p>Ministerio de Salud</p><p>Constitución</p><br><div class="alert alert-primer"><i class="fa fa-address-card" aria-hidden="true"></i>  No pide experiencia</div><div class="row card-footer"><div class="col-xs-9 col-md-8 text-left"><a class="cronograma btn " url="https://www.empleospublicos.cl/pub/convocatorias/convpostularavisoTrabajo.aspx?i=130648&amp;c=0&amp;j=0&amp;tipo=convpostularavisoTrabajo" onclick="return false;" href="#" title="Ver Cronograma de la Convocatoria"><i class="fa fa-calendar-days"></i> Calendarización</a>
        <div class="compartir-social">      
            <div class="row">
                <div class="col-xs-3 col-md-4">
                    <a class="btn" onclick="enviarRS('t', 'https://www.empleospublicos.cl/pub/convocatorias/convpostularavisoTrabajo.aspx?i=130648&amp;c=0&amp;j=0&amp;tipo=convpostularavisoTrabajo', 'Medico (a) especialista en Anestesiología 44 horas Servicio de Salud Maule / Hospital de Constitución'); return false;" href="#" target="_blank" title="Compartir en Twitter"><i class="fa-brands fa-square-x-twitter fa-xl" aria-hidden="true"></i></a>
                </div>
                <div class="col-xs-3 col-md-4">
                    <a class="btn" onclick="enviarRS('f', 'https://www.empleospublicos.cl/pub/convocatorias/convpostularavisoTrabajo.aspx?i=130648&amp;c=0&amp;j=0&amp;tipo=convpostularavisoTrabajo', 'Medico (a) especialista en Anestesiología 44 horas Servicio de Salud Maule / Hospital de Constitución'); return false;" href="#" target="_blank" title="Compartir en Facebook"><i class="fa-brands fa-square-facebook fa-xl" aria-hidden="true"></i></a>
                </div>
                <div class="col-xs-3 col-md-4">
                    <a class="btn" onclick="enviarRS('l', 'https://www.empleospublicos.cl/pub/convocatorias/convpostularavisoTrabajo.aspx?i=130648&amp;c=0&amp;j=0&amp;tipo=convpostularavisoTrabajo', 'Medico (a) especialista en Anestesiología 44 horas Servicio de Salud Maule / Hospital de Constitución'); return false;" href="#" target="_blank" title="Compartir en Linkedin"><i class="fa-brands fa-linkedin fa-xl" aria-hidden="true"></i></a>
                </div>
                <div class="col-xs-3 col-md-4">
                    <a class="btn whatsapp-link visible-xs visible-sm" title="Compartir en Whatsapp" onclick="enviarRS('w', 'https://www.empleospublicos.cl/pub/convocatorias/convpostularavisoTrabajo.aspx?i=130648&amp;c=0&amp;j=0&amp;tipo=convpostularavisoTrabajo', 'Medico (a) especialista en Anestesiología 44 horas Servicio de Salud Maule / Hospital de Constitución'); return false;" href="#" data-action="share/whatsapp/share"><i class="fa-brands fa-square-whatsapp fa-xl" aria-hidden="true"></i></a>
                </div>
            </div>
        </div>
    <div class="row"><div class="col-md-12 card-footer-contenido "></div></div></div></div></div></div></div>

Inspecting that element, the link to the job offer is here:

<a class="cronograma btn " url="https://www.empleospublicos.cl/pub/convocatorias/convpostularavisoTrabajo.aspx?i=130648&amp;c=0&amp;j=0&amp;tipo=convpostularavisoTrabajo"

I can organize all the job offers using purrr:

html <- read_html(rmDr$getPageSource()[[1]])

offers <- html %>%
  html_nodes("div.items")

timestamp <- Sys.time()

offers_tbl <- map_df(offers, function(offer) {
  # Extract position (job title)
  position <- offer %>%
    html_node("h3 a") %>%
    html_text(trim = TRUE)
  
  # Extract organization (usually the first <p> inside .top)
  organization <- offer %>%
    html_node(".top p") %>%
    html_text(trim = TRUE)
  
  # Extract city (the second <p> inside .cnt)
  city <- offer %>%
    html_nodes(".cnt p") %>%
    .[2] %>%
    html_text(trim = TRUE)
  
  # Extract job offer link (href from h3 a)
  link <- offer %>%
    html_node("h3 a") %>%
    html_attr("href")
  
  tibble(
    position = position,
    organization = organization,
    city = city,
    link = link,
    timestamp = timestamp
  )
})

Here is the result:

> offers_tbl
# A tibble: 545 × 5
   position                         organization city  link  timestamp          
   <chr>                            <chr>        <chr> <chr> <dttm>             
 1 Medico (a) especialista en Anes… Servicio de… Cons… http… 2025-08-21 13:39:10
 2 Titulares de la Planta Profesio… Servicio de… Valp… http… 2025-08-21 13:39:10
 3 ENFERMERA-O, JORNADA DIURNA, GR… Servicio de… Reco… http… 2025-08-21 13:39:10
 4 Psiquiatra infanto-juvenil sist… Servicio de… La P… http… 2025-08-21 13:39:10
 5 Neurólogo(a) adulto GES Alzheim… Servicio de… Puen… http… 2025-08-21 13:39:10
 6 Médico(a) especialista en Neuro… Servicio de… Cast… http… 2025-08-21 13:39:10
 7 Arquitecto de Software           Central de … Ñuñoa http… 2025-08-21 13:39:10
 8 ARQUITECTO(A) SECCIÓN OBRAS CIV… Servicio de… Chil… http… 2025-08-21 13:39:10
 9 TENS OPERADOR DE EQUIPOS DE EST… Servicio de… Peña… http… 2025-08-21 13:39:10
10 TENS DE CUIDADOS PALIATIVOS Y E… Servicio de… San … http… 2025-08-21 13:39:10
# ℹ 535 more row

To export to an XLSX file:

write_xlsx(offers_tbl, "offers_20250821.xlsx")

I hope this is useful. The next part shll cover reading the details of each job offer on each URL.