Полтора года назад у меня появился пет-проект поиска книжек на популярных площадках типа 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
Установка в скриптах предельно простая:
Нажимаю «+», добавляю id скрипта: 1ReeQ6WO8kKNxoaA_O0XEQ589cIrRvEBA9qcWpNqdOP17i47u6N9M5Xh0
и он успешно импортируется в будущий скрипт:
Всё, теперь можно пользоваться функционалом.
Работа с 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 — здесь.
В строку поиска ввожу название или автора:
Результат выводится в каждый раздел:
Сортировка по цене выглядит следующим образом: