CoderCastrov logo
CoderCastrov
Парсер

Как парсить с использованием Puppeteer.js ПОШАГОВО

Как парсить с использованием Puppeteer.js ПОШАГОВО
просмотров
13 мин чтение
#Парсер

Копирование информации с любого веб-сайта с использованием Node.js и Express.js с помощью Puppeteer.js и сохранение информации в MongoDB

Введение

Эта статья является частью серии, в которой я рассказываю о своем опыте создания API с использованием GraphQL для получения конечной статистики всех чемпионов игры Raid: Shadow Legends. В этой статье я расскажу, как я получил данные о чемпионах, парся информацию с блога Raid-Codex.

Я использовал Puppeteer.js для выполнения этой задачи, но существуют и другие библиотеки, такие как Selenium, Cheerio или Nightmare.

Что такое Puppeteer.js?

Puppeteer.js - это библиотека, которая позволяет автоматизировать процессы, используя браузерный движок Chromium. Этот инструмент имитирует навигацию на веб-сайте и выполняет ряд действий, которые мы указываем.

В нашем случае мы хотим использовать эту библиотеку для:

И все это мы сможем сделать менее чем в 150 строк кода.

Требования

Для того чтобы начать сбор информации автоматически с использованием Puppeteer.js, нам понадобится установить NPM и Node.js на наших компьютерах. Чтобы проверить, установлены ли они, просто откройте терминал и введите:

node -v

Если они установлены, должно отобразиться что-то вроде следующего:

node -v
v.12.14.3

И чтобы проверить, установлен ли npm, используйте:

npm -v

Если он установлен, должно отобразиться что-то вроде следующего:

6.13.4

Если один из них не установлен, просто перейдите на страницу Node.js и установите последнюю версию LTS, которая также включает последнюю версию NPM.

Начальные настройки

После того, как мы убедились, что у нас установлены NPM и Node.js, открываем терминал и вводим следующую команду:

mkdir парсинг && cd парсинг && npm init -y && npm install express mongoose puppeteer

Что мы сделали с этой командой, так это создали папку с названием парсинг. Затем мы перешли в эту папку, инициализировали проект NPM и установили express, mongoose и puppeteer. Этот процесс может занять некоторое время. После завершения этой задачи не закрывайте терминал.

Express.js

Это фреймворк для Node.js, который позволяет нам, среди многих других вещей, объявлять, что должно произойти, когда мы переходим по определенному URL. В нашем случае, мы хотим, чтобы при входе в наше приложение с помощью "/парсинг", выполнялся Puppeteer.js

Puppeteer.js

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

Mongoose

Эта библиотека позволяет нам подключаться к базе данных MongoDB и отправлять информацию, которую мы собираем с помощью нашего робота.

После установки этих зависимостей мы продолжаем настройку среды. В терминале вводим:

touch index.js

Это создаст файл с именем index.js.

Теперь открываем проект в нашей любимой среде разработки, в моем случае это Visual Studio Code. Открываем файл package.json и видим следующее:

{
  "name": "scrapping",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \\"Error: no test specified\\" && exit 1",
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.17.1",
    "mongoose": "^5.8.11",
    "puppeteer": "^2.1.0"
  }
}

Этот файл был создан командой npm init -y. Здесь мы добавляем следующую строку "dev": "node index.js" внутри "scripts". Получится следующее:

{
  "name": "scrapping",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \\"Error: no test specified\\" && exit 1",
++  "dev": "node index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.17.1",
    "mongoose": "^5.8.11",
    "puppeteer": "^2.1.0"
  }
}

Эта строка добавляет команду, которая позволяет нам запустить проект в среде разработки на наших компьютерах.

Теперь открываем созданный ранее файл index.js и добавляем следующий код:

const express = require('express')
const app = express()
const port = 3000app.get('/', (req, res) => res.send('Hola mundo!'))app.listen(port, () => console.log(`App de ejemplo escuchando el puerto ${port}!`))

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

npm run dev

Если все в порядке, мы увидим следующее сообщение в терминале “App de ejemplo escuchando el puerto 3000!”. Теперь остается только перейти по адресу http://localhost:3000/ и увидеть наше "Hola mundo!".

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

Парсинг информации о чемпионах в игре Raid: Shadow Legends

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

const puppeteer = require("puppeteer");

Эта строка запрашивает модуль Puppeteer и сохраняет его в константной переменной с тем же именем.

Следующее, что нам нужно сделать, это объявить URL-адрес, к которому мы будем обращаться каждый раз, когда захотим запустить наш парсер. Для этого добавляем следующий фрагмент кода:

app.get("/parsing", function (req, res) {
  res.send('Мы готовы начать');
})

Вход на URL

Теперь мы готовы начать давать инструкции нашему парсеру. Сначала нам нужно знать, куда мы его отправим. В нашем случае, URL, на который мы отправимся, следующий: Raid-Codex.

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

Вход на страницу преобразуется в следующий код:

