Расширение Chrome для поиска акций на eToro с помощью скринера акций FINVIZ

Во время пандемии я создал аккаунт на eToro и начал разбираться с тем, как можно получить акции Rolex, Rolls Royce и Rover. Наблюдая за профессионалами на YouTube, я наткнулся на скринер акций FINVIZ. С помощью этой платформы можно отсортировывать более 8 000 акций на основе различных технических факторов, что помогает лучше планировать сделки.

Однако на eToro недоступны некоторые акции. Чтобы удостовериться в их наличии, я обычно копировал тикер, заходил в поиск на eToro и вставлял его в поле. Если Finviz выдает список из 100 акций, то это действие приходится повторять 100 раз.

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

Объем работы

  1. Создать расширение для Chrome, которое считывает тикеры акций из FINVIZ и передает их на бэкенд.
  2. Использовать Laravel на бэкенде для проверки значений в базе данных и возврата булевого значения для каждого тикера.
  3. Найти обходной путь для получения данных из eToro, так как, похоже, у сервиса нет официального API.

План действий

  1. Написать беспорядочный код. Чем беспорядочнее, тем лучше. Я создаю код в кратчайшие сроки. Я пишу его для себя, поэтому считаю, что в этом случае код не должен быть правильно структурирован. Он просто должен работать. Кроме того, держитесь подальше от конструкторов модулей. Мне жалко тратить целый день на то, чтобы разбираться в настройках Webpack.
  2. Ограничить скрейпинг тикеров вкладкой “Обзор” (Overview) на FINVIZ для проверки работы.
  3. Придумать, как включить в расширение функцию оплаты/выставления счетов.
  4. Расширить функционал инструмента и на CoinGecko. Об этом можно подумать позже.
  5. Придумать название для расширения, подобрать иконку и т. д. И об этим деталях можно позаботиться позднее.

Написание кода

Установка и настройка проекта

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.

Читайте также:

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод статьи Ajay Madhukar: I Built a Chrome Extension to Instantly Find eToro’s Stocks on FINVIZ’s Stock Screener

Предыдущая статья#04TheNotSoToughML | “Давай, минимизируй ошибки” — Но достаточно ли этого?
Следующая статьяКак создать инструмент PGP-шифрования на основе Python