Эпопея с калькулятором продолжается. В этот раз мы добавим в него конвертер валют, который в реальном времени собирает данные о курсах с сайта banki.ru и предоставляет информацию в интерфейсе нашего приложения.
Вся работа будет поделена на три шага – мы будем создавать парсер курса валют со стороннего ресурса, компонент для конвертации валют и переключатель для изменения типа данных для конвертации.
Предыдущий материал: Калькулятор-конвертер на базе React. Часть 7: Drag & Drop
Создаем парсер
Парсер – это инструмент, позволяющий собрать данные с сайта и при необходимости отфильтровать их, чтобы выудить конкретный кусок информации с какой-либо страницы. Парсинг можно применять для различных задач. Проверка курса валют – лишь одна из сотен, применяющихся на практике. Парсить можно практически любой контент из сети, где это не запрещается владельцами сайтов и нет специальной защиты, мешающей делать запросы не через обычный браузер.
Чтобы сделать свой парсер, надо сначала выбрать источник данных. Как по мне, сайт banki.ru вполне подходит. Я рекомендую использовать его, потому что алгоритм настроен конкретно под этот ресурс и может не работать на других похожих сайтах.
Перед тем как приступить к написанию кода парсера, я проверил, откуда именно мне нужно будет добывать данные. Для этого надо открыть страницу с информацией и нажать сочетание клавиш Shift + Ctrl + C. Откроются инструменты разработчика и появится возможность выделить любой элемент на странице, чтобы посмотреть его код. Я выбираю блок с названием валюты и вижу, что это компонент таблицы.
Отсутствие внятных классов для названия и суммы усложняет задачу. Придется «генерировать костыли» и кустарными методами пробираться к необходимой информации.
Все они хранятся в единых блоках tr, а это позволит нам настроить относительно несложный алгоритм для поиска необходимого контента на banki.ru и последующего его использования в приложении. Нас интересует название валюты и ее стоимость по отношению к рублю (на текущий момент этого будет достаточно).
Настраиваем axis и cheerio
Если на первом этапе вы по какой-то причине пропустили настройку и установку Axios с Cheerio, то стоит сделать это сейчас. Обе утилиты необходимы, чтобы добыть нужную информацию о странице и после этого отфильтровать необходимые данные.
Axios делает сетевые запросы и может предоставить весь HTML-код по ссылке, а Cheerio умеет этот код обрабатывать, вытаскивая определенные компоненты, ориентируясь на классы, атрибуты или просто содержимое HTML-блоков.
Для установки первого необходимо ввести команду:
npm install axios
Для установки второго:
npm install cheerio
Менять конфигурационные файлы этих утилит не нужно. Параметры по умолчанию нам подходят.
Создаем парсер
Парсер получится простеньким и уложится в одну функцию. Напишем мы ее в отдельном файле parser.js (можете разместить его где удобно, а я пока оставлю в папке src).
Внутри parser.js будет следующий код:
-
Сначала импортируем все необходимые элементы, а именно Cheerio и Axios (здесь используется синтаксис CommonJS, поэтому вместо привычных импортов вы увидите ключевое слово require).
const cheerio = require('cheerio), axios = require('axios')
-
Здесь же укажем ссылку, к которой будет обращаться парсер при попытке найти информацию о курсе валют.
url = 'https://www.banki.ru/products/currency/cb/'
Далее будет код, отвечающий непосредственно за добычу данных. Он довольно специфичный, потому что использует массив для хранения блока с информацией о конкретной валюте. Постараюсь разъяснить его принцип работы максимально доступно.
-
Мы создаем асинхронную функцию getData (это и будет основный метод для запроса информации с banki.ru). Аргументом будет слово currency, через него планируется передавать название валюты.
const getData = (currency) => { }
-
В теле функции создадим пустой массив. В будущем он будет содержать в себе всю информацию о выбранной валюте.
const array = []
-
Затем создаем переменную response, содержащую в себе асинхронный запрос к сайту banki.ru через Axios.
const response = await axios.get(url)
-
Потом создаем переменную с символом $ и в нее добавляем асинхронный запрос к ответу от Axios со стороны Cheerio, чтобы загрузить весь HTML-код в парсер.
const $ = await cheerio.load(response.data)
-
Следующий этап – создание регулярного выражения для поиска названия курса валют среди всех, что указаны на сайте banki.ru. В нашем случае надо найти только название конкретной валюты, переданной в функцию getData в качестве аргумента. То есть регулярным выражением станет слово currency.
const regex = new RegExp(currency)
-
Теперь можно воспользоваться 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()))} })
-
А в завершение работы функции getData вернем третий элемент массива. Почему третий? Потому что это сумма, указанная рядом с названием валюты.
return array[3]
После этого необходимо экспортировать метод getData:
А затем импортировать его же, но уже в конвертере.
Новый компонент для конвертации валют
Для конвертации валют (пока что) понадобится отдельный компонент. Для этого не будем далеко ходить, а скопируем уже имеющийся у нас код. То есть тот, что используется для конвертации метров в сантиметры.
Мы возьмем тот же код и вытащим его в отдельный компонент. Пока что назовем его Money. Главное, не забыть переименовать основные единицы. В нашем случае это «Доллар США» и «Рубли». Надо заменить внутренний текст в option и value в option.
И еще заметьте, что референсы теперь передаются как пропсы, потому что мы вывели уже существующую логику в отдельный компонент. Соответственно, теперь все методы и элементы, связанные с родительским компонентом, должны передаваться как пропсы.
Метод конвертации валют
Дополнительная логика конвертера (та, что не связана с парсингом) прописывается в компоненте converter. Пока мы не вывели все формулы в отдельные функции и файлы, создадим новый метод прямо внутри Converter и назовем его 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.
Только в этом компоненте мы заменим названия переключателей. Вместо Calculator и Converter сделаем Money и Distance.
Далее необходимо дополнить код компонента Converter всеми элементами, отвечающими за переключение режимов работы программы. Во-первых, создаем состояние mode и приравниваем его к Distance по умолчанию:
const [mode, setMode] = useState('Distance')
Затем создаем переменную converter, в которой будет храниться компонент для отображения в интерфейсе:
let converter
И добавляем конструкцию switch, отвечающую за отображаемый в текущий момент режим работы конвертера:
На примере Money разберем пропсы, передаваемые в новый тип компонентов:
-
setInput – это метод изменения значения поля Input внутри компонента Converter.
-
convert – метод конвертации. В случае с Money – это convertMoney, так как способ конвертации отличается от того, что используется при преобразовании расстояний.
-
first и second – это референсы к двум полям Select.
-
data – это массив со списком элементов, которые должны отображаться в меню Select. Об этом поговорим ниже.
Теперь вместо отдельных компонентов Money или Distance мы можем отправить в метод return переменную converter, и уже она будет решать, какой вид конвертера доступен пользователю. Не придется городить десятки видов конвертации на одной странице.
Упрощаем переиспользование компонентов
Теперь подробнее поговорим о том, что такое data и почему вообще эта переменная появилась в списке пропсов, относящихся к компонентам с конвертерами.
data – это массив, в котором по задумке должна содержаться информация о тех метриках, что будут исчисляться внутри конвертера. Благодаря отдельным массивам со списком этих значений можно отказаться от ручного ввода блоков option в каждом компоненте. То есть исключить из кода Money строки:
<option value="Доллар США">Доллар США</option>
Вместо этого создадим массив со всеми возможными значениями для конвертации и передадим его в компонент как пропс.
После можно будет обработать пропс внутри компонента и распределить каждый элемент массива в блоке 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>
Такой подход позволит упростить повторное создание и использование компонентов, так как не придется заново указывать весь список конвертируемых элементов в одном блоке данных.
Таким же образом можно создать массивы data для неограниченного количества метрик и вообще вывести их в отдельный файл, чтобы они не мешались с основной логикой компонента.
Но и это не все, так как нам приходится для каждого вида метрик создавать новый компонент с большим количеством повторяющейся логики. Это замедляет процесс разработки и делает код слишком объемным и неудобным для редактирования. Поэтому надо преобразовать компонент с блоками Select во что-то более универсальное.
Мы оставим в нем тот же код, но сам компонент переименуем в Convertible, чтобы отразить его новую сущность. Он теперь в ответе за любые виды метрик.
Нам остается лишь передавать в этот компонент разные пропсы. Разные методы вычисления, разные массив со списками метрик и т.п.
Если мы захотим изменить дизайн или логику со списком элементов <option>, то нам не придется редактировать десяток разных компонентов, а достаточно будет внести изменения в Convertible. Мы уже рассматривали декомпозицию в четвертой части цикла, и вот она снова во всей красе показывает, как сокращение кода может упростить жизнь разработчика.
И вот вам напоследок страшная картинка с двумя долларами, конвертированными в рубли.
Вместо заключения
Теперь вы знаете, как создать собственный парсер на базе Axios и Cheerio, умеете фильтровать данные, а также декомпозировать интерфейс в наиболее простой вид, чтобы можно было его легко редактировать и дополнять.
Продолжение цикла тут.
Комментарии