app.get("/scrapping", function (req, res) {
	let scrape = async () => {
		const browser = await puppeteer.launch({ headless: false });
		const page = await browser.newPage();
		await page.setViewport({ width: 1920, height: 1080 });
		await page.goto("<https://raid-codex.com/champions/#!?filter=e30%3D>", [
      1000,
      { waitUntil: "domcontentloaded" }
    ]);
	}	scrape()
})

Этот небольшой фрагмент кода создает асинхронную функцию с именем scrape. Внутри этой функции мы запускаем парсер с небольшой настройкой { headless: false }, которая открывает браузер, чтобы мы могли видеть, что он делает (это очень полезно на этапе разработки, затем можно удалить). Затем мы сохраняем его в константной переменной с именем browser.

Важно настроить вид нашего парсера на 1920x1080, потому что ему нужно видеть всё содержимое страницы, чтобы выполнить инструкции, которые мы даем.

Затем мы говорим browser открыть новую страницу и сохранить ее в константной переменной с именем page. И, наконец, мы заставляем робота перейти на URL, который мы указали, на этой новой странице. Дополнительная настройка говорит ему подождать 1000 мс (1 секунду) и дождаться полной загрузки содержимого страницы.

И с последней строкой мы просто вызываем ранее созданную функцию.

Чтобы увидеть процесс, нам нужно остановить службу в консоли. Для этого мы нажимаем комбинацию CMD + C, если мы используем Mac, или Ctrl + C, если мы используем Windows, затем снова запускаем команду npm run dev и переходим по адресу http://localhost:3000/scrapping. Это нужно делать каждый раз, когда мы вносим изменения и хотим увидеть их в парсере.

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

Нажатие на чемпиона.

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

let elementToClick = '<Селектор>'
await page.waitForSelector(elementToClick);
  
await Promise.all([
  page.click(elementToClick),
  page.waitForNavigation({ waitUntil: 'networkidle2' }),
])

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

В нашем случае мы заходим на Raid-Codex и с помощью селектора элементов нажимаем на изображение чемпиона. На странице будет выделена область HTML, и нам нужно будет щелкнуть правой кнопкой мыши на ней → Копировать → Копировать селектор. После этого заменяем '<Селектор>' на то, что мы только что скопировали. Обратите внимание, что то, что мы присваиваем переменной elementToClick, должно быть строкой.

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

Парсинг информации о чемпионе

Отлично! Мы почти закончили. Осталось только указать нашему роботу, что мы хотим, чтобы он скопировал следующие поля, находясь на странице чемпиона: имя, здоровье, атаку, защиту, шанс критического удара, урон критического удара, скорость, сопротивление, редкость, фракцию, тип и элемент.

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

