Web scraping данных на JavaScript и Node.js через Nightmare JS

Все чаще среди разработчиков web приложений используется стек с жестким разграничением пользовательского интерфейса (front end) и программно-аппаратной части сервера (back end). Для таких реализаций на стороне клиента, как правило, устанавливаются JavaScript-фреймворки, такие как AngularJS, Vue.js, React, Polymer и д.р., которые по определенным механизмам взаимодействуют с серверами для получения необходимых данных, после получения данных, а они как правило приходят постепенно, не за один запрос, «на лету» генерируют часть DOM и выводят их на экран. Тем самым мы получаем, что сайт «собирается» самостоятельно средствами JavaScript после того, как мы туда уже зашли. Если мы захотим, по старинке, получить и проанализировать DOM такого ресурса, к примеру, средствами PHP, C# или Python, то это у нас вызовет сильные трудности т.к. они не умеют запускать JS код. Для решения таких проблем есть два выхода:
- Заходить на ресурс через отладочную панель, искать отправляемые запросы и получаемые данные, пытаться понять принцип их обработки и реализовывать аналог получения данных через необходимый язык программирования.
- Запустить приложение, которое умеет обрабатывать JS код и через него вытащить необходимую информацию.
Что бы выяснить какой путь решения задачи нам подходит, в первую очередь необходимо понять где затраченное время и усилий будет меньше.
В первой реализации придется столкнуться с рядом проблем, одними из которых будет обход системы безопасности стороннего сервиса и анализ JavaScript кода для получения данных, что может занять не ограниченное время.
Во втором же случае проблема заключается только в том, что бы сторонний ресурс не заподозрил, что зашедший клиент — бот, что в разы проще по реализации в отличии от предыдущего метода.
Выбрав метод реализации, приступаем к построению проекта по анализу стороннего сервиса, а в роли «жертвы» идеально подходит новостной ресурс medium.com, который написан на AngularJS.
При выборе стека для реализации данной задачи, выбор пал на Nightmare JS, по причине его простоты и высокой функциональности.
Для его использования потребуется установить Node.js и npm. Для этого идем на оф.сайт, скачиваем и устанавливаем.
Далее нам необходимо установить сам Nightmare и две дополнительные библиотеки — dotenv (отвечает за подключение конфигурационного файла «.env») , nightmare-real-mouse (подключает «реальную» мышь в Nightmare с 3-я событиями: click, mouseover, mousedown)
npm i dotenv && npm i nightmare-real-mouse
Теперь осталось только создать файл конфигураций и сам скрипт работы приложения. Для этого создадим файл «.env» и добавим в него основные ссылки для поиска данных:
KEY_TAB=\u0009
KEY_ENTER=\u000d
SITE=https://medium.com
ONLOAD_START=.js-homeStream .streamItem
ONLOAD_SEARCH=.js-postList
HEADER_SEARCH_BUTTON=label.button svg.svgIcon-use
HEADER_SEARCH_INPUT=label.button.is-touched input.js-predictiveSearchInput
HEADER_SEARCH_TEXT=From Russia
SEARCH_MAIN=div.js-postListHandle
SEARCH_LISTS=.postArticle-content
SEARCH_LIST_ITEM=.section-content h3
А теперь и сам запускаемый файл «index.js»:
//подключаем конф.файл .env
require('dotenv').config();
//подключаем приложение
const Nightmare = require("nightmare");
//подключаем реальную мышь в приложение
require('nightmare-real-mouse')(Nightmare);
//запуск асинхронно функцию приложения
(async function(conf){
//создаем приложение с настройками
let nightmare = Nightmare({
show: true, //отображать приложение
width: 1440, // ширина экрана
waitTimeout : 0, // в ms
});
try {
const result = await nightmare
.useragent ("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36") //юзер агент
.goto(conf.SITE) //открываемая страница
.wait(conf.ONLOAD_START) // дожыдаемся пока появится обьект dom
.realClick(conf.HEADER_SEARCH_BUTTON) // нажимаем кнопку в меню
.wait(1000) // делаем задежку в секунду
.type(conf.HEADER_SEARCH_INPUT, conf.HEADER_SEARCH_TEXT) //заполняем поле поиска
.wait(1000) // делаем задежку в секунду
.type('body', conf.KEY_ENTER) // нажимаем кнопку ENTER
.wait(conf.ONLOAD_SEARCH) // дожыдаемся пока появится обьект dom
.screenshot('shot.png') // делаем скриншот
.evaluate(function() { //событие для получения данных
//получаем список новостей
let JS_SEARCH_MAIN = document.querySelector(conf.SEARCH_MAIN + ' ' + conf.SEARCH_LISTS),
resArr = [];
// вытаскиваем только названия статей
for(let key in JS_SEARCH_MAIN){
resArr.push( JS_SEARCH_MAIN[key].querySelector(conf.SEARCH_LIST_ITEM[key]) );
}
//возвращаем результат
return resArr;
})
.wait(5000) // делаем задежку в 5 секунд
.end(); // выходим из скрипта
console.log(result); // вывод результата в консоле
} catch (error) {
throw error;
} finally {
await nightmare.end();
}
})(process.env);
Собственно и все, список данных получен 🙂