14_10_25
## used (Mb) gc trigger (Mb) max used (Mb)
## Ncells 618329 33.1 1415461 75.6 702048 37.5
## Vcells 1156215 8.9 8388608 64.0 1927429 14.8
# Ejecutar SOLO si los paquetes no están instalados
install.packages("rvest", dependencies = TRUE)
install.packages("dplyr", dependencies = TRUE)
install.packages("lubridate", dependencies = TRUE)
install.packages("stringr", dependencies = TRUE)
install.packages("knitr", dependencies = TRUE)
install.packages("DT", dependencies = TRUE)
install.packages("ggplot2", dependencies = TRUE)
obtener_fuente <- function(url) {
dominios <- list(
"mvsnoticias.com" = "MVS Noticias",
"yucatan.com.mx" = "Yucatán.com.mx",
"eluniversal.com.mx" = "El Universal"
)
for (dominio in names(dominios)) {
if (grepl(dominio, url, fixed = TRUE)) {
return(dominios[[dominio]])
}
}
return("Medio desconocido")
}
extraer_fecha <- function(pagina, selectores) {
for (selector in selectores) {
tryCatch({
# Intentar extraer del atributo datetime
fecha <- pagina %>%
html_element(selector) %>%
html_attr("datetime")
if (!is.na(fecha) && nchar(fecha) > 0) {
return(fecha)
}
# Si no hay datetime, extraer texto
fecha <- pagina %>%
html_element(selector) %>%
html_text() %>%
str_trim()
if (!is.na(fecha) && nchar(fecha) > 0) {
return(fecha)
}
}, error = function(e) NULL)
}
return(NA_character_)
}
extraer_texto <- function(pagina, selectores) {
# Intentar con selectores específicos
for (selector in selectores) {
tryCatch({
texto <- pagina %>%
html_element(selector) %>%
html_text() %>%
str_trim() %>%
str_squish()
if (!is.na(texto) && nchar(texto) > 100) {
return(texto)
}
}, error = function(e) NULL)
}
# Fallback: extraer todos los párrafos
tryCatch({
texto <- pagina %>%
html_elements("p") %>%
html_text() %>%
str_trim() %>%
paste(collapse = " ") %>%
str_squish()
if (!is.na(texto) && nchar(texto) > 100) {
return(texto)
}
}, error = function(e) NULL)
return(NA_character_)
}
scrape_noticia <- function(url) {
tryCatch({
# Descargar la página
pagina <- read_html(url)
fuente <- obtener_fuente(url)
# Selectores por medio
selectores_fecha <- list(
"MVS Noticias" = c("time", ".date", ".published-date",
"meta[property='article:published_time']"),
"Yucatán.com.mx" = c("time[datetime]", ".article-date",
".published-date", ".entry-date"),
"El Universal" = c("time[datetime]", ".publish-date", "time",
".date-published")
)
selectores_autor <- list(
"MVS Noticias" = c(".author-name", ".autor", "[rel='author']", ".byline"),
"Yucatán.com.mx" = c(".author", "[rel='author']", ".author-name",
".entry-author"),
"El Universal" = c(".author-name", ".byline", "[rel='author']", ".autor")
)
selectores_texto <- list(
"MVS Noticias" = c("article", ".article-content", ".entry-content",
".post-content"),
"Yucatán.com.mx" = c("article", ".entry-content", ".post-content",
".article-body"),
"El Universal" = c("article", ".article-body", ".story-body",
".entry-content")
)
# Obtener selectores
sel_fecha <- selectores_fecha[[fuente]] %||%
c("time", ".date", "meta[property='article:published_time']")
sel_autor <- selectores_autor[[fuente]] %||%
c("[rel='author']", ".author", ".byline")
sel_texto <- selectores_texto[[fuente]] %||%
c("article", ".entry-content", ".post-content")
# Extraer información
fecha <- extraer_fecha(pagina, sel_fecha)
autor <- extraer_autor(pagina, sel_autor)
texto <- extraer_texto(pagina, sel_texto)
# Normalizar valores
fecha <- ifelse(is.na(fecha) || nchar(fecha) == 0, "No disponible", fecha)
autor <- ifelse(is.na(autor) || nchar(autor) == 0, "No disponible", autor)
texto <- ifelse(is.na(texto) || nchar(texto) == 0, "No disponible", texto)
# Crear resultado
resultado <- tibble(
Fuente = fuente,
URL = url,
Fecha_Redaccion = fecha,
Autor = autor,
Texto_Crudo = texto,
Fecha_Scraping = as.character(Sys.time()),
Caracteres = nchar(texto)
)
return(resultado)
}, error = function(e) {
return(tibble(
Fuente = obtener_fuente(url),
URL = url,
Fecha_Redaccion = NA_character_,
Autor = NA_character_,
Texto_Crudo = NA_character_,
Fecha_Scraping = as.character(Sys.time()),
Caracteres = NA_integer_
))
})
}
scrape_noticias_lote <- function(urls, pausa = 2) {
total <- length(urls)
cat(sprintf("\n=== Iniciando scraping de %d noticias ===\n\n", total))
resultados <- vector("list", total)
for (i in seq_along(urls)) {
cat(sprintf("[%d/%d] Procesando: %s\n", i, total, urls[i]))
resultados[[i]] <- scrape_noticia(urls[i])
if (i < total) {
Sys.sleep(pausa)
}
}
cat("\n=== Scraping completado ===\n\n")
return(bind_rows(resultados))
}
urls_yucatan <- c(
"https://mvsnoticias.com/yucatan/2025/10/13/investigacion-en-curso-por-fallecimiento-de-una-persona-en-oxkutzcab-715216.html",
"https://www.yucatan.com.mx/merida/2025/10/12/feminicidios-en-yucatan-ante-una-preocupante-tendencia.html",
"https://www.eluniversal.com.mx/estados/reportan-nuevo-envenenamiento-de-animales-en-yucatan/",
"https://www.yucatan.com.mx/central-9/2025/10/02/mas-violencia-en-yucatan-suben-28-los-heridos-a-balazos-y-25-los-homicidios-en-un-ano.html"
)
cat("Total de URLs a procesar:", length(urls_yucatan), "\n")
## Total de URLs a procesar: 4
##
## === Iniciando scraping de 4 noticias ===
##
## [1/4] Procesando: https://mvsnoticias.com/yucatan/2025/10/13/investigacion-en-curso-por-fallecimiento-de-una-persona-en-oxkutzcab-715216.html
## [2/4] Procesando: https://www.yucatan.com.mx/merida/2025/10/12/feminicidios-en-yucatan-ante-una-preocupante-tendencia.html
## [3/4] Procesando: https://www.eluniversal.com.mx/estados/reportan-nuevo-envenenamiento-de-animales-en-yucatan/
## [4/4] Procesando: https://www.yucatan.com.mx/central-9/2025/10/02/mas-violencia-en-yucatan-suben-28-los-heridos-a-balazos-y-25-los-homicidios-en-un-ano.html
##
## === Scraping completado ===
# Mostrar las primeras filas
noticias_yucatan %>%
select(Fuente, Autor, Caracteres) %>%
head() %>%
kable(caption = "Primeras filas del dataset")
Fuente | Autor | Caracteres |
---|---|---|
MVS Noticias | No disponible | 1273 |
Yucatán.com.mx | David Domínguez Massa | 15212 |
El Universal | No disponible | 2060 |
Yucatán.com.mx | Flor Estrella Santana | 2166 |
## === ESTADÍSTICAS GENERALES ===
## Total de noticias procesadas: 4
## Noticias con texto extraído: 4
## Noticias con autor: 2
## Noticias con fecha: 3
resumen_fuente <- noticias_yucatan %>%
group_by(Fuente) %>%
summarise(
Cantidad = n(),
Promedio_Caracteres = round(mean(Caracteres, na.rm = TRUE), 0),
Min_Caracteres = min(Caracteres, na.rm = TRUE),
Max_Caracteres = max(Caracteres, na.rm = TRUE),
Con_Autor = sum(Autor != "No disponible"),
Con_Fecha = sum(Fecha_Redaccion != "No disponible"),
.groups = "drop"
)
resumen_fuente %>%
kable(caption = "Resumen por fuente de noticias",
col.names = c("Fuente", "Cantidad", "Promedio Car.", "Min Car.",
"Max Car.", "Con Autor", "Con Fecha"))
Fuente | Cantidad | Promedio Car. | Min Car. | Max Car. | Con Autor | Con Fecha |
---|---|---|---|---|---|---|
El Universal | 1 | 2060 | 2060 | 2060 | 0 | 0 |
MVS Noticias | 1 | 1273 | 1273 | 1273 | 0 | 1 |
Yucatán.com.mx | 2 | 8689 | 2166 | 15212 | 2 | 2 |
ggplot(noticias_yucatan, aes(x = Fuente, y = Caracteres, fill = Fuente)) +
geom_col(show.legend = FALSE) +
geom_text(aes(label = scales::comma(Caracteres)),
vjust = -0.5, size = 4) +
labs(
title = "Longitud de Artículos por Medio",
subtitle = "Número total de caracteres extraídos",
x = "Medio de Comunicación",
y = "Caracteres"
) +
theme_minimal(base_size = 14) +
theme(
plot.title = element_text(face = "bold", size = 16),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
scale_y_continuous(labels = scales::comma)
Cantidad de caracteres por medio
datos_completitud <- noticias_yucatan %>%
summarise(
`Texto Extraído` = sum(Caracteres > 100, na.rm = TRUE),
`Con Autor` = sum(Autor != "No disponible"),
`Con Fecha` = sum(Fecha_Redaccion != "No disponible")
) %>%
tidyr::pivot_longer(everything(), names_to = "Campo", values_to = "Cantidad")
ggplot(datos_completitud, aes(x = Campo, y = Cantidad, fill = Campo)) +
geom_col(show.legend = FALSE) +
geom_text(aes(label = Cantidad), vjust = -0.5, size = 5) +
labs(
title = "Completitud de Datos Extraídos",
subtitle = paste("Total de", nrow(noticias_yucatan), "noticias procesadas"),
x = "",
y = "Cantidad"
) +
theme_minimal(base_size = 14) +
theme(plot.title = element_text(face = "bold", size = 16)) +
ylim(0, nrow(noticias_yucatan) + 0.5)
Completitud de los datos extraídos
# CSV
archivo_csv <- "output/noticias_yucatan.csv"
write.csv(noticias_yucatan, archivo_csv, row.names = FALSE, fileEncoding = "UTF-8")
cat(sprintf("✓ Datos exportados a: %s\n", archivo_csv))
## ✓ Datos exportados a: output/noticias_yucatan.csv
# TSV
archivo_tsv <- "output/noticias_yucatan.tsv"
write.table(noticias_yucatan, archivo_tsv, row.names = FALSE,
sep = "\t", fileEncoding = "UTF-8")
cat(sprintf("✓ Datos exportados a: %s\n", archivo_tsv))
## ✓ Datos exportados a: output/noticias_yucatan.tsv
# RDS (formato R)
archivo_rds <- "output/noticias_yucatan.rds"
saveRDS(noticias_yucatan, archivo_rds)
cat(sprintf("✓ Datos exportados a: %s\n", archivo_rds))
## ✓ Datos exportados a: output/noticias_yucatan.rds
# Excel (opcional)
if (requireNamespace("writexl", quietly = TRUE)) {
archivo_xlsx <- "output/noticias_yucatan.xlsx"
writexl::write_xlsx(noticias_yucatan, archivo_xlsx)
cat(sprintf("✓ Datos exportados a: %s\n", archivo_xlsx))
}
## ✓ Datos exportados a: output/noticias_yucatan.xlsx
cat("- **Tasa de éxito en extracción:**",
round(sum(noticias_yucatan$Caracteres > 100, na.rm = TRUE) /
nrow(noticias_yucatan) * 100, 1), "%\n")
cat("- **Promedio de caracteres por noticia:**",
round(mean(noticias_yucatan$Caracteres, na.rm = TRUE), 0), "\n")
## R version 4.5.1 (2025-06-13 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26100)
##
## Matrix products: default
## LAPACK version 3.12.1
##
## locale:
## [1] LC_COLLATE=Spanish_Mexico.utf8 LC_CTYPE=Spanish_Mexico.utf8
## [3] LC_MONETARY=Spanish_Mexico.utf8 LC_NUMERIC=C
## [5] LC_TIME=Spanish_Mexico.utf8
##
## time zone: America/Mexico_City
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] ggplot2_4.0.0 DT_0.34.0 knitr_1.50 stringr_1.5.2
## [5] lubridate_1.9.4 dplyr_1.1.4 rvest_1.0.5
##
## loaded via a namespace (and not attached):
## [1] gtable_0.3.6 jsonlite_2.0.0 selectr_0.4-2 compiler_4.5.1
## [5] tidyselect_1.2.1 xml2_1.4.0 tidyr_1.3.1 jquerylib_0.1.4
## [9] scales_1.4.0 yaml_2.3.10 fastmap_1.2.0 R6_2.6.1
## [13] labeling_0.4.3 generics_0.1.4 curl_7.0.0 htmlwidgets_1.6.4
## [17] tibble_3.3.0 bslib_0.9.0 pillar_1.11.1 RColorBrewer_1.1-3
## [21] rlang_1.1.6 cachem_1.1.0 stringi_1.8.7 xfun_0.53
## [25] S7_0.2.0 sass_0.4.10 timechange_0.3.0 cli_3.6.5
## [29] withr_3.0.2 magrittr_2.0.4 crosstalk_1.2.2 digest_0.6.37
## [33] grid_4.5.1 rstudioapi_0.17.1 lifecycle_1.0.4 vctrs_0.6.5
## [37] writexl_1.5.4 evaluate_1.0.5 glue_1.8.0 farver_2.1.2
## [41] purrr_1.1.0 rmarkdown_2.30 httr_1.4.7 tools_4.5.1
## [45] pkgconfig_2.0.3 htmltools_0.5.8.1
Documento generado el: 2025-10-14 12:48:13.079503