Создаем калькулятор-конвертер на базе React. Часть 8: Конвертер валют и собственный парсер данных

Обсудить
Делаем калькулятор-конвертер на базе React. Часть 8: Конвертер валют и собственный парсер данных

Эпопея с калькулятором продолжается. В этот раз мы добавим в него конвертер валют, который в реальном времени собирает данные о курсах с сайта banki.ru и предоставляет информацию в интерфейсе нашего приложения.

Вся работа будет поделена на три шага – мы будем создавать парсер курса валют со стороннего ресурса, компонент для конвертации валют и переключатель для изменения типа данных для конвертации. 

Предыдущий материал: Калькулятор-конвертер на базе React. Часть 7: Drag & Drop

Создаем парсер

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

Чтобы сделать свой парсер, надо сначала выбрать источник данных. Как по мне, сайт banki.ru вполне подходит. Я рекомендую использовать его, потому что алгоритм настроен конкретно под этот ресурс и может не работать на других похожих сайтах. 

Сайт banki.ru

Перед тем как приступить к написанию кода парсера, я проверил, откуда именно мне нужно будет добывать данные. Для этого надо открыть страницу с информацией и нажать сочетание клавиш Shift + Ctrl + C. Откроются инструменты разработчика и появится возможность выделить любой элемент на странице, чтобы посмотреть его код. Я выбираю блок с названием валюты и вижу, что это компонент таблицы.

Инструменты разработчика в Google Chrome

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

HTML-код на странице с курсами валют

Все они хранятся в единых блоках tr, а это позволит нам настроить относительно несложный алгоритм для поиска необходимого контента на banki.ru и последующего его использования в приложении. Нас интересует название валюты и ее стоимость по отношению к рублю (на текущий момент этого будет достаточно).

Настраиваем axis и cheerio

Если на первом этапе вы по какой-то причине пропустили настройку и установку Axios с Cheerio, то стоит сделать это сейчас. Обе утилиты необходимы, чтобы добыть нужную информацию о странице и после этого отфильтровать необходимые данные. 

Axios делает сетевые запросы и может предоставить весь HTML-код по ссылке, а Cheerio умеет этот код обрабатывать, вытаскивая определенные компоненты, ориентируясь на классы, атрибуты или просто содержимое HTML-блоков. 

Для установки первого необходимо ввести команду: 

npm install axios

Для установки второго:

npm install cheerio

Менять конфигурационные файлы этих утилит не нужно. Параметры по умолчанию нам подходят. 

Создаем парсер

Парсер получится простеньким и уложится в одну функцию. Напишем мы ее в отдельном файле parser.js (можете разместить его где удобно, а я пока оставлю в папке src).

Внутри parser.js будет следующий код:

