Stop worrying about the potholes in the road and enjoy the journey
AI

Cómo construir un extractor de noticias universal con Node.js, Gemini AI y SQLite

Imagina despertarte cada mañana y encontrar una base de datos llena de artículos de noticias de hoy, ya limpiados, estructurados y listos para el análisis. Sin navegación manual, sin copiar y pegar, sin dolores de cabeza de formato. Solo datos frescos esperándote.

Eso es exactamente lo que construimos: un raspador de noticias universal que funciona en prácticamente cualquier sitio web de noticias, entiende fechas en lenguaje natural en cualquier formato, descubre automáticamente enlaces de paginación, se detiene cuando llega a los artículos de ayer y almacena todo ordenadamente en una base de datos local SQLite.

La clave es usar la IA no como un chatbot, sino como un motor de análisis inteligente. En lugar de escribir selectores CSS frágiles que se rompen cada vez que un sitio se rediseña, dejamos que Gemini lea la página como un humano y extraiga lo que importa.


Visión General de la Arquitectura

El sistema consta de tres componentes que se ejecutan localmente en tu máquina:

Servidor Proxy (puerto 3001) — Un servidor HTTP Node.js con dos puntos finales. El punto final /fetch descarga cualquier URL, elimina scripts y estilos, preserva etiquetas HTML estructurales, extrae enlaces de paginación del HTML bruto antes de la truncación y devuelve contenido limpio y estructurado. El punto final /ai recibe contenido de página y un aviso, los envía a la API de Gemini con reintento automático en errores de límite de tasa, y devuelve la respuesta del modelo.

Script de Raspado — Un script Node.js que orquesta todo. Se ejecuta en dos fases: la Fase 1 recopila todos los enlaces de artículos de las páginas de listado siguiendo la paginación automáticamente, la Fase 2 visita cada URL de artículo, extrae el contenido completo a través de Gemini y lo guarda en la base de datos.

Base de Datos SQLite — Una base de datos local de un solo archivo que almacena artículos con campos para URL (único, previene duplicados), título, contenido, fecha, fuente y marca de tiempo de creación.

[Cualquier sitio web de noticias]
       ↓ HTTP fetch (encabezados tipo navegador)
[Servidor Proxy — server.js :3001]
       ↓ HTML limpio + enlace de paginación
[Gemini 2.5 Flash Lite API]
       ↓ JSON estructurado
[scrape.js]
       ↓ INSERTAR O IGNORAR
[news.db — SQLite]

¿Por qué Gemini y no un Modelo Local?

Probamos modelos locales (Llama 3.2, Mistral 7B a través de Ollama) para esta tarea. Los resultados fueron decepcionantes: los modelos pequeños que se ejecutan en CPU son demasiado lentos para procesar cientos de páginas, alucinan enlaces de paginación que no existen y tienen dificultades para distinguir el contenido principal de los widgets de la barra lateral cuando la estructura de la página es compleja.

Gemini 2.5 Flash Lite resuelve todos estos problemas. Procesa una página completa limpiada en 1–2 segundos, identifica correctamente las fechas de los artículos en cualquier idioma y formato, entiende la estructura semántica HTML y cuesta prácticamente nada — aproximadamente €0.55 por 200 artículos.


Paso 1: Obtén Tu Clave API de Gemini

Ve a aistudio.google.com/api-keys y crea una nueva clave. El nivel gratuito ofrece 20 solicitudes por minuto, lo cual es suficiente para el desarrollo. Para el raspado en producción, añade un método de pago — cuesta aproximadamente €0.25 por mes con un uso diario típico.

Pega tu clave en server.js en la línea 10:

const GEMINI_KEY = 'tu_clave_aquí';

Paso 2: El Servidor Proxy (server.js)

El servidor proxy es el corazón del sistema. Su trabajo más crítico es extraer el enlace de la siguiente página antes de eliminar los atributos HTML — un detalle sutil pero importante.

