Источник: Nuances of Programming
я пандемии я создал аккаунт на eToro и начал разбираться с тем, как можно получить акции Rolex, Rolls Royce и Rover. Наблюдая за профессионалами на YouTube, я наткнулся на скринер акций FINVIZ. С помощью этой платформы можно отсортировывать более 8 000 акций на основе различных технических факторов, что помогает лучше планировать сделки.
Однако на eToro недоступны некоторые акции. Чтобы удостовериться в их наличии, я обычно копировал тикер, заходил в поиск на eToro и вставлял его в поле. Если Finviz выдает список из 100 акций, то это действие приходится повторять 100 раз.
Конечно, нужно было найти лучший способ. Вместо того чтобы искать инструмент, который выполнял бы эти задачи, я решил создать собственный с нуля.
Объем работы
- Создать расширение для Chrome, которое считывает тикеры акций из FINVIZ и передает их на бэкенд.
- Использовать Laravel на бэкенде для проверки значений в базе данных и возврата булевого значения для каждого тикера.
- Найти обходной путь для получения данных из eToro, так как, похоже, у сервиса нет официального API.
План действий
- Написать беспорядочный код. Чем беспорядочнее, тем лучше. Я создаю код в кратчайшие сроки. Я пишу его для себя, поэтому считаю, что в этом случае код не должен быть правильно структурирован. Он просто должен работать. Кроме того, держитесь подальше от конструкторов модулей. Мне жалко тратить целый день на то, чтобы разбираться в настройках Webpack.
- Ограничить скрейпинг тикеров вкладкой “Обзор” (Overview) на FINVIZ для проверки работы.
- Придумать, как включить в расширение функцию оплаты/выставления счетов.
- Расширить функционал инструмента и на CoinGecko. Об этом можно подумать позже.
- Придумать название для расширения, подобрать иконку и т. д. И об этим деталях можно позаботиться позднее.
Написание кода
Установка и настройка проекта
Google предлагает подробное руководство по началу работы. В нем также можно найти ссылку на готовый пример (кодовую базу). Для начала можно просто загрузить его и начать экспериментировать с кодом.
После скачивания кодовой базы вы можете загрузить ее как распакованное расширение через Chrome. Шаги описаны в указанном выше руководстве.
Разбираемся в кодовой базе
Согласно документации, основными файлами являются следующие.
- manifest.json — сюда заносятся все метаданные, разрешения и т. д.
- background.js обрабатывает события, которые важны для самого расширения. Здесь находится логика обработки различных событий жизненного цикла.
- popup.html — слой пользовательского интерфейса (UI). Сюда помещена вся логика HTML и CSS для элементов UI. Popup.html также имеет соответствующий JS-файл, который обрабатывает логику для манипулирования UI-слоем.
- popup.js — JS-файл для обработки событий из popup.html. Также допускается внедрение скриптов контента и т. д.
Для начала перейдите к файлу manifest.json и измените его название и описание.
- manifest.json:
{
"name": "Finviz X eToro extension",
"description": "Show if the ticker exists on eToro",
//
}
Обратите внимание: каждый раз, когда вы вносите изменения в manifest.json, нужно переходить в chrome://extensions и перезагружать расширение.
Далее я перешел к файлу popup.html, добавил стили в тело файла и элемент h2. Изменения отразились мгновенно.
Кроме того, в popup.html есть зеленая кнопка, и у нее есть соответствующее событие click в файле popup.js, которое передается методу setPageBackgroundColor.
Не будем менять много элементов — просто добавим console.log в каждый метод и посмотрим, как это выглядит в Chrome DevTools.
- popup.js:
let changeColor = document.getElementById("changeColor");
// Когда кнопка нажата, вводим setPageBackgroundColor в текущую страницу
changeColor.addEventListener("click", async () => {
let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
// строка, которую я добавил в
console.log('button clicked');
chrome.scripting.executeScript({
target: { tabId: tab.id },
function: setPageBackgroundColor,
});
});
// Тело этой функции будет выполнено как скрипт контента внутри
// текущей страницы
function setPageBackgroundColor() {
/ строка, которую я добавил в
console.log('setPageBackgroundColor method called.');
chrome.storage.sync.get("color", ({ color }) => {
document.body.style.backgroundColor = color;
});
}
Результат:
Любой код, который мы пишем внутри слушателя событий кнопки, отображается в инструментах разработки для расширения, а код в методе setPageBackgroundColor выполняется в инструментах разработки для браузера (страница FINVIZ).
Обе среды должны быть изолированы.
Каждый раз, когда нажимается зеленая кнопка, мы будем получать тикеры со страницы FINVIZ, и console.log будет выводить их в setPageBackgroundColor. Пока что необходимо вместить как можно больше логики в сам метод.
На данном этапе не нужно даже переименовывать метод, мы займемся этим в конце.
Скрейпинг тикеров с FINVIZ
Это, вероятно, самая простая часть работы. Достаточно лишь проверить элемент таблицы в HTML и получить класс. Потом выполнить поиск querySelectorAll и отобразить внутренний HTML.
let tickers = Array.from(document.querySelectorAll('.screener-link-primary'))
.map(function (tickerHtml) {
return tickerHtml.innerHTML;
})
console.log(tickers);
Отображение тикеров в виде списка в расширении (popup.html)
Для успешного выполнения этого шага сначала нужно понять, как устроены расширения Chrome. Для этого можно воспользоваться следующими ссылками.
Дело в том, что код, который написан непосредственно для самого расширения, и код, который “внедряется” в текущую страницу, полностью изолированы друг от друга. Поэтому для обмена данными между ними необходимо использовать Messaging API.
Я поступил следующим образом.
- Скопировал раздел c hrome.runtime.sendMessage из указанного выше руководства по передаче сообщений (Message Passing) и добавил его в раздел скрипта контента (метод setPageBackgroundColor).
- Добавил соответствующую часть chrome.runtime.onMessage.addListener в нижней части popup.js (также скопировал из руководства по передаче сообщений).
Все получилось!
- Метод setPageBackgroundColor, или раздел скрипта контента:
function setPageBackgroundColor() {
let tickers = Array.from(document.querySelectorAll('.screener-link-primary'))
.map(function (tickerHtml) {
return tickerHtml.innerHTML;
})
console.log(tickers);
chrome.runtime.sendMessage({
identifier: "finvizOverviewTab",
tickers: tickers
}, function (response) {
console.log(response.farewell);
});
}
- popup.js (в нижней части файла):
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (request.identifier === "finvizOverviewTab") {
console.log('tickers', request.tickers);
// поработайте с DOM
}
sendResponse({ farewell: "goodbye" });
}
);
Результат:
Отображение тикера на расширении (ошибка)
Мне казалось, что все работает отлично. Я попытался отобразить тикер на самом расширении и в инструментах разработчика, и вдруг выскочила ошибка.
Сначала меня это озадачило, ведь в файле manifest.json есть разрешение activeTab, и я посчитал, что проблем не будет. Однако оказалось, что не все так просто.
Ошибка:
Uncaught (in promise) Error: Cannot access contents of the page. Extension manifest must request permission to access the respective host.
Решение:
Оказывается, чтобы все работало, нужен ключ host_permissions в файле manifest.json.
"host_permissions": [
"*://finviz.com/"
],
Похоже, это решило проблему.
Результат:
Первая половина работы сделана. Теперь нужно разобраться с бэкендом.
Получение данных из бэкенда
На этом этапе мне оставалось только передать список тикеров со страницы на бэкенд через HTTP-вызов, получить ответ JSON и обработать его соответствующим образом.
Для простоты я преобразовал массив тикеров в строку следующим образом:
let tickerString = tickers.join('|');
Затем я добавил его к URL. Я использовал Fetch API для выполнения HTTP-вызова на бэкенд:
let results = await fetch('<http://example-backend.test/check-tickers-status?tickers=>' + tickerString)
.then(response => response.json())
.then(response => response.data);
console.log(results);
Затем бэкенд вернул объект со всеми тикерами и соответствующим булевым значением. Я просто отобразил эти значения во всплывающем окне.
Результат:
Выделение текста на экране
Теперь тикеры, которые находятся на eToro, отображаются в самом всплывающем окне расширения. Однако мне также хотелось выделить элементы на странице FINVIZ.
Я скопировал фрагмент кода из руководства по передаче сообщений и отредактировал его следующим образом:
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
chrome.tabs.sendMessage(tabs[0].id, {
'identifier': 'tickerResults',
'results': results
}, function (response) {
console.log(response.farewell);
});
});
А в метод setPageBackgroundColor (скрипт контента) добавил слушателя для обработки сообщения:
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (request.identifier === "tickerResults") {
console.log('received the ticker results from the extension', request.results);
// update the dom
}
sendResponse({ 'farewell': "goodbye" });
}
);
Результат:
И наконец, нужно взять результаты, которые были переданы из расширения в скрипт контента, а затем обновить DOM:
let tickerResultsFromEtoro = request.results;
let tickersHtmlElements = Array.from(document.querySelectorAll('.screener-link-primary'));
let tickers = tickersHtmlElements.map(function (tickerHtml) {
return tickerHtml.innerHTML;
})
for (let key in tickerResultsFromEtoro) {
if (tickerResultsFromEtoro[key]) {
let element = tickersHtmlElements[tickers.indexOf(key)];
element.parentElement.parentElement.style.background = '#c6ddc6';
let thirdCol = element.parentElement.parentElement.children[2];
thirdCol.innerHTML = thirdCol.innerHTML + `<a href="<https://www.etoro.com/markets/${key}>" target="__blank" class="screener-link-primary" style="float:right;">Open on eToro</a>`
}
}
Результат:
Я применил базовые познания в CSS и немного улучшил вид popup.js:
Все работает! Теперь можно быстро посмотреть, какие тикеры доступны на eToro, что экономит много времени и усилий.
Опыт разработчика
Если вы знаете “ванильный” JS, то легко справитесь с этой задачей. В противном случае могут возникнуть некоторые трудности. Тем не менее документация составлена понятно, поэтому просто следуйте ей.
Еще одно преимущество заключается в том, что не нужно устанавливать множество различных инструментов и пакетов. Достаточно просто загрузить кодовую базу и запустить расширение Chrome в браузере.
Бэкенд
Большая часть логики этого расширения хрома обрабатывается на бэкенде. У eToro нет официального API, поэтому мне пришлось найти обходной путь для получения данных от этой бирже. Как только я получил эти данные, я просто использовал Laravel для обработки входящих HTTP-запросов от расширения Chrome.
Читайте также:
Перевод статьи Ajay Madhukar: I Built a Chrome Extension to Instantly Find eToro’s Stocks on FINVIZ’s Stock Screener