Изначально опубликовано в моем блоге

Puppeteer - отличная библиотека Node.js, которая предоставляет нам множество команд для управления экземпляром Chrome без головы (или без него) и автоматизации навигации с помощью нескольких строк кода. В этом посте мы собираемся использовать суперсилы кукловода и создать инструмент для сбора информации об автомобилях для каталога подержанных автомобилей и выбрать лучший вариант.

Несколько дней назад я читал со своим товарищем по команде и большим другом @mafesernaarbole о парсинге веб-страниц и различных онлайн-инструментах, которые ей понадобились для личного проекта. Просматривая различные статьи и репозитории, мы обнаружили Puppeteer, который представляет собой высокоуровневый API для управления Chrome без головы по протоколу DevTools. Этот замечательный инструмент пробудил в нас интерес, и, хотя в конце дня он оказался для нее бесполезным, мы оба сказали: Черт возьми! Мы должны что-то с этим делать !! . Через пару дней, я сказал ей, Кукловод будет отличной темой для первой статьи в моем блоге… и вот я здесь. Я надеюсь, тебе это нравится.

Наш учебный пример

Идея довольно проста, в нашей стране, Колумбии, есть каталог подержанных автомобилей, он называется tucarro.com.co. В основном, учитывая марку и модель автомобиля, tucarro.com.co предлагает вам список подержанных автомобилей, которые подходят и выставлены на продажу по всей стране. Дело в том, что потенциальный клиент должен искать один за другим из этих результатов и анализировать, какой из них является лучшим (или вариантами).

Итак, наша цель - создать небольшое приложение Node.js для навигации по веб-сайту каталога и поиска, как это делал бы человек, затем мы собираемся взять первую страницу результатов, очистить ее информацию (в частности, год машины, пробег и цена… ну и конечно URL объявления). Наконец, обладая этой информацией и используя некоторый алгоритм оптимизации, мы собираемся предложить клиенту лучший выбор (или варианты), основанный на цене и пройденных километрах.

Заявление об ограничении ответственности: это упражнение преследует только академические цели, а не коммерческий интерес. Мы не храним извлеченные данные, которые принадлежат tucarro.com.co. Исходный код приложения распространяется по лицензии MIT, что освобождает нас от какой-либо ответственности за производную работу. Мы настоятельно рекомендуем использовать его ответственно.

Начальная настройка

Мы собираемся создать приложение Node.js, поэтому первым шагом, конечно же, будет создание нового проекта npm в новом каталоге. С параметром -y будет создан package.json со значениями по умолчанию:

И добавьте в свой проект зависимость кукловод

$ npm install --save puppeteer 
# or, if you prefer Yarn: 
$ yarn add puppeteer

Наконец, в наш файл package.json добавьте следующий скрипт:

"scripts": { 
   "start": "node index.js" 
}

Этот скрипт упрощает запуск нашего приложения - теперь мы можем сделать это с помощью команды npm start.

Важно! Как вы вскоре увидите, кукловоду нужны функции async и await из ядра Node.js, поэтому нам понадобится последняя версия узла, которая поддерживает эти функции (в этой статье мы используем v9.2.0 версию, однако, поскольку v7.6 обе функции поддерживаются)

Давай рок

После успешной настройки нашего npm проекта следующим шагом будет кодирование, давайте создадим наш index.js файл. А вот и скелет нашего приложения кукловода.

'use strict' 
const puppeteer = require('puppeteer') 
async function run() { 
  const browser = await puppeteer.launch()
  const page = await browser.newPage() 
  browser.close() 
} 
run();

В основном мы импортируем зависимость puppeteer в строку 2, затем мы открываем функцию async, чтобы обернуть все взаимодействия браузера / кукловода, в следующих строках мы получаем экземпляр для браузера Chrome, а затем открываем новый tab (page) ... в конце последних строк мы закрываем браузер (и его процесс) и, наконец, запускаем функцию async.

Переход на наш целевой сайт

Переход на конкретный веб-сайт - это простая задача с использованием нашего экземпляра вкладки (page). Нам просто нужно использовать метод goto:

