CoderCastrov logo
CoderCastrov
Парсер

Парсинг и визуализация данных с помощью Cheerio и Prometheus

Парсинг и визуализация данных с помощью Cheerio и Prometheus
просмотров
6 мин чтение
#Парсер

Как использовать Cheerio, node-cron и Prometheus для парсинга данных с веб-сайта и их визуализации со временем.

Введение

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

Я не могу показать точный проект, над которым я работал, поэтому здесь я буду использовать цены на акции. Финансовые данные, очевидно, хорошо обслуживаются API, поэтому парсинг обычно не требуется, но это хороший пример полезных числовых данных с веб-сайта, которые часто меняются и могут быть спарсены. Вы можете использовать эту настройку для отслеживания доступности товаров, получения последних цен на электронной коммерции или туристических веб-сайтах и многого другого.


План проекта

Предположим, что я хочу получить значение FTSE 100 и отслеживать его со временем. Мне понадобится способ получить данные с веб-сайта (парсинг), затем некоторый метод для получения данных периодически (запланированная задача) и место для хранения этих данных, чтобы я мог визуализировать их.

Этот проект написан на TypeScript и использует Prettier, ESlint, ts-node и nodemon для удобства разработки. Полный код проекта можно найти на Github.


Начало работы с Cheerio

Для парсинга сайта я буду использовать Cheerio, который описывается как: "Быстрая, гибкая и элегантная библиотека для разбора и манипулирования HTML и XML". Это широко используемая библиотека для парсинга данных с веб-сайтов, и документация была полезной и легко понятной.

Я буду использовать Hargreaves Lansdown для данных, так как их сайт отрисовывается на сервере - стоит отметить, что Cheerio плохо работает с сайтами, отрисовываемыми на клиентской стороне!

Я могу использовать axios для выполнения запроса на целевой веб-сайт, а затем передать его в Cheerio, чтобы получить HTML страницы, поэтому давайте установим эти зависимости:

yarn add cheerio axios

Мой первый шаг - написать простую функцию для получения данных с сайта HL и вывести их в консоль, чтобы убедиться, что она работает. Я могу деструктурировать data из вызова axios.get() и затем получить структурированную версию HTML с помощью метода load из cheerio:

const { data } = await axios.get('https://www.hl.co.uk/.../ftse-100');

const $ = cheerio.load(data);

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

Чтобы получить эту информацию, щелкните правой кнопкой мыши на элементе и выберите "Инспектировать". Здесь я могу использовать идентификатор span #indices-val-UKX, чтобы выбрать его.

Вы также можете скопировать селектор, щелкнув правой кнопкой мыши на элементе в панели инструментов разработчика, перейдя в "Копировать", затем "Копировать селектор".

Затем я могу подставить селектор идентификатора в Cheerio и использовать метод .text(), чтобы получить содержимое элемента span - значение FTSE 100 в данном случае. Мне нужно очистить эту строку (включая запятую) и преобразовать ее в число с двумя десятичными знаками перед выводом в консоль:

import * as cheerio from 'cheerio';
import axios from 'axios';

async function scrape(): Promise<void> {
  try {
    const { data } = await axios.get('https://www.hl.co.uk/.../ftse-100');

    const $ = cheerio.load(data);
    
    const ftse100StringValue = $('#indices-val-UKX').text();
    
    const ftse100NumberValue = parseFloat(ftse100StringValue.replace(',', ''));
    
    console.log(`Значение FTSE 100: ${ftse100NumberValue}`);

  } catch (error) {
    console.error(error);
  }
}

scrape()

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

Логирование метрик в Prometheus

Далее я хочу записывать данные в Prometheus, чтобы я мог визуализировать данные со временем. В будущем я также могу добавить больше метрик (таких как индекс DOW, NASDAQ или конкретная цена акции).

Я буду использовать prom-client, который является "клиентом Prometheus для Node.js, поддерживающим гистограммы, суммары, меры и счетчики".

yarn add prom-client

Сначала мне нужно определить метрики, которые я хочу собирать, а затем зарегистрировать их в Prometheus. В этом случае я буду использовать "метку" - единственное значение, которое будет меняться (как вверх, так и вниз) со временем.