Cuando limpiamos HTML, eliminamos atributos como rel="next" y aria-label para reducir el tamaño del contenido para la IA. Pero esos son exactamente los atributos que identifican el enlace de paginación. Así que llamamos a extractNextPage() primero en el HTML bruto, luego limpiamos todo lo demás.

La función busca tres patrones que cubren la gran mayoría de los sitios de noticias:

  • rel="next" — el atributo estándar de paginación HTML5
  • aria-label que contiene “next”, “далі”, “вперед”, “следующая”
  • Elementos con clase is-next — común en marcos CSS
// server.js
// [INSERTAR CÓDIGO COMPLETO DE server.js AQUÍ]

La función cleanHtml() mantiene las etiquetas estructurales (<main>, <article>, <section>, <aside>, <nav>, <time datetime>) mientras elimina todo lo demás. Esto le da a Gemini suficiente contexto para distinguir el contenido principal de las barras laterales sin procesar megabytes de HTML bruto.

La función askGemini() lee el tiempo de espera real de la respuesta de error de límite de tasa de Gemini (retry in 23.9s) y espera exactamente ese tiempo antes de reintentar. Sin adivinanzas, sin retrasos fijos.


Paso 3: Instalar Dependencias y Ejecutar el Servidor

cd tu-carpeta-del-proyecto
npm install better-sqlite3
node server.js

Deberías ver:

Servidor ejecutándose en http://localhost:3001

Pruébalo abriendo esta URL en tu navegador:

http://localhost:3001/fetch?url=https://example.com

Recibirás una respuesta JSON con campos text, links y nextPage.


Paso 4: Descubrimiento Inteligente de Paginación

El raspador nunca tiene una lista codificada de URLs de página. Comienza desde una URL y sigue el enlace de “siguiente página” automáticamente en cada página.

Diferentes sitios usan patrones de paginación completamente diferentes:

  • https://site.com/news/page/2 — estilo WordPress
  • https://site.com/news/page-2 — separador de guion (TSN.ua como ejemplo para el artículo)
  • https://site.com/news?page=2 — parámetro de consulta
  • https://site.com/news-2.html — estilo HTML plano

El proxy maneja todos ellos porque busca atributos semánticos en el HTML, no patrones de URL. El raspador solo lee pageData.nextPage de la respuesta del proxy y lo sigue ciegamente.


Paso 5: Filtrado de Fechas Potenciado por IA

Aquí es donde el enfoque realmente brilla sobre los raspadores tradicionales. Los sitios de noticias muestran fechas en formatos muy diferentes:

  • <time datetime="2026-04-21T17:30:00+03:00"> — estándar HTML5
  • 21 квітня 2026 — forma larga ucraniana
  • Сьогодні, 17:30 — “Hoy” en ucraniano
  • Solo 17:30 — solo hora, implicando hoy

Un raspador tradicional necesitaría lógica de análisis personalizada para cada formato en cada sitio. Nosotros solo le decimos a Gemini la fecha de hoy y le pedimos que filtre:

Hoy es 2026-04-21.

Extrae enlaces de artículos de noticias del área de contenido PRINCIPAL publicados SOLO HOY.
Las fechas pueden estar en cualquier formato: <time datetime>, "21.04.26", "17:30" (solo hora = hoy), "сьогодні", etc.
EXCLUIR: barras laterales, "читайте також", anuncios, navegación, páginas de categoría/etiqueta/autor.
Establecer "stop": true si ves artículos de AYER o anteriores en el contenido principal.

Devuelve SOLO JSON válido:
{"articles":[{"title":"...","url":"https://..."}],"stop":false,"reason":"..."}

La bandera stop es el mecanismo clave. Cuando Gemini ve artículos de ayer en el contenido principal, devuelve "stop": true y el raspador deja de seguir la paginación, sin importar en qué número de página estemos.


Paso 6: Extracción de Contenido de Artículos

