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

Como construir um raspador de notícias universal com Node.js, Gemini AI e SQLite

Imagine acordar todas as manhãs e encontrar um banco de dados cheio de artigos de notícias do dia, já limpos, estruturados e prontos para análise. Sem navegação manual, sem copiar e colar, sem dores de cabeça com formatação. Apenas dados frescos esperando por você.

Foi exatamente isso que construímos: um raspador de notícias universal que funciona em praticamente qualquer site de notícias, entende datas em linguagem natural em qualquer formato, descobre automaticamente links de paginação, para quando chega aos artigos de ontem e armazena tudo ordenadamente em um banco de dados local SQLite.

A principal percepção é usar IA não como um chatbot, mas como um motor de análise inteligente. Em vez de escrever seletores CSS frágeis que quebram toda vez que um site é redesenhado, deixamos o Gemini ler a página como um humano e extrair o que importa.


Visão Geral da Arquitetura

O sistema consiste em três componentes rodando localmente na sua máquina:

Servidor Proxy (porta 3001) — Um servidor HTTP Node.js com dois endpoints. O endpoint /fetch baixa qualquer URL, remove scripts e estilos, preserva tags estruturais HTML, extrai links de paginação do HTML bruto antes da truncagem e retorna conteúdo limpo e estruturado. O endpoint /ai recebe o conteúdo da página e um prompt, envia-os para a API Gemini com tentativa automática em caso de erros de limite de taxa e retorna a resposta do modelo.

Script de Raspagem — Um script Node.js que orquestra tudo. Ele roda em duas fases: Fase 1 coleta todos os links de artigos das páginas de listagem seguindo a paginação automaticamente, Fase 2 visita cada URL de artigo, extrai o conteúdo completo via Gemini e salva no banco de dados.

Banco de Dados SQLite — Um banco de dados local de arquivo único que armazena artigos com campos para URL (único, evita duplicatas), título, conteúdo, data, fonte e timestamp de criação.

[Qualquer site de notícias]
       ↓ Busca HTTP (cabeçalhos semelhantes a navegador)
[Servidor Proxy — server.js :3001]
       ↓ HTML limpo + link de paginação
[Gemini 2.5 Flash Lite API]
       ↓ JSON estruturado
[scrape.js]
       ↓ INSERT OR IGNORE
[news.db — SQLite]

Por que Gemini e Não um Modelo Local?

Testamos modelos locais (Llama 3.2, Mistral 7B via Ollama) para esta tarefa. Os resultados foram decepcionantes — modelos pequenos rodando em CPU são muito lentos para processar centenas de páginas, eles alucinam links de paginação que não existem e têm dificuldade em distinguir o conteúdo principal de widgets de barra lateral quando a estrutura da página é complexa.

O Gemini 2.5 Flash Lite resolve todos esses problemas. Ele processa uma página limpa completa em 1–2 segundos, identifica corretamente datas de artigos em qualquer idioma e formato, entende a estrutura semântica HTML e custa praticamente nada — cerca de €0,55 por 200 artigos.


Passo 1: Obtenha Sua Chave de API Gemini

Vá para aistudio.google.com/api-keys e crie uma nova chave. O nível gratuito oferece 20 solicitações por minuto, o que é suficiente para desenvolvimento. Para raspagem em produção, adicione um método de pagamento — custa cerca de €0,25 por mês com uso diário típico.

Cole sua chave na linha 10 do server.js:

const GEMINI_KEY = 'sua_chave_aqui';

Passo 2: O Servidor Proxy (server.js)

O servidor proxy é o coração do sistema. Seu trabalho mais crítico é extrair o link da próxima página antes de remover atributos HTML — um detalhe sutil, mas importante.

Quando limpamos o HTML, removemos atributos como rel="next" e aria-label para reduzir o tamanho do conteúdo para a IA. Mas esses são exatamente os atributos que identificam o link de paginação. Então chamamos extractNextPage() primeiro no HTML bruto, depois limpamos todo o resto.

A função procura por três padrões que cobrem a grande maioria dos sites de notícias:

  • rel="next" — o atributo padrão de paginação HTML5
  • aria-label contendo “next”, “далі”, “вперед”, “следующая”
  • Elementos com a classe is-next — comum em frameworks CSS
// server.js
// [INSERIR CÓDIGO COMPLETO DO server.js AQUI]

A função cleanHtml() mantém tags estruturais (<main>, <article>, <section>, <aside>, <nav>, <time datetime>) enquanto remove todo o resto. Isso dá ao Gemini contexto suficiente para distinguir o conteúdo principal das barras laterais sem processar megabytes de HTML bruto.

A função askGemini() lê o tempo de espera real da resposta de erro de limite de taxa do Gemini (retry in 23.9s) e espera exatamente esse tempo antes de tentar novamente. Sem adivinhações, sem atrasos fixos.


Passo 3: Instale Dependências e Execute o Servidor

cd sua-pasta-do-projeto
npm install better-sqlite3
node server.js

Você deve ver:

Servidor rodando em http://localhost:3001

Teste abrindo este URL no seu navegador:

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

Você receberá uma resposta JSON com os campos text, links e nextPage.


Passo 4: Descoberta Inteligente de Paginação

O raspador nunca tem uma lista codificada de URLs de página. Ele começa a partir de um URL e segue o link “próxima página” automaticamente em cada página.