await page.goto('https://www.tucarro.com.co/')

Вот как сайт выглядит в браузере

Searching

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

await page.waitForSelector('.nav-search-submit') 
await page.click('button[type=submit]');

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

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

await page.waitForSelector('#id_category > dd:nth-child(2) > h3 > a') 
await page.click('#id_category > dd:nth-child(2) > h3 > a')

И вот, теперь у нас есть страница с результатами по машинам ... давайте ее почистим!

Примечание. Селекторы, используемые в этом разделе, были обнаружены при изучении HTML-кода сайта, доставленного в браузер, и / или при использовании параметра copy selector

Соскоблите это!

На нашей странице результатов нам просто нужно перебрать DOM узлы и извлечь информацию. К счастью, кукловод тоже может нам в этом помочь.

await page.waitForSelector('.ch-pagination') 
const cars = await page.evaluate(() => { 
  const results = 
     Array.from(document.querySelectorAll('li.results-item')) 
return results.map(result => { 
     return { 
       link: result.querySelector('a').href, 
       price: result.querySelector('.ch-price').textContent, 
       name: result.querySelector('a').textContent, 
       year: result.querySelector('.destaque > strong:nth-  child(1)').textContent, 
       kms: result.querySelector('.destaque > strong:nth-child(3)').textContent 
     } 
   });
 return results }); 
console.log(cars)

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

{ 
  link: 'https://articulo.tucarro.com.co/MCO-ford-fusion-2007-_JM', 
  price: '$ 23.800.000 ', 
  name: ' Ford Fusion V6 Sel At 3000cc', 
  year: '2007', 
  kms: '102.000 Km' 
}

О, да! у нас есть информация и структура JSON, однако, если мы хотим ее оптимизировать, нам нужно нормализовать данные - ведь вычисления немного сложны с этими Kms и $ символами, не так ли?… Итак, мы собираемся изменить наш фрагмент карты результатов, подобный этому

return results.map(result => { 
  return { 
      link: result.querySelector('a').href, 
      price: Number((result.querySelector('.ch-price').textContent).replace(/[^0-9-]+/g,"")), 
      name: result.querySelector('a').textContent, 
      year: Number(result.querySelector('.destaque > strong:nth-child(1)').textContent), 
      kms: Number((result.querySelector('.destaque > strong:nth-child(3)').textContent).replace(/[^0-9-]+/g,"")) 
  } 
});

Конечно, Regular Expressions спасти день, у нас есть числа, где нам нужны числа.

Время оптимизации !!

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

  • Каждой переменной мы назначаем вес в зависимости от важности для потенциального покупателя (цена имеет 4, год и км - 3 для каждой).
  • Учитывая, что км и цена должны быть минимизированы, мы собираемся использовать его значения в качестве знаменателя дроби.
  • Для упрощения расчетов мы нормализуем числовые коэффициенты для наших переменных, поэтому каждая цена будет разделена между 1 миллионом, годом и километрами на 1 тысячу.

Это окончательная формула. Заявление об ограничении ответственности: это гипотетическая формула для выполнения этого упражнения, поэтому в реальной жизни она не имеет математической или научной ценности

score = 4 (1/price) + 3 (year) + 3 (1/kms)

И фрагмент кода с этой формулой

let car = {score: 0} 
for (let i = 0; i < cars.length; i++) { 
   cars[i].score = (4 * (1/(cars[i].price/1000000))) + (3 * (cars[i].year/1000)) + (3 * (1/(cars[i].kms/1000))) 
 if(cars[i].score > car.score){ 
   car = cars[i] 
 } 
} 
console.log(car)

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

await page.goto(car.link) await page.waitForSelector('.gallery__thumbnail') await page.screenshot({path: 'result.png', fullPage: true});

и это все !

  • Документацию по Puppeter API можно найти прямо здесь!
  • Полный исходный код этого упражнения можно найти в этом репозитории Github.
  • Ага! раздел оптимизации должен быть улучшен с помощью некоторой техники машинного обучения или алгоритма оптимизации, но это ткань для другой футболки
  • Спасибо за чтение! комментарии, предложения и DM приветствуются!