La Fase 2 visita cada URL de artículo y extrae el contenido real. El aviso es deliberadamente mínimo:

Extrae lo siguiente de esta página de artículo de noticias:
1. "title" — el titular del artículo
2. "date" — fecha de publicación en formato YYYY-MM-DD
3. "content" — texto completo del artículo, limpio, sin anuncios ni navegación

Devuelve SOLO JSON válido sin explicación, sin markdown, sin texto antes o después:
{"title":"...","date":"2026-04-21","content":"..."}

Nota que no hay campo de resumen. Es tentador generar resúmenes mientras se raspa, pero desperdicia tokens y tiempo para algo que quizás nunca uses. Almacena contenido bruto ahora, genera resúmenes bajo demanda cuando realmente los necesites para el análisis.


Paso 7: El Script de Raspado (scrape.js)

// scrape.js
// [INSERTAR CÓDIGO COMPLETO DE scrape.js AQUÍ]

La declaración SQL INSERTAR O IGNORAR combinada con la restricción UNIQUE en la columna url maneja toda la deduplicación automáticamente. Ejecuta el raspador cada hora: omite silenciosamente cualquier artículo que ya esté en la base de datos.


Ejecutándolo

Necesitas dos ventanas de terminal:

Terminal 1 — servidor proxy (mantener en ejecución):

node server.js

Terminal 2 — raspador (ejecutar cuando quieras artículos frescos):

node scrape.js

Para raspar un sitio de noticias diferente, cambia solo una línea en la parte superior de scrape.js:

const START = 'https://tu-sitio-de-noticias.com/news';

Resultados

Ejecutando contra TSN.ua (sitio de noticias ucraniano importante, como ejemplo para el artículo) el 21 de abril de 2026:

  • Fase 1: 198 enlaces de artículos recopilados de 20 páginas — se detuvo automáticamente cuando aparecieron artículos de ayer
  • Fase 2: 198 artículos obtenidos, limpiados y almacenados
  • Tiempo total: ~11 minutos
  • Costo total: €0.55 en créditos de API de Gemini
  • Tamaño de la base de datos: ~8MB

El raspador ignoró correctamente los widgets de la barra lateral con artículos populares antiguos, horóscopos, pronósticos del tiempo y páginas de tasas de cambio. Solo los artículos de noticias reales publicados ese día llegaron a la base de datos.


Visualizando la Base de Datos

Descarga DB Browser for SQLite — gratis, de código abierto. Abre news.db y verás todos los artículos en una vista tipo hoja de cálculo. Puedes filtrar por fecha, buscar contenido, exportar a CSV.


¿Qué Sigue?

Autopublicación en WordPress — un script que lee de la base de datos y publica en WordPress a través de la API REST. Combinado con Claude reescribiendo contenido en español, alemán o polaco, esto se convierte en un sitio de noticias multilingüe completamente automatizado.

Múltiples fuentes — el mismo servidor y raspador funcionan en cualquier sitio de noticias. BBC, Reuters, TechCrunch, sitios regionales en cualquier idioma — una base de código maneja todos ellos.

Ejecución programada — añade un trabajo de Programador de Tareas de Windows o cron de Linux para ejecutar node scrape.js cada mañana. Despierta con una base de datos completa de las noticias de hoy.

Capa de análisis — un segundo script lee los artículos de hoy desde SQLite, los envía a la API de Claude y genera un informe matutino: historias clave, tendencias, implicaciones. La base de datos se convierte en un feed de inteligencia.


Construido en un día usando Node.js 23, Gemini 2.5 Flash Lite y better-sqlite3. La parte más difícil fue descubrir que los enlaces de paginación desaparecen cuando eliminas atributos HTML — por eso extractNextPage() debe ejecutarse en HTML bruto antes de cualquier limpieza.


Descargar Código Fuente

  • fuentes — servidor proxy con integración de Gemini + raspador de dos fases con almacenamiento SQLite
0