Во время пандемии я создал аккаунт на 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.
Читайте также:
- 6 шагов по созданию расширения Chrome, которое можно продать
- API Chrome: 7 новинок
- 9 полезных расширений для 2020 года
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Ajay Madhukar: I Built a Chrome Extension to Instantly Find eToro’s Stocks on FINVIZ’s Stock Screener