Код в файле parser.js

  • Сначала импортируем все необходимые элементы, а именно Cheerio и Axios (здесь используется синтаксис CommonJS, поэтому вместо привычных импортов вы увидите ключевое слово require). 

    const cheerio = require('cheerio), axios = require('axios')
  • Здесь же укажем ссылку, к которой будет обращаться парсер при попытке найти информацию о курсе валют. 

    url = 'https://www.banki.ru/products/currency/cb/'

Далее будет код, отвечающий непосредственно за добычу данных. Он довольно специфичный, потому что использует массив для хранения блока с информацией о конкретной валюте. Постараюсь разъяснить его принцип работы максимально доступно.

  1. Мы создаем асинхронную функцию getData (это и будет основный метод для запроса информации с banki.ru). Аргументом будет слово currency, через него планируется передавать название валюты. 

    const getData = (currency) => { }
  2. В теле функции создадим пустой массив. В будущем он будет содержать в себе всю информацию о выбранной валюте. 

    const array = []
  3. Затем создаем переменную response, содержащую в себе асинхронный запрос к сайту banki.ru через Axios. 

    const response = await axios.get(url)
  4. Потом создаем переменную с символом $ и в нее добавляем асинхронный запрос к ответу от Axios со стороны Cheerio, чтобы загрузить весь HTML-код в парсер. 

    const $ = await cheerio.load(response.data)
  5. Следующий этап – создание регулярного выражения для поиска названия курса валют среди всех, что указаны на сайте banki.ru. В нашем случае надо найти только название конкретной валюты, переданной в функцию getData в качестве аргумента. То есть регулярным выражением станет слово currency. 

    const regex = new RegExp(currency)
  6. Теперь можно воспользоваться Cheerio. Сначала мы подгрузим весь список элементов tr в парсер и начнем перебор. С помощью метода each обратимся ко всем элементам с тегом tr. Если в тексте хотя бы одного из них найдется название выбранной валюты (проверка if), то то мы добавим весь HTML-код этого tr-блока в наш массив. 

    $('tr').each((e, i) => {if ($(i).text().match(regex)) {$(i).children().each((e, x) => array.push($(x).html()))} })
  7. А в завершение работы функции getData вернем третий элемент массива. Почему третий? Потому что это сумма, указанная рядом с названием валюты. 

    return array[3]

После этого необходимо экспортировать метод getData:

Экспорт метода getData

А затем импортировать его же, но уже в конвертере.

Импорт метода getData

Комьюнити теперь в Телеграм
Подпишитесь и будьте в курсе последних IT-новостей
Подписаться

Новый компонент для конвертации валют

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

Код компонента-конвертера

Мы возьмем тот же код и вытащим его в отдельный компонент. Пока что назовем его Money. Главное, не забыть переименовать основные единицы. В нашем случае это «Доллар США» и «Рубли». Надо заменить внутренний текст в option и value в option.

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

Измененный код компонента Money

Метод конвертации валют

Дополнительная логика конвертера (та, что не связана с парсингом) прописывается в компоненте converter. Пока мы не вывели все формулы в отдельные функции и файлы, создадим новый метод прямо внутри Converter и назовем его convertMoney. Это будет асинхронная функция для расчета курса валют на основе информации из парсера. 

Функция convertMoney

  • Объявляем сам метод. 

    async function convertMoney() { }
  • В его теле делаем асинхронный запрос к функции getData и в качестве аргумента передаем значение первого референса (про референсы написано в седьмой части цикла). Для примера это будет «Доллар США». 

    getData(first.current.value).then()
  • При успешном выполнении запроса мы будем обращаться к конструкции switch, которая проверит валюту во втором референсе и на ее основе сделает расчет. Изменим значения состояния result:x => { case 'Рубли': setResult(input * Number(x)); break }

Тут стоит отметить, что мы делаем каст (преобразование типа данных) значения x в Number, потому что по умолчанию метод getData возвращает нам строку, а не число. Этого поведения можно избежать, если добавить каст прямо внутри getData. Тогда не придется писать лишний код в convertMoney. Смотрите, как вам удобнее.

Готово. У нас есть парсер, метод вычисления курса и даже примитивный интерфейс для отслеживания работы конвертера. Проблема только в том, что он не подключен к компоненту Converter. Но мы это исправим. 

Создаем меню для переключения между разными конвертерами

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

Для начала создадим компонент Menu в файле Converter.jsx и полностью скопируем туда код из того Menu, что находится в файле App.jsx. 

Компонент Menu в конвертере

Только в этом компоненте мы заменим названия переключателей. Вместо Calculator и Converter сделаем Money и Distance. 

Кнопки переключения режимов конвертации

Далее необходимо дополнить код компонента Converter всеми элементами, отвечающими за переключение режимов работы программы. Во-первых, создаем состояние mode и приравниваем его к Distance по умолчанию: 

const [mode, setMode] = useState('Distance')

Состояние mode в компоненте Converter

Затем создаем переменную converter, в которой будет храниться компонент для отображения в интерфейсе:

let converter

И добавляем конструкцию switch, отвечающую за отображаемый в текущий момент режим работы конвертера: 

Конструкция switch для переключения видов конвертации

На примере Money разберем пропсы, передаваемые в новый тип компонентов:

  1. setInput – это метод изменения значения поля Input внутри компонента Converter. 

  2. convert – метод конвертации. В случае с Money – это convertMoney, так как способ конвертации отличается от того, что используется при преобразовании расстояний. 

  3. first и second – это референсы к двум полям Select. 

  4. data – это массив со списком элементов, которые должны отображаться в меню Select. Об этом поговорим ниже. 

Переменная converter в методе return

Теперь вместо отдельных компонентов Money или Distance мы можем отправить в метод return переменную converter, и уже она будет решать, какой вид конвертера доступен пользователю. Не придется городить десятки видов конвертации на одной странице. 

Упрощаем переиспользование компонентов

Теперь подробнее поговорим о том, что такое data и почему вообще эта переменная появилась в списке пропсов, относящихся к компонентам с конвертерами. 

data – это массив, в котором по задумке должна содержаться информация о тех метриках, что будут исчисляться внутри конвертера. Благодаря отдельным массивам со списком этих значений можно отказаться от ручного ввода блоков option в каждом компоненте. То есть исключить из кода Money строки: 

<option value="Доллар США">Доллар США</option>

Массив data

Вместо этого создадим массив со всеми возможными значениями для конвертации и передадим его в компонент как пропс. 

Компонент Money с пропсом data

После можно будет обработать пропс внутри компонента и распределить каждый элемент массива в блоке Select так, чтобы он отображался в качестве блока <option>. 

Чтобы это реализовать, создадим переменную selection и в ней запишем функцию перебора массива data. 

const selection = props.data.map(e => {return <option key={e} value={e}>{e}</option>})

И получившуюся переменную отправляем в код между тегами Select. 

<Select ref={props.first}> {selection} </Select>

Компонент Money

Такой подход позволит упростить повторное создание и использование компонентов, так как не придется заново указывать весь список конвертируемых элементов в одном блоке данных. 

Массивы data

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

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

Мы оставим в нем тот же код, но сам компонент переименуем в Convertible, чтобы отразить его новую сущность. Он теперь в ответе за любые виды метрик. 

Универсальный компонент

Нам остается лишь передавать в этот компонент разные пропсы. Разные методы вычисления, разные массив со списками метрик и т.п.

Обновленная конструкция switch

Если мы захотим изменить дизайн или логику со списком элементов <option>, то нам не придется редактировать десяток разных компонентов, а достаточно будет внести изменения в Convertible. Мы уже рассматривали декомпозицию в четвертой части цикла, и вот она снова во всей красе показывает, как сокращение кода может упростить жизнь разработчика.

И вот вам напоследок страшная картинка с двумя долларами, конвертированными в рубли. 

Интерфейс конвертера валют

Вместо заключения

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

Продолжение цикла тут

Hello World! Гайды и обзоры для девелоперов разных мастей.

Комментарии

Ваш баланс 10 ТК
1 ТК = 1 ₽
О том, как заработать и потратить Таймкарму, читайте в этой статье
Чтобы потратить Таймкарму, зарегистрируйтесь на нашем сайте
С помощью соцсетей
У меня нет аккаунта Зарегистрироваться
Нажимая кнопку «Зарегистрироваться», я даю согласие на обработку своих персональных данных, указанных в форме регистрации.
С помощью соцсетей
У меня уже есть аккаунт Войти
Нажимая кнопку «Зарегистрироваться», я даю согласие на обработку своих персональных данных, указанных в форме регистрации.
Инструкции по восстановлению пароля высланы на Ваш адрес электронной почты.
Пожалуйста, укажите email вашего аккаунта
Войти в Комьюнити
Регистрация в Комьюнити
Восстановление пароля