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

Как создать универсальный скрейпер новостей с использованием Node.js, Gemini AI и SQLite

Представьте, что каждое утро вы просыпаетесь и находите базу данных, полную сегодняшних новостных статей, уже очищенных, структурированных и готовых для анализа. Без ручного просмотра, без копирования и вставки, без проблем с форматированием. Только свежие данные, ждущие вас.

Именно это мы и создали: универсальный новостной скрейпер, который работает практически на любом новостном сайте, понимает даты на естественном языке в любом формате, автоматически обнаруживает ссылки на пагинацию, останавливается, когда достигает вчерашних статей, и аккуратно сохраняет все в локальной базе данных SQLite.

Ключевая идея заключается в использовании ИИ не как чат-бота, а как интеллектуального движка для парсинга. Вместо написания хрупких CSS-селекторов, которые ломаются каждый раз при редизайне сайта, мы позволяем Gemini читать страницу как человек и извлекать то, что важно.


Обзор архитектуры

Система состоит из трех компонентов, работающих локально на вашем компьютере:

Прокси-сервер (порт 3001) — HTTP-сервер на Node.js с двумя конечными точками. Конечная точка /fetch загружает любой URL, удаляет скрипты и стили, сохраняет структурные HTML-теги, извлекает ссылки на пагинацию из необработанного HTML до усечения и возвращает чистый структурированный контент. Конечная точка /ai получает содержимое страницы и подсказку, отправляет их в API Gemini с автоматическим повтором при ошибках ограничения скорости и возвращает ответ модели.

Скрипт скрейпера — скрипт на Node.js, который оркестрирует все. Он работает в два этапа: Этап 1 собирает все ссылки на статьи с листинговых страниц, автоматически следуя пагинации, Этап 2 посещает каждый URL статьи, извлекает полный контент через Gemini и сохраняет в базу данных.

База данных SQLite — локальная однофайловая база данных, хранящая статьи с полями для URL (уникальный, предотвращает дублирование), заголовка, контента, даты, источника и временной метки создания.

[Любой новостной сайт]
       ↓ HTTP-запрос (заголовки, как у браузера)
[Прокси-сервер — server.js :3001]
       ↓ чистый HTML + ссылка на пагинацию
[Gemini 2.5 Flash Lite API]
       ↓ структурированный JSON
[scrape.js]
       ↓ INSERT OR IGNORE
[news.db — SQLite]

Почему Gemini, а не локальная модель?

Мы тестировали локальные модели (Llama 3.2, Mistral 7B через Ollama) для этой задачи. Результаты были разочаровывающими — маленькие модели, работающие на CPU, слишком медленны для обработки сотен страниц, они придумывают несуществующие ссылки на пагинацию и испытывают трудности с различением основного контента от виджетов боковой панели, когда структура страницы сложна.

Gemini 2.5 Flash Lite решает все эти проблемы. Он обрабатывает полную очищенную страницу за 1–2 секунды, правильно идентифицирует даты статей на любом языке и в любом формате, понимает семантическую структуру HTML и стоит практически ничего — примерно €0.55 за 200 статей.


Шаг 1: Получите ваш ключ API Gemini

Перейдите на aistudio.google.com/api-keys и создайте новый ключ. Бесплатный уровень предоставляет 20 запросов в минуту, что достаточно для разработки. Для производственного скрейпинга добавьте способ оплаты — стоимость примерно €0.25 в месяц при типичном ежедневном использовании.

Вставьте ваш ключ в server.js на строке 10:

const GEMINI_KEY = 'your_key_here';

Шаг 2: Прокси-сервер (server.js)

Прокси-сервер — это сердце системы. Его самая важная задача — извлечение ссылки на следующую страницу до удаления атрибутов HTML — тонкая, но важная деталь.

Когда мы очищаем HTML, мы удаляем атрибуты, такие как rel="next" и aria-label, чтобы уменьшить размер контента для ИИ. Но именно эти атрибуты идентифицируют ссылку на пагинацию. Поэтому мы вызываем extractNextPage() сначала на необработанном HTML, а затем очищаем все остальное.

Функция ищет три шаблона, которые охватывают подавляющее большинство новостных сайтов:

  • rel="next" — стандартный атрибут пагинации HTML5
  • aria-label, содержащий “next”, “далі”, “вперед”, “следующая”
  • Элементы с классом is-next — распространенные в CSS-фреймворках
// server.js
// [ВСТАВЬТЕ ПОЛНЫЙ КОД server.js ЗДЕСЬ]

Функция cleanHtml() сохраняет структурные теги (<main>, <article>, <section>, <aside>, <nav>, <time datetime>), удаляя все остальное. Это дает Gemini достаточно контекста, чтобы отличить основной контент от боковых панелей без обработки мегабайтов необработанного HTML.

Функция askGemini() считывает фактическое время ожидания из ответа об ошибке ограничения скорости Gemini (retry in 23.9s) и ждет ровно столько времени перед повторной попыткой. Без догадок, без фиксированных задержек.


Шаг 3: Установите зависимости и запустите сервер

cd your-project-folder
npm install better-sqlite3
node server.js

Вы должны увидеть:

Сервер запущен на http://localhost:3001

Проверьте его, открыв этот URL в вашем браузере:

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

Вы получите JSON-ответ с полями text, links и nextPage.


Шаг 4: Умное обнаружение пагинации

Скрейпер никогда не имеет жестко закодированного списка URL страниц. Он начинает с одного URL и автоматически следует за ссылкой “следующая страница” на каждой странице.

Разные сайты используют совершенно разные шаблоны пагинации:

  • https://site.com/news/page/2 — стиль WordPress
  • https://site.com/news/page-2 — разделитель тире (TSN.ua как пример для статьи)
  • https://site.com/news?page=2 — параметр запроса
  • https://site.com/news-2.html — плоский HTML-стиль