const result = await page.evaluate(() => {
	// Здесь мы указываем, что мы хотим, чтобы он сделал
}

Теперь мы хотим, чтобы он нашел на странице место, где находится нужная нам информация, и сохранил ее. Для этого мы полагаемся на технику, которую мы изучили на предыдущем шаге. Мы копируем селектор, но теперь нам не нужен селектор. Нам нужно содержимое. Это можно сделать, используя функцию JavaScript document.querySelector('Селектор').innerText. Применяя это для получения текста селектора имени, получим:

let name = document.querySelector("body > main > div > div.col-12.text-center.mb-3 > h1").innerHTML;

И мы повторяем это для остальных полей, которые мы ищем. После этого роботу нужно сохранить все эти поля где-то. Для этого мы создаем константную переменную с именем championModel и присваиваем ей формат JSON, в который передаем переменные с информацией, полученной при парсинге. Например:

let championModel = {
  name: name,
  rarity: rarity,
  faction: faction,
  type: type,
  element: element,
  stats: {
    health: health,
    attack: attack,
    defense: defense,
    criticalRate: criticalRate,
    criticalDamage: criticalDamage,
    speed: speed,
    resistance: resistance,
    accuracy: accuracy
  }
}

После этого мы можем использовать console.log(championModel), и у нас должно получиться следующее:

{
  name: 'Raglin',
  rarity: 'Легендарный',
  faction: 'Banner-Lords',
  type: 'Помощник',
  element: 'Пустота',
  stats: {
    health: '20310',
    attack: '1156',
    defense: '1068',
    criticalRate: '15%',
    criticalDamage: '50%',
    speed: '104',
    resistance: '50',
    accuracy: '0'
  }
}

Отлично! У нас все получилось. Теперь нам просто нужно сделать так, чтобы эта функция, в которой мы работали, возвращала то, что мы только что сделали. Это так просто, как добавить эту последнюю строку в конце функции.

return championModel

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

Парсинг информации в цикле

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

await page.goBack([5000, { waitUntil: "domcontentloaded" }]);

Теперь давайте вернемся в начало кода до того момента, где мы находим следующую строку:

await page.goto("<https://raid-codex.com/champions/#!?filter=e30%3D>", [...]);

Под ней мы должны создать две константные переменные. Одна называется parentRow, а другая totalChampions.

Для первой переменной мы снова открываем страницу, которую парсим, и с помощью инспектора находим родительский <div> для переменной elementToClick, копируем селектор и присваиваем его.

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

const parentRow = "body > main > champion-list > div";
await page.waitForSelector(parentRow);
const totalChampions = await page.$eval(parentRow, el => el.childElementCount);

Создав эти две переменные, перейдем к созданию цикла, который позволит нам получить информацию о всех чемпионах из списка.

Для этого мы создадим изменяемую переменную championList и присвоим ей пустой объект. Затем мы используем функцию for(), указываем, что этот цикл должен выполняться, пока количество итераций не станет равным или больше totalChampions. Перемещаем то, что у нас уже есть, внутрь этого цикла.

Важно: Мы должны изменить значение переменной elementToClick, потому что до этого каждый раз, когда код выполнялся, он искал только одного чемпиона. Теперь, благодаря тому, что у нас есть количество итераций цикла, мы можем заменить номер дочернего элемента parentRow, на который мы хотим кликнуть.

Перед тем, как робот перейдет на предыдущую страницу, мы делаем так, чтобы результат, полученный из result, добавлялся в объект championList с помощью функции push(). Это должно выглядеть так:

let championList = [];
for (var i = 1; i <= totalChampions; i++) {
	let elementToClick = `body > main > champion-list > div > div:nth-child(${i}) > div > div > div:nth-child(1) > a > picture > img`;
  await page.waitForSelector(elementToClick);
  
  await Promise.all([
    page.click(elementToClick),
    page.waitForNavigation({ waitUntil: 'networkidle2' }),
  ])  const result = await page.evaluate(() => {...})
	championList.push(result);
	await page.goBack([5000, { waitUntil: "domcontentloaded" }]);
}

Теперь мы сделали так, чтобы наш робот парсил информацию о всех чемпионах из списка. Но нам нужно сделать так, чтобы при вызове функции scrape(), которую мы создали в самом начале, она возвращала то, что она делала. И не забудьте отправить нашего робота отдохнуть после всей работы, которую мы ему поручили. Чтобы сделать это, добавьте следующее за пределами функции for().

browser.close();
return championList;

Сохранение информации в MongoDB

Отлично. Теперь мы знаем, как навигироваться и получать информацию с помощью puppeteer.js. Проблема, с которой мы столкнулись, заключается в том, что эта информация будет доступна только до тех пор, пока мы не остановим проект.

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

После регистрации мы создадим кластер с любым именем. После создания мы увидим панель, похожую на ту, что показана ниже. Нажмите кнопку «Connect», а затем откроется модальное окно, в котором нужно выбрать Connect your application.

На следующем шаге убедитесь, что выбран Node.js в качестве драйвера и 3.0 или более поздняя версия. Затем убедитесь, что вкладка Connection string only выбрана, нажмите кнопку «Copy» и закройте окно.

Вернитесь в свою среду разработки и добавьте две новые константные переменные в начало кода. Первая переменная будет вызывать библиотеку, которая позволит нам связаться с нашим кластером и отправить ему информацию. Во второй переменной вставьте доступ, скопированный со страницы MongoDB, в виде строки.

const mongoose = require('mongoose');
const mongoUrl = 'mongodb+srv://<MongoDB>';

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

mongoose.connect(mongoUrl, { useNewUrlParser: true });
var db = mongoose.connection;!db ? console.log("Hubo un error conectandose a la base de datos") : console.log("Conexión a base de datos satisfactoria");

Наконец, нам остается только сказать нашему роботу, чтобы он после парсинга страницы отправил информацию в MongoDB. Для этого сначала создадим новый файл с именем championModel.js, который будет содержать модель информации, которую мы ожидаем получить.

var mongoose = require('mongoose');
var Schema = mongoose.Schema;var championSchema = new Schema({
  name: {
    type: String,
    required: true
  },
  rarity: {
    type: String,
  },
  faction: {
    type: String,
  },
  type: {
    type: String,
  },
  element: {
    type: String,
  },
  stats: {
    health: {
      type: String,
      required: true
    },
    attack: {
      type: String,
      required: true
    },
    defense: {
      type: String,
      required: true
    },
    criticalRate: {
      type: String,
      required: true
    },
    criticalDamage: {
      type: String,
      required: true
    },
    speed: {
      type: String,
      required: true
    },
    resistance: {
      type: String,
      required: true
    },
    accuracy: {
      type: String,
      required: true
    },
  }
});module.exports = Champion = mongoose.model('champions', championSchema);

Добавьте следующую строку в index.js, чтобы использовать эту модель.

const Champion = require("./championModel")

Теперь нам остается только отправить информацию в MongoDB после завершения парсинга. Для этого мы делаем следующее:

scrape().then(value => {    
		Champion.create(value, function (err, small) {
			if (err) return handleError(err);
			// saved!
		});
		res.send(value);
		return;
	});

Заключение

Поздравляю! Теперь вы знаете основы использования библиотеки puppeteer.js для парсинга информации. Вы знаете, как указать библиотеке, что вы хотите зайти на страницу, сделать клик, собрать некоторую информацию, вернуться на предыдущую страницу и повторить процесс до тех пор, пока не обработаете всех чемпионов на странице. Вы не только знаете, как давать инструкции, но и знаете, как подключиться к MongoDB и отправить полученную информацию.

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

Репозиторий на GitHub

Документация

Puppeteer.js

MongoDB

Mongoose.js