Парсер на NodeJS & Puppeteer
DegreetВведение
Всем привет! Сегодня мы будем учиться писать парсеры на NodeJS при помощи библиотеки Puppeteer.
Что нужно знать?
Для того чтобы перейти к уроку, Вы уже должны быть знакомы с NodeJS, и Вы уже должны знать зачем нужны парсеры и что они делают. Также желательно уметь немного работать с DOM через JavaScript. Желательно еще уметь работать с DevTools.
Установка
Первым делом надо установить саму библиотеку Puppeteer, при помощи которой мы и будем писать парсеры. Для этого в терминале введем соответствующую команду:
npm install puppeteer
После чего мы готовы к работе. Естественно перед установкой надо инициализировать nodejs проект.
Как работает Puppeteer?
Puppeteer открывает отдельное окно браузера и делает все действия в нем. Таким образом мы управляем как бы браузером, и получаем от него данные.
Инициализация проекта
Введем данный код:
const puppeteer = require('puppeteer')
void (async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto('https://example.com')
await page.screenshot({ path: 'example.png' })
await browser.close()
})()
В первой строке импортируем сам модуль. Далее создаем функцию-обертку чтобы иметь доступ к асинхронным действиям. "void" в данном случае нужен чтобы не писать точку с запятой.
В этой строке,
const browser = await puppeteer.launch()
мы открываем браузер, в котором будем совершать действия.
Далее в строке,
const page = await browser.newPage()
мы открываем новую страницу в только что открытом браузере.
В след. строке
await page.goto('https://example.com')
в только что открытой странице мы переходим по адресу сайта, с которого будем парсить данные.
Далее в строке
await page.screenshot({ path: 'example.png' })
мы делаем скриншот открытого сайта, который сохраняем в корне проекта с названием "example.png". Сделали мы это чтобы посмотреть правильно ли все отработало. Скриншоты в puppeteer это один из вариантов дебаггинга - о нем позже.
Далее в строке
await browser.close()
мы закрываем открытый браузер. Если его не закрывать, то программа будет висеть дальше в фоне. А так после скриншота браузер закроется и программа завершиться. Эту строку надо писать в конце программы.
Делаем запуск! Вместо адреса "https://example.com" укажите тот адрес, с которого собираетесь парсить данные. После чего запускаете программу, и когда она закроется в корне проекта появиться файл (фото) "example.png", в котором Вы увидите страницу которую хотите спарсить.
Парсим данные
И наконец получаем данные со страницы. Для начала в нашем браузере открываем страницу с которой нужно спарсить данные, затем открываем DevTools (Ctrl+Shift+C), наводим на тот элемент с которого хотим спарсить данные и нажимаем на него. Далее в самом DevTools нас перекинет на данный объект. Нажмите на нем ПКМ -> Copy -> Copy selector. Таким образом скопировали селектор (css путь) к данному объекту.

