Я вытащил 30 000 акций из eToro — вот как

Вы можете задаться вопросом — что вообще побудило меня сделать такое?!

Что ж, последние несколько недель я работал над запуском своего первого расширения для Google Chrome (о саге можно прочитать здесь).

Я назвал расширение — Finviz Screener X eToro.

Я не мог придумать лучшего имени, но оно выполняет свою работу.

С помощью этого расширения вы можете мгновенно проверить, действительно ли конкретная акция Finviz существует на eToro или нет. Таким образом, вы исключаете весь процесс ручной проверки акций eToro за акциями.

Чтобы эта концепция заработала, мне нужно было найти способ запрашивать данные eToro на основе результатов, показанных в Finviz Stock Screener.

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

Поэтому я решил взять дело в свои руки и создать собственный API. И для этого мне пришлось очистить eToro для получения необходимых мне биржевых данных.

План игры

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

Причина в том, что eToro группирует акции по отраслям, и доступ к индексу для каждой отрасли возможен только в том случае, если вы вошли в систему.

Поэтому, чтобы обойти это, я решил вместо этого использовать PuppeteerJS.

PuppeteerJS — это, по сути, библиотека Node, которая позволяет вам программно запускать экземпляр Chrome/Chromium (честно говоря, я не знаю разницы между ними), и вы можете использовать ее для автоматизации тестирования, просмотра веб-страниц и т. д.

Итак, мой план игры высокого уровня:

  1. Настройте проект node/puppeteer
  2. Найдите способ войти в систему eToro программно
  3. Перейдите на страницу категорий и очистите бегущие строки акций, название и цену.
  4. Сохранить данные в БД

Как всегда, я сосредоточусь на функциональности, а не на повторном использовании/структуре. Так что я надену свой поварской колпак и приготовлю этот спагетти-код, который облегчит мне жизнь.

1. Настройка проекта

Вот все, что вам нужно для начала:

  1. Узел и Npm — https://nodejs.org/en/download/
  2. Хром — https://www.google.com/chrome/
  3. Редактор кода. Я использую VSCode — https://code.visualstudio.com/download

У меня все это было установлено, поэтому я просто создал папку своего проекта, запустил npm init (нажав Enter до конца) и создал файл с именем index.js через терминал (команда: touch index.js).

Затем я запустил эту команду в терминале:

npm i puppeteer

Как только npm сделал свое дело, я просто скопировал код примера (из документации) в свой файл index.js (с небольшим изменением) и выполнил файл с узлом (введите это в своем терминале: node index.js ).

Результат:

Работал как шарм. Я могу перейти на страницу входа без каких-либо проблем.

Теперь я могу начать исправлять код. Я могу избавиться от скриншота, а также закомментирую строку browser.close(), пока буду разбираться.

2. Войти программно

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

Поэтому я зашел на YouTube и наткнулся на это видео от Тома Барановича, и он проделал потрясающую работу, рассказав о PuppeteerJS и о том, как он работает.

Общая суть его:

  • найти селектор
  • Используйте методы кукловода для управления селектором. Наиболее распространенные методы, которые я использовал для этого:
- page.type(selector, text, options) // Allows you to type a text into an input field
- page.click(selector, options) // click an element. good for submitting forms etc.,
- page.waitForNavigation(); // if the page redirects, just wait for the new page to load
- page.goto(url) // if you know the url, just navigate to the url
- page.waitForTimeout(1000) // pause execution for some time

Затем я перешел к eToro, щелкнул правой кнопкой мыши и выбрал параметр «Проверить элемент», чтобы увидеть, как структурирован HTML.

Все HTML-элементы на eToro имеют идентификатор автоматизации, на который можно просто сослаться, и это значительно упрощает работу.

Например: селектор для поля ввода имени пользователя = input[automation-id="login-sts-username-input"]

Переключите окна на мой VSCode, и я начал печатать в файле index.js.

Я использовал метод page.goto для перехода на страницу входа.

Затем я использовал метод page.type для ввода электронной почты и пароля и метод page.click для нажатия кнопки.

const browser = await puppeteer.launch({ 
        headless: false, 
    });
const page = await browser.newPage();
console.log('going to the login page'); 
// Navigate to the login page
await page.goto('<https://etoro.com/login>');
await page.waitForSelector('input[automation-id="login-sts-username-input"]');
console.log(`Typing the email: ${email}`); 
await page.type('input[automation-id="login-sts-username-input"]', email, {
   delay: 25
}); 
console.log(`Typing the password: ${password}`); 
await page.type('input[automation-id="login-sts-password-input"]', password, {
   delay: 25, 
});
console.log('wait for .5 seconds before finding the login button'); 
    
await page.waitForTimeout(1000);
    
console.log('click the login button');
    
await page.click('button[automation-id="login-sts-btn-sign-in"]');

Результат:

А потом бум.

Меня встретило сообщение об ошибке — «Произошла ошибка, попробуйте еще раз».

