Cheerio в скриптах или как можно парсить «ещё»…

Полтора года назад у меня появился пет-проект поиска книжек на популярных площадках типа labirint.ru. Проект пережил несколько версий, в основном потому, что не получалось одинаково парсить данные с разных интернет магазинов.

Недавно я наткнулся на Cheerio — HTML-парсер, API которого устроено так же, как API jQuery. jQuery — набор функций JavaScript, фокусирующийся на взаимодействии JavaScript и HTML. Библиотека jQuery помогает легко получать доступ к любому элементу DOM, обращаться к атрибутам и содержимому элементов DOM, манипулировать ими.

То есть, если понятным языком, то Cheerio позволяет получить html страницу в виде DOM объекта.

Cheerio для google apps script

Стандартно он ставится через npm — npm install cheerio — что в случае с гугл скриптами не рабочий вариант. Однако на гитхабе есть версия для наших целей : https://github.com/tani/cheeriogs

Установка в скриптах предельно простая:

google script add library

Нажимаю «+», добавляю id скрипта: 1ReeQ6WO8kKNxoaA_O0XEQ589cIrRvEBA9qcWpNqdOP17i47u6N9M5Xh0

library with script id

и он успешно импортируется в будущий скрипт:

script added

Всё, теперь можно пользоваться функционалом.

Работа с dom объектом

Получение объекта осуществляется через стандартный фетч и загрузку его в cheerio:

function getLabirintTable(request) {
  const shopTitle = "Labirint"

  const url = `https://www.labirint.ru/search/${request}/?stype=0&display=table&available=1&paperbooks=1`
  
  const response = UrlFetchApp.fetch(url);
  const responseContent = response.getContentText();

  const $ = Cheerio.load(responseContent)
}

(request) передаю в функцию как параметр, потому что получать его буду из web app.

Собственно вся магия преобразования в объект происходит здесь: const $ = Cheerio.load(responseContent)

Дальше указываю, что именно надо искать в dom дереве. В коде ниже это название книги, цена книги и изображение (обложка) книги:

const bookTitle = 'div.b-search-page-content > div.content-block > table > tbody > tr > td.col-sm-4 > div > a'
  const bookPrice = 'div.b-search-page-content > div.content-block > table > tbody > tr > td.products-table__price.col-sm-1 > div > div > span.price-val > span'
  const bookImage = 'div.b-search-page-content > div.content-block > table > tbody > tr > td.products-table__buy.col-sm-2 > div > div > div.fleft.product-icons-outer > div > div > a.icon-compare.track-tooltip.js-open-actions-block'

то есть по этим путям в объекте будет искаться необходимый контент.

Например, я хочу дернуть из Лабиринта книжки по поиску «Корчак как любить ребенка». Немного изменю код, для примера:

function getLabirintTable() {
  const request = "Корчак как любить ребенка"
  const shopTitle = "Labirint"

  const url = `https://www.labirint.ru/search/${request}/?stype=0&display=table&available=1&paperbooks=1`
  
  const response = UrlFetchApp.fetch(url);
  const responseContent = response.getContentText();

  const $ = Cheerio.load(responseContent)

  const bookTitle = 'div.b-search-page-content > div.content-block > table > tbody > tr > td.col-sm-4 > div > a'
  const bookPrice = 'div.b-search-page-content > div.content-block > table > tbody > tr > td.products-table__price.col-sm-1 > div > div > span.price-val > span'
  const bookImage = 'div.b-search-page-content > div.content-block > table > tbody > tr > td.products-table__buy.col-sm-2 > div > div > div.fleft.product-icons-outer > div > div > a.icon-compare.track-tooltip.js-open-actions-block'

  // Получаю массив названий книг
  const bookTitleArr = []
  for (let title of $(bookTitle)) {
    bookTitleArr.push(title.attribs.title)
  }

  console.log(bookTitleArr)
}

и в результате получу массив с названиями книг:

массив с названиями книг

Для того, чтобы понять откуда берутся названия в детали получаемого dom объекта и искать их. В данном случае названия были в title.attribs.title

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

Подход в сборе остальных деталей по книге аналогичен описанному выше. Финальный код поиска по Лабиринту будет следующим:

function getLabirintTable() {
  const request = "Корчак как любить ребенка"
  const shopTitle = "Labirint"

  const url = `https://www.labirint.ru/search/${request}/?stype=0&display=table&available=1&paperbooks=1`
  
  const response = UrlFetchApp.fetch(url);
  const responseContent = response.getContentText();

  const $ = Cheerio.load(responseContent)

  const bookTitle = 'div.b-search-page-content > div.content-block > table > tbody > tr > td.col-sm-4 > div > a'
  const bookPrice = 'div.b-search-page-content > div.content-block > table > tbody > tr > td.products-table__price.col-sm-1 > div > div > span.price-val > span'
  const bookImage = 'div.b-search-page-content > div.content-block > table > tbody > tr > td.products-table__buy.col-sm-2 > div > div > div.fleft.product-icons-outer > div > div > a.icon-compare.track-tooltip.js-open-actions-block'

  // Получаю массив названий книг
  const bookTitleArr = []
  for (let title of $(bookTitle)) {
    bookTitleArr.push(title.attribs.title)
  }

  const bookPriceArr = []
  for (let price of $(bookPrice)) {
    bookPriceArr.push(parseInt((price.children[0].data).split(/\s+/).join("")))
  }

  const bookImageArr = []
  const bookLinkArr = []
  for (let image of $(bookImage)) {
    bookImageArr.push(image.attribs['data-image'])
    bookLinkArr.push(image.attribs['data-url'])
  }
  //--------------------- Финальный массив со всеми данными
  const finalArr = []
  for (let i = 0; i < bookTitleArr.length; i++) {
    finalArr.push([bookTitleArr[i], bookPriceArr[i], bookLinkArr[i], bookImageArr[i], shopTitle])
  }

  // Сортировка массива по цене
  finalArr.sort((a, b) => a[1] - b[1])
  console.log(finalArr)
  const tableHtml = convertToTags(finalArr)

  return [tableHtml, finalArr]
}

Результат будет выглядеть следующим образом:

полный массив со всеми данными по книгам

Может вызвать вопрос следующая строчка — convertToTags(finalArr)

На самом деле это всего лишь еще одна функция которая переводит массив в набор тегов для загрузки в веб апп:

//Перевод массива в теги
function convertToTags(array){
return array.map(elem => `<tr>
                  <td style="width: 600px">${elem[0]}</td>
                  <td>${elem[1]}</td>
                  <td>
                  <a href="${elem[2]}" target="_blank">Ссылка</a>
                  </td>
                  <td><img src="${elem[3]}" width="60">
                  </tr>`).join("")
}

Результат

Реализован поиск по интернет магазинам : Лабиринт, Республика, Буквоед и Майшоп. Так же есть сортировка по возрастанию цены.

Ссылка на web app — здесь.

web app поиска

В строку поиска ввожу название или автора:

web app с запросом поиска

Результат выводится в каждый раздел:

web app с результатом поиска

Сортировка по цене выглядит следующим образом:

web app с сортировкой

Добавить комментарий