После чего можем перейти к коду. В коде уберем создание скриншота, т.к. оно нам пока что не понадобиться. Далее вводим строки:
const subs = await page.$eval(selector, (el) => el.innerText) console.log(subs)
Метод "$eval" выполняет JavaScript код на странице. Первым параметром данный метод принимает селектор к элементу, с которым нужно что-то сделать (то есть вместо "selector" вам надо указать скопированный селектор в кавычках), а вторым параметром данный метод принимает функцию, которая будет принимать в себя элемент найденный по селектору. В данном случае функция возвращает текст найденного элемента по селектору. Как Вы поняли, данный метод возвращает то, что вернула функция во втором параметре. Ну и console.log просто выводит результат. Если запустим - то увидим нужный результат. Если нет - идем дальше. Пример текущего кода:
const puppeteer = require('puppeteer')
void (async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto('https://www.youtube.com/c/Degreet/videos')
const subs = await page.$eval('#subscriber-count', (el) => el.innerText)
console.log(subs)
await browser.close()
})()
Ошибка вместо получения данных
Иногда бывает что не получается спарсить данные, выходит ошибка. Это может быть связано с тем, что страница еще полностью не загрузилась. В таком случае перед получением данных следует ввести еще одну строку:
await page.waitForSelector(selector)
(вместо "selector" указываем селектор к элементу)
Этот метод "waitForSelector" "ждет" пока появиться указанный элемент на странице. И когда он появиться, или же загрузиться, программа пойдет дальше и все должно отработать. Если же все равно возникает ошибка - скорее всего ошибка у Вас в коде. Пример текущего кода:
const puppeteer = require('puppeteer')
void (async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto('https://www.youtube.com/c/Degreet/videos')
await page.waitForSelector('#subscriber-count')
const subs = await page.$eval('#subscriber-count', (el) => el.innerText)
console.log(subs)
await browser.close()
})()
Клик на странице
Если Вам понадобиться сделать клик например по кнопке на странице, Вы можете воспользоваться соответствующим методом "click", который принимает один параметр: селектор к элементу на который нужно кликнуть:
await page.click(selector)
Авторизация на сайте
Также бывает такое, что нужно спарсить данные, к которым доступ имеют только авторизованные пользователи. Чтобы спарсить такие данные нам понадобиться программно через парсер авторизоваться. Для этого мы уже все умеем делать. Переходим к практике, делать будем на примере сайта smartprogress. Заходим на сайт, и смотрим, чтобы авторизоваться нам нужно нажать на кнопку "Войти", ввести данные в поля для ввода и снова нажать на кнопку "Войти". Берем селектор кнопки "Войти" и пишем код:
await page.click('body > header > div.mobile-menu__login > a:nth-child(1)')
await page.screenshot({ path: 'example.png' })
Запускаем, открываем файл "example.png", и видим что ничего не сработало. Причина как Вы могли подумать в том, что элемент не успел загрузиться. Однако если мы попробуем вызвать метод waitForSelector и передать туда селектор к полю для ввода, то все равно ничего не сработает. Проблема кроется за анимацией, она идет примерно 2-3 секунды. Поэтому как бы нам не хотелось, хоть это и неправильно, пишем новый метод "waitForTimeout". Данный метод принимает в себя один параметр: время в миллисекундах. Мы передадим туда 5000 (что значит 5 секунд). То есть мы будем ждать 5 секунд, и после этого программа продолжит работу. И это уже сработает. Вот пример кода:
const puppeteer = require('puppeteer')
void (async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto('https://smartprogress.do/')
await page.click('body > header > div.mobile-menu__login > a:nth-child(1)')
await page.waitForTimeout(5000)
await page.screenshot({ path: 'example.png' })
await browser.close()
})()
Но я считаю это неправильным. Ведь на любом пк это время может отличаться. Поэтому мы поступим иначе, мы не будем переходить на главную страницу и нажимать кнопку "Войти", мы будем изначально переходить на страницу с авторизацией. А именно: https://smartprogress.do/user/login. Это также работает и уже правильно реализовано. Пример кода:
const puppeteer = require('puppeteer')
void (async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto('https://smartprogress.do/user/login')
await page.screenshot({ path: 'example.png' })
await browser.close()
})()
Далее при помощи знакомого нам метода "$eval" устанавливаем всем полям для ввода свои данные, не забываем копировать селекторы:
await page.$eval('#UserLoginForm_email', (el) => el.value = 'lalala@gmail.com')
await page.$eval('#UserLoginForm_password', (el) => el.value = 'пароль')
после чего нажимаем на кнопку входа:
await page.click('#login-form-modal > div.user-form__submit-wrapper > button')
await page.waitForTimeout(5000)
Запускаем, открываем файл "example.png" и как видим - все работает. Ну конечно, если Вы вместо фейковых данных подставили свои. Пример текущего кода:
const puppeteer = require('puppeteer')
void (async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto('https://smartprogress.do/user/login')
await page.$eval('#UserLoginForm_email', (el) => el.value = 'lalala@gmail.com')
await page.$eval('#UserLoginForm_password', (el) => el.value = 'пароль')
await page.click('#login-form-modal > div.user-form__submit-wrapper > button')
await page.waitForTimeout(5000)
await page.screenshot({ path: 'example.png' })
await browser.close()
})()
И на последок, спарсим текущий опыт пользователя. Для начала получим селектор,

и пишем код:
const expSelector = 'body > div.page__body-wrapper > div > div.container.feed-wrapper > div.feed-col.feed-col--side > div.user-menu > div.user-menu__info > div.user-menu__info-text.user-menu__info-text--exp' await page.waitForSelector(expSelector) const exp = await page.$eval(expSelector, (el) => el.innerText) console.log(exp)
Запускаем, проверяем, работает. Пример кода:
const puppeteer = require('puppeteer')
void (async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto('https://smartprogress.do/user/login')
await page.$eval('#UserLoginForm_email', (el) => el.value = 'lalala@gmail.com')
await page.$eval('#UserLoginForm_password', (el) => el.value = 'пароль')
await page.click('#login-form-modal > div.user-form__submit-wrapper > button')
const expSelector = 'body > div.page__body-wrapper > div > div.container.feed-wrapper > div.feed-col.feed-col--side > div.user-menu > div.user-menu__info > div.user-menu__info-text.user-menu__info-text--exp'
await page.waitForSelector(expSelector)
const exp = await page.$eval(expSelector, (el) => el.innerText)
console.log(exp)
await browser.close()
})()
В данном случае, я вынес селектор в отдельную переменную, т.к. селектор большой + используется не один раз.
Дебаггинг
Иногда в коде возникают ошибки, чтобы понять в чем проблема, Вы можете дебажить код. Для этого я рекомендую делать скриншоты. Просто на месте проблемы, делаете скриншот при помощи:
await page.screenshot({ path: 'example.png' })
открываете его и смотрите в чем проблема. Например если Вы не видите тот элемент, который хотите спарсить, значит он еще не загрузился, и Вам следует использовать метод "waitForSelector".
Итоги
Теперь Вы умеете создавать парсеры. Вы научились парсить данные сайтов, а также авторизовываться на сайтах. Это значит, что Вы можете при помощи данной библиотеки не только парсить данные, но еще и автоматизировать какие-то действия в браузере.
Тестовое задание
Если Вы хотите проверить насколько Вы все усвоили, предлагаю Вам написать парсер с google переводчика.
Конец
Автор: Degreet
YouTube: https://www.youtube.com/c/Degreet/featured
Telegram: https://t.me/degreet
Telegram группа: https://t.me/degreet_subs