Я понятия не имел, что на самом деле означало это сообщение.

Поэтому я направился к Stackoverflow, чтобы найти обходной путь.

Оказывается, проблема заключалась в том, что у eToro были некоторые механизмы, чтобы удерживать ботов (таких как я) подальше от их системы.

Таким образом, решение состояло в том, чтобы замаскировать бота под человека, добавив 2 новых пакета через npm.

Я обновил свой код следующим образом:

const puppeteer = require('puppeteer-extra');
(async () => {
    // Add stealth plugin and use defaults (all tricks to hide puppeteer usage)
    const StealthPlugin = require('puppeteer-extra-plugin-stealth')
    puppeteer.use(StealthPlugin())
    const browser = await puppeteer.launch({
        headless: false, 
    });
    const page = await browser.newPage();
    await page.goto('<https://etoro.com/login>');
    // await page.screenshot({ path: 'example.png' });
    await page.waitForTimeout(1000);
    const email = '[email protected]';
    const password = 'insertyourownpassword';
    console.log(`Typing the email: `);
    await page.type('input[automation-id="login-sts-username-input"]', email, {
        delay: 25
    });
    console.log(`Typing the password: `);
    await page.type('input[automation-id="login-sts-password-input"]', password, {
        delay: 25,
    });
    console.log('wait for .5 seconds before finding the login button');
    await page.waitForTimeout(1000);
    console.log('click the login button');
    await page.click('button[automation-id="login-sts-btn-sign-in"]'); 
    // await browser.close();
})();

А потом бум. Я был в.

Результат:

Переходим к веселым вещам…

3. Парсинг биржевых тикеров, названия и цены

На данный момент все, что мне нужно было сделать, это найти на eToro список акций, классифицированных по отраслям. Я сделал прыжок веры, зашел в Google и набрал техноиндустрия eToro.

Вуаля! Первая ссылка привела меня прямо к акциям технологической отрасли на eToro. У них было около 369 результатов, поэтому я решил сначала очистить эти данные, а затем заняться другими отраслями (в раскрывающемся списке).

Я щелкнул правой кнопкой мыши по первому результату и выбрал опцию проверки элемента, а также отметил селектор, который мне нужно использовать, ⇒ et-instrument-trading-row.

В том же файле index.js я сначала использовал метод page.goto, а затем метод page.$$eval (эквивалентный методу document.querySelectorAll) для получения массива строк.

Код:

let etoroUrl = '<https://www.etoro.com/discover/markets/stocks/industry/technology>'; 
console.log('Navigating to ' + etoroUrl); 
await page.goto(etoroUrl);
console.log('wait for 1 second');
await page.waitForTimeout(1000);
console.log('wait for selector on the page');
await page.waitForSelector('et-instrument-trading-row');
const instrumentTradingRows = await page.$$eval('et-instrument-trading-row', (rows) => {
   console.log('rows', rows[0], rows[1]); 
});

Результат:

Достаточно просто.

Затем я изменил метод page.$$eval таким образом, чтобы он возвращал объект атрибутов, который я мог бы сохранить в БД.

const instrumentTradingRows = await page.$$eval('et-instrument-trading-row', (rows) => {
	return rows.map(row => {
    let attributes = row.children[0].innerText.split('\\n')
    console.log('attributes', attributes);
    return {
        ticker: attributes[0],
        name: attributes[1],
        sellPrice: attributes[5] * 100,
        buyPrice: attributes[7] * 100,
        link: '<https://www.etoro.com/markets/>' + attributes[0]
    };
 '});
});

Наконец, я просто следовал этому руководству, чтобы сохранить массив результатов в БД: https://www.w3schools.com/nodejs/nodejs_mysql.asp.

И с этим у меня был основной поток и работа. Осталось только заключить все в цикл for (создать объект со всеми URL-адресами), и я откинулся на спинку кресла и наблюдал, как eToro очищается 30 000 раз.

Основные извлеченные уроки

Этот урок обязателен.

Всегда открывайте URL-адрес в режиме инкогнито, чтобы правильно проверить, действительно ли страница защищена уровнем аутентификации.

Потому что, если это не так, вы можете сразу же очистить URL-адрес, не беспокоясь об аутентификации.

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

Как говорится, все это не было пустой тратой времени.

Я узнал о пакете Puppeteer-extra и StealthPlugin, которые очень удобны.

Общий опыт разработки

Одно слово — Супер!

После просмотра видео Тома Барановича на YouTube все обрело смысл, и это помогло мне преодолеть первоначальную борьбу с поиском пути через документацию.

В общем, API Puppeteer очень интуитивно понятен, и если вы сможете запомнить всего несколько методов, вы будете весьма продуктивны в следующий раз, когда возьметесь за проект.

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

Дайте мне знать, если вы хотите, чтобы я написал о парсинге другого веб-приложения!

И хватит программирования на эту неделю.

Также читайте: как я обновил и официально запустил свое первое расширение для Chrome

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