Прокси обрабатывает все из них, потому что ищет семантические атрибуты в HTML, а не шаблоны URL. Скрейпер просто читает pageData.nextPage из ответа прокси и следует за ним вслепую.


Шаг 5: Фильтрация дат с помощью ИИ

Здесь подход действительно превосходит традиционные скрейперы. Новостные сайты отображают даты в самых разных форматах:

  • <time datetime="2026-04-21T17:30:00+03:00"> — стандартный HTML5
  • 21 квітня 2026 — украинская длинная форма
  • Сьогодні, 17:30 — “Сегодня” на украинском
  • Просто 17:30 — только время, подразумевая сегодня

Традиционному скрейперу понадобилась бы индивидуальная логика парсинга для каждого формата на каждом сайте. Мы просто говорим Gemini сегодняшнюю дату и просим его отфильтровать:

Сегодня 2026-04-21.

Извлеките ссылки на новостные статьи из основной области контента, опубликованные ТОЛЬКО СЕГОДНЯ.
Даты могут быть в любом формате: <time datetime>, "21.04.26", "17:30" (только время = сегодня), "сьогодні" и т.д.
ИСКЛЮЧИТЬ: боковые панели, "читайте также", рекламу, навигацию, страницы категорий/тегов/авторов.
Установите "stop": true, если вы видите статьи из ВЧЕРА или ранее в основном контенте.

Возвращайте ТОЛЬКО допустимый JSON:
{"articles":[{"title":"...","url":"https://..."}],"stop":false,"reason":"..."}

Флаг stop — это ключевой механизм. Когда Gemini видит вчерашние статьи в основном контенте, он возвращает "stop": true, и скрейпер прекращает следовать пагинации — независимо от того, на каком номере страницы мы находимся.


Шаг 6: Извлечение контента статьи

Этап 2 посещает каждый URL статьи и извлекает фактический контент. Подсказка намеренно минимальна:

Извлеките следующее из этой страницы новостной статьи:
1. "title" — заголовок статьи
2. "date" — дата публикации в формате YYYY-MM-DD
3. "content" — полный текст статьи, чистый, без рекламы или навигации

Возвращайте ТОЛЬКО допустимый JSON без объяснений, без разметки, без текста до или после:
{"title":"...","date":"2026-04-21","content":"..."}

Обратите внимание, что поля для резюме нет. Соблазнительно генерировать резюме во время скрейпинга, но это тратит токены и время на то, что вы можете никогда не использовать. Сохраняйте необработанный контент сейчас, генерируйте резюме по требованию, когда они действительно понадобятся для анализа.


Шаг 7: Скрипт скрейпера (scrape.js)

// scrape.js
// [ВСТАВЬТЕ ПОЛНЫЙ КОД scrape.js ЗДЕСЬ]

Оператор SQL INSERT OR IGNORE в сочетании с ограничением UNIQUE на столбце url автоматически обрабатывает все дублирования. Запускайте скрейпер каждый час — он молча пропускает любую статью, уже находящуюся в базе данных.


Запуск

Вам понадобятся два окна терминала:

Терминал 1 — прокси-сервер (держите запущенным):

node server.js

Терминал 2 — скрейпер (запускайте, когда хотите свежие статьи):

node scrape.js

Чтобы скрейпить другой новостной сайт, измените только одну строку в начале scrape.js:

const START = 'https://your-news-site.com/news';

Результаты

Запуск против TSN.ua (крупный украинский новостной сайт, как пример для статьи) 21 апреля 2026 года:

  • Этап 1: собрано 198 ссылок на статьи с 20 страниц — остановлено автоматически, когда появились вчерашние статьи
  • Этап 2: 198 статей извлечено, очищено и сохранено
  • Общее время: ~11 минут
  • Общая стоимость: €0.55 в кредитах API Gemini
  • Размер базы данных: ~8MB

Скрейпер правильно игнорировал виджеты боковой панели со старыми популярными статьями, гороскопами, прогнозами погоды и страницами с курсами валют. Только фактические новостные статьи, опубликованные в тот день, попали в базу данных.


Просмотр базы данных

Скачайте DB Browser for SQLite — бесплатно, с открытым исходным кодом. Откройте news.db, и вы увидите все статьи в виде, похожем на электронную таблицу. Вы можете фильтровать по дате, искать контент, экспортировать в CSV.


Что дальше

Автопубликация в WordPress — скрипт, который читает из базы данных и публикует в WordPress через REST API. В сочетании с переписыванием контента на испанский, немецкий или польский с помощью Claude, это становится полностью автоматизированным многоязычным новостным сайтом.

Несколько источников — тот же сервер и скрейпер работают на любом новостном сайте. BBC, Reuters, TechCrunch, региональные сайты на любом языке — одна кодовая база обрабатывает их все.

Запланированное выполнение — добавьте задание в Планировщик задач Windows или cron в Linux, чтобы запускать node scrape.js каждое утро. Просыпайтесь с полной базой данных сегодняшних новостей.

Аналитический слой — второй скрипт читает сегодняшние статьи из SQLite, отправляет их в API Claude и генерирует утренний брифинг: ключевые истории, тенденции, последствия. База данных становится источником информации.


Создано за один день с использованием Node.js 23, Gemini 2.5 Flash Lite и better-sqlite3. Самой сложной частью было обнаружение, что ссылки на пагинацию исчезают, когда вы удаляете атрибуты HTML — поэтому extractNextPage() должен выполняться на необработанном HTML до любой очистки.


Скачать исходный код

  • исходники — прокси-сервер с интеграцией Gemini + двухфазный скрейпер с хранением в SQLite
1