Diferentes sites usam padrões de paginação completamente diferentes:

  • https://site.com/news/page/2 — estilo WordPress
  • https://site.com/news/page-2 — separador por hífen (TSN.ua como exemplo para o artigo)
  • https://site.com/news?page=2 — parâmetro de consulta
  • https://site.com/news-2.html — estilo HTML plano

O proxy lida com todos eles porque procura por atributos semânticos no HTML, não padrões de URL. O raspador apenas lê pageData.nextPage da resposta do proxy e o segue cegamente.


Passo 5: Filtragem de Datas com IA

É aqui que a abordagem realmente se destaca em relação aos raspadores tradicionais. Sites de notícias exibem datas em formatos extremamente diferentes:

  • <time datetime="2026-04-21T17:30:00+03:00"> — padrão HTML5
  • 21 квітня 2026 — forma longa ucraniana
  • Сьогодні, 17:30 — “Hoje” em ucraniano
  • Apenas 17:30 — apenas hora, implicando hoje

Um raspador tradicional precisaria de lógica de análise personalizada para cada formato em cada site. Nós apenas dizemos ao Gemini a data de hoje e pedimos para filtrar:

Hoje é 2026-04-21.

Extraia links de artigos de notícias da área de conteúdo PRINCIPAL publicados SOMENTE HOJE.
As datas podem estar em qualquer formato: <time datetime>, "21.04.26", "17:30" (apenas hora = hoje), "сьогодні", etc.
EXCLUA: barras laterais, "читайте також", anúncios, navegação, páginas de categoria/tag/autor.
Defina "stop": true se você vir artigos de ONTEM ou anteriores no conteúdo principal.

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

A flag stop é o mecanismo chave. Quando o Gemini vê artigos de ontem no conteúdo principal, ele retorna "stop": true e o raspador para de seguir a paginação — não importa em qual número de página estamos.


Passo 6: Extração de Conteúdo de Artigos

A Fase 2 visita cada URL de artigo e extrai o conteúdo real. O prompt é deliberadamente mínimo:

Extraia o seguinte desta página de artigo de notícias:
1. "title" — o título do artigo
2. "date" — data de publicação no formato YYYY-MM-DD
3. "content" — texto completo do artigo, limpo, sem anúncios ou navegação

Retorne APENAS JSON válido sem explicação, sem markdown, sem texto antes ou depois:
{"title":"...","date":"2026-04-21","content":"..."}

Note que não há campo de resumo. É tentador gerar resumos enquanto raspa, mas isso desperdiça tokens e tempo para algo que você pode nunca usar. Armazene o conteúdo bruto agora, gere resumos sob demanda quando realmente precisar deles para análise.


Passo 7: O Script de Raspagem (scrape.js)

// scrape.js
// [INSERIR CÓDIGO COMPLETO DO scrape.js AQUI]

A instrução SQL INSERT OR IGNORE combinada com a restrição UNIQUE na coluna url lida com toda a deduplicação automaticamente. Execute o raspador a cada hora — ele ignora silenciosamente qualquer artigo já no banco de dados.


Executando

Você precisa de duas janelas de terminal:

Terminal 1 — servidor proxy (mantenha rodando):

node server.js

Terminal 2 — raspador (execute quando quiser artigos frescos):

node scrape.js

Para raspar um site de notícias diferente, altere apenas uma linha no topo do scrape.js:

const START = 'https://seu-site-de-noticias.com/news';

Resultados

Executando contra TSN.ua (grande site de notícias ucraniano, como exemplo para o artigo) em 21 de abril de 2026:

  • Fase 1: 198 links de artigos coletados de 20 páginas — parou automaticamente quando apareceram artigos de ontem
  • Fase 2: 198 artigos buscados, limpos e armazenados
  • Tempo total: ~11 minutos
  • Custo total: €0,55 em créditos da API Gemini
  • Tamanho do banco de dados: ~8MB

O raspador ignorou corretamente widgets de barra lateral com artigos populares antigos, horóscopos, previsões do tempo e páginas de taxas de câmbio. Apenas artigos de notícias reais publicados naquele dia foram para o banco de dados.


Visualizando o Banco de Dados

Baixe o DB Browser for SQLite — gratuito, de código aberto. Abra news.db e você verá todos os artigos em uma visualização semelhante a uma planilha. Você pode filtrar por data, pesquisar conteúdo, exportar para CSV.


O Que Vem a Seguir

Autopublicação no WordPress — um script que lê do banco de dados e publica no WordPress via REST API. Combinado com Claude reescrevendo conteúdo em espanhol, alemão ou polonês, isso se torna um site de notícias multilíngue totalmente automatizado.

Múltiplas fontes — o mesmo servidor e raspador funcionam em qualquer site de notícias. BBC, Reuters, TechCrunch, sites regionais em qualquer idioma — uma única base de código lida com todos eles.

Execução agendada — adicione um trabalho no Agendador de Tarefas do Windows ou cron do Linux para executar node scrape.js todas as manhãs. Acorde com um banco de dados completo das notícias do dia.

Camada de análise — um segundo script lê os artigos de hoje do SQLite, envia-os para a API Claude e gera um briefing matinal: principais histórias, tendências, implicações. O banco de dados se torna um feed de inteligência.


Construído em um dia usando Node.js 23, Gemini 2.5 Flash Lite e better-sqlite3. A parte mais difícil foi descobrir que links de paginação desaparecem quando você remove atributos HTML — por isso extractNextPage() deve rodar no HTML bruto antes de qualquer limpeza.


Baixar Código Fonte

  • fontes — servidor proxy com integração Gemini + raspador de duas fases com armazenamento SQLite
0