import client, { Registry } from 'prom-client';

export function config(): Registry {
  // Создаем реестр, который будет регистрировать метрики
  const register = new client.Registry();

  // Добавляем метку по умолчанию, которая будет добавляться ко всем метрикам
  register.setDefaultLabels({
    app: 'site-scraper',
  });

  // Регистрируем метрику
  register.registerMetric(
    new client.Gauge({
      name: 'ftse_100_value',
      help: 'Значение индекса FTSE 100',
      labelNames: ['index_value', 'index_name'],
    }),
  );

  return register;
}

Я должен убедиться, что эта функция конфигурации запускается каждый раз при запуске приложения, поэтому я вызову ее из файла index.ts, но перед этим мне нужно настроить конечную точку для отображения метрик в пользовательском интерфейсе Prometheus.

Я могу использовать Docker для запуска экземпляра Prometheus и открытия доступа к метрикам на определенном порту (localhost:8080/metrics в данном случае) и визуализации их на localhost:9090. Здесь немного сложно показать все в этой статье, поэтому ознакомьтесь с файлами prometheus.yml, Dockerfile, docker-compose.yml и server.ts в репозитории GitHub.

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

import * as dotenv from 'dotenv';
import axios from 'axios';
import * as cheerio from 'cheerio';
import { config } from './prometheus-config';
import { startServer } from './server';

dotenv.config();
const prometheusRegister = config();
startServer(prometheusRegister);

async function scrape(metricsRegister): Promise<void> {
  try {
    const { data } = await axios.get('https://www.hl.co.uk/.../ftse-100');

    const $ = cheerio.load(data);

    const ftse100StringValue = $('#indices-val-UKX').text();

    const ftse100NumberValue = parseFloat(ftse100StringValue.replace(',', ''));

    const ftse100Metric = await metricsRegister._metrics.ftse_100_value;

    ftse100Metric.set({ index_name: 'ftse_100' }, ftse100NumberValue);

  } catch (error) {
    console.error(error);
  }
}

scrape(prometheusRegister)

Теперь я могу видеть метрики в Prometheus, но я получаю данные только один раз (при первом запуске приложения), поэтому мне нужно настроить периодическую загрузку данных с помощью cron-задачи.


Настройка cron-задачи

Cron-задача позволяет запускать определенную функцию либо в определенное время, либо по расписанию. Я буду использовать node-cron, который является "небольшим планировщиком задач на чистом JavaScript для node.js".

yarn add node-cron

Мы хотим, чтобы функция parse запускалась по расписанию, поэтому вместо вызова ее в конце файла index.ts, мы можем заменить это на:

cron.schedule(process.env.CRON_SCHEDULE, () => parse(prometheusRegister));

CRON_SCHEDULE устанавливается в переменной среды. Я предлагаю использовать crontab.guru, чтобы получить нужное значение для нужного вам расписания. Например, * * * * * будет запускать функцию каждую минуту, /10 * * * * будет запускать ее раз в 10 минут, а 0 0 * * * будет запускать в полночь каждый день.

Со всеми этими элементами вместе мы теперь можем видеть значение FTSE 100, которое регистрируется в prometheus со временем:


Заключение

В этой статье мы использовали Cheerio, node-cron и Prometheus для получения и визуализации данных со временем. Это простой пример, но существует множество других способов и сценариев, в которых можно использовать эти инструменты.

Я использовал Cheerio, чтобы получить только одну точку данных здесь, но его можно использовать для получения нескольких точек данных на странице, или даже для перебора списка элементов и получения заданного элемента (например, цены) из каждого элемента. Также может быть сложно использовать Cheerio, когда сайт рендерится на стороне клиента, но можно обойти эту проблему, извлекая данные JSON из вызова API на странице.

Вы можете использовать Grafana для визуализации данных более наглядным и удобным способом. Prometheus также может регистрировать другие типы данных, такие как счетчики (для измерения накопительных данных) и гистограммы (для группировки данных в настраиваемые корзины).

Надеюсь, вам была полезна эта статья, и полный код для этого проекта можно найти на GitHub. 🙂