Михаил Сисин Со-основатель облачного сервиса по сбору информации и парсингу сайтов Diggernaut. Работает в области сбора и анализа данных, а также разработки систем искусственного интеллекта и машинного обучения  более десяти лет.

Учимся парсить данные с площадки eBay

Парсер для eBay

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

Сайт eBay достаточно простой, не использует для показа страниц Javascript, и технических проблем для его парсинга быть не должно. Однако надо знать, что на выдачу данных по запросу на Ebay есть лимит. Поэтому, если вы хотите собрать все результаты, вам придется строить запросы таким образом, чтобы поисковый запрос или фильтрация возвращала такое количество объявлений, которое бы не превышало лимит.

Допустим, мы хотим продавать электронные книги. Попробуем найти нужную нам категорию и настроить фильтры. В Ebay есть категория Tablets & eReaders. Именно она нам и нужна как отправная точка. Откроем эту страницу в Google Chrome.

eBay - Tablets and eReaders

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

eBay - more filters

Там нам нужно найти фильтр “Type”, выбрать в нем опцию “eBook Reader” и нажать на кнопку “Apply”.

eBay - устанавливаем фильтр на тип

Мы видим, что товаров стало значительно меньше, однако нас интересуют только новые устройства. Поэтому нам нужно выбрать в фильтре “Condition” опцию “New”.

eBay - выбираем только новые товары

Также нас не интересуют аукционы, поэтому над блоком с листингами выберем опцию “Buy It Now”.

eBay - выбираем не аукционы

Теперь для экономии на запросах к серверу, увеличим количество выводимых результатов на странице. По умолчанию, eBay выводит 50 листингов. Мы можем выбрать 200, что сэкономит наши затраты на прохождение всего каталога отфильтрованной категории в 4 раза. Для этого под блоком с результатами надо выбрать количество выводимых результатов: 200.

eBay - увеличиваем количество выводимых листингов

После того, как мы установили все фильтры и опции, нужно скопировать URL из адресной строки:
https://www.ebay.com/sch/Tablets-eBook-Readers/171485/i.html?_dcat=171485&_fsrp=1&_sacat=171485&rt=nc&Type=eBook%2520Reader&LH_ItemCondition=1000&LH_BIN=1&_ipg=200

Это и будет наш стартовый URL.

Далее нам надо отключить JS на странице. Делать мы это будем как обычно, при помощи расширения для Google Chrome: Quick Javascript Switcher. Далее откроем инструменты разработчика в Google Chrome, нажав клавиши Ctrl + Shift + I. После чего, используя инструмент для выбора элементов, найдем нужные нам блоки на странице и CSS селекторы для них. Во-первых, нас интересует блок с листингом, а во-вторых – ссылка на следующую страницу.

eBay - инструменты разработчика

Сначала соберем все блоки с листингами, то есть определим CSS селектор для них.

eBay - определяем CSS селектор для блока с товаром

CSS селектор: ul > li.s-item. Для проверки сделаем поиск в секции “Elements” (нажав в ней Ctrl +F). И удостоверимся, что селектор выбирает все листинги. Мы увидим, что выбралось более 200 элементов, хотя должно быть 200 ровно. Это произошло, потому что Ebay, кроме найденных листингов, также показывает нам рекламные (Sponsored).

eBay - коммерческие листинги товаров

Отфильтровать их будет непросто, но мы попробуем это сделать. Нажмем на одну из букв в слове “SPONSORED” и посмотрим какой элемент откроется в окне “Elements”. Мы увидим набор элементов span, содержащих случайный набор символов.

eBay - метка SPONSORED

Мы видим, что у этих элементов разные классы. Вполне понятно, что “SPONSORED” на странице показывается за счет того, что один из классов показывается, а второй нет. Но мы не можем просто взять нужный нам класс как константу. Потому что, судя по имени класса, он не статичный, а генерируется. Поэтому на его имя мы не можем полагаться. Однако, если один из классов показывается, а второй нет, где-то на странице должен быть CSS который устанавливает это правило. Чтобы его найти, сделаем поиск в элементах по имени класса и обнаружим, что на странице присутствует следующий код:

<style type="text/css">
span.s-m1yuhh {
    display: inline;
}
span.s-o2xlx7k {
    display: none;
}
</style>

Наша задача теперь, сконструировать для него селектор и проверить его в окне “Elements”, удостоверившись, что селектор выберет только 1 элемент на странице.

style:contains(“display: inline;”) – такой селектор будет прекрасно работать для нашей задачи.

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

span\.([^\s\{]+)\s*\{\s*display\:\s*inline;

Используя это регулярное выражение , мы извлечем имя класса: s-m1yuhh.
Теперь, когда у нас есть класс, мы можем зайти в элемент span[role=”text”] и удалить все элементы span с классом отличным от нашего класса (который мы предварительно запишем в переменную class). Сделать это можно с помощью команды node_remove:

- node_remove: span:not(.<%class%>)

После чего нужно использовать команду parse и сравнить результат в регистре со строкой “SPONSORED”, используя команду if.

Таким образом мы сможем отсеять коммерческие листинги.

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

eBay - CSS селекторы для данных с блоке товара

С этой страницы мы соберем:

  • название товара: h3.s-item__title
  • цену товара: span.s-item__price
  • стоимость доставки: span.s-item__shipping.s-item__logisticsCost
  • рейтинг листинга: div.b-starrating > span.clipped
  • количество отзывов: span.s-item__reviews-count > span:not(.clipped)
  • количество людей, отслеживающих этот листинг: span.s-item__hotness:contains(“Watching”)
  • количество проданных товаров: span.s-item__hotness:contains(“Sold”)
  • ссылка на товар: a.s-item__link

В рамках этот статьи мы ограничимся сбором данных со страницы каталога, не заходя на страницу с товаром. Вы можете самостоятельно улучшить парсер eBay, добавив логику парсинга страницы товара. Для этого вы должны использовать команду walk для перехода на страницу с товаром и команды find для перехода по блокам, используя CSS селекторы.

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

eBay - CSS селектор перехода на следующую страницу

На изображении видно, что нам подойдет селектор a[rel=»next»]. Он один на странице, и все что нам остается сделать – отпарсить аттрибут href и положить значение в пул линков.

Мы готовы писать конфигурацию диггера:

---
config:
    debug: 2
    agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
do:
# Добавляем стартовый URL в пул линков
- link_add:
    url:
    - https://www.ebay.com/sch/Tablets-eBook-Readers/171485/i.html?_dcat=171485&_fsrp=1&_sacat=171485&rt=nc&Type=eBook%2520Reader&LH_ItemCondition=1000&LH_BIN=1&_ipg=200
# Начинаем итерировать по пулу линков и заходим на страницы в пуле
- walk:
    to: links
    do:
    # Очищаем переменные
    - variable_clear: class
    # Находим ссылку на следующую страницу
    - find:
        path: a[rel="next"]
        do:
        # Парсим значение в аттрибуте href
        - parse:
            attr: href
        # Делаем стандартную очистку данных в регистре
        - space_dedupe
        - trim
        # Проверяем, если ли какое-либо значение в регистре
        - if:
            match: \w+
            do:
            # Добавляем значение регистра в пул линков
            - link_add
    # Находим блок с CSS для извлечения класса отметки SPONSORED
    - find:
        path: 'style:contains("display: inline;")'
        do:
        # Парсим имя класса и записываем его в переменную
        - parse:
            filter: 'span\.([^\s\{]+)\s*\{\s*display\:\s*inline;'
        - variable_set: class
    # Находим блоки с листингами
    - find:
        path: ul > li.s-item
        do:
        # Очищаем переменные
        - variable_clear: sponsored
        # Определяем, листинг SPONSORED или нет
        - find:
            path: span[role="text"]
            do:
            - node_remove: span:not(.<%class%>)
            - parse
            - space_dedupe
            - trim
            - if:
                match: SPONSORED
                do:
                - variable_set:
                    field: sponsored
                    value: 1
        # Если листинг SPONSORED - игнорируем его
        - variable_get: sponsored
        - if:
            match: 1
            else:
            # Это обычный листинг, собираем данные
            # Создаем объект для хранения данных
            - object_new: item
            # Собираем название товара
            - find:
                path: h3.s-item__title
                do:
                - parse
                - space_dedupe
                - trim
                - object_field_set:
                    object: item
                    field: name
            # Собираем цену товара
            - find:
                path: span.s-item__price
                do:
                # Парсим только цифры, включаем регулярное выражение для случаев когда указано 2 цены
                - parse:
                    filter:
                    - \$([0-9\.]+)\s+to
                    - \$([0-9\.]+)
                # Проверяем есть ли в регистре цифры
                - if:
                    match: \d+
                    do:
                    # Сохраняем значение в поле price как float
                    - object_field_set:
                        object: item
                        field: price
                        type: float
            # Собираем стоимость доставки
            - find:
                path: span.s-item__shipping.s-item__logisticsCost
                do:
                # Проверяем бесплатная доставка или нет
                - parse
                - if:
                    match: Free
                    do:
                    - register_set: 0.0
                    - object_field_set:
                        object: item
                        field: delivery
                        type: float
                    else:
                    # Парсим цену доставки
                    - parse:
                        filter:
                        - \$([0-9\.]+)
                    - if:
                        match: \d+
                        do:
                        - object_field_set:
                            object: item
                            field: delivery
                            type: float
            # Собираем рейтинг товара
            - find:
                path: div.b-starrating > span.clipped
                do:
                # Парсим только цифры рейтинга
                - parse:
                    filter: ([0-9\.]+)\s+out
                - if:
                    match: \d+
                    do:
                    - object_field_set:
                        object: item
                        field: rating
                        type: float
            # Собираем количество отзывов
            - find:
                path: div.b-starrating > span.clipped
                do:
                # Парсим только цифры
                - parse:
                    filter: (\d+)
                - if:
                    match: \d+
                    do:
                    - object_field_set:
                        object: item
                        field: reviews
                        type: int
            # Собираем количество людей, отслеживающих листинг
            - find:
                path: span.s-item__hotness:contains("Watching")
                do:
                # Парсим только цифры
                - parse:
                    filter: (\d+)
                - if:
                    match: \d+
                    do:
                    - object_field_set:
                        object: item
                        field: watching
                        type: int
            # Собираем количество проданных товаров
            - find:
                path: span.s-item__hotness:contains("Sold")
                do:
                # Парсим только цифры
                - parse:
                    filter: (\d+)
                - if:
                    match: \d+
                    do:
                    - object_field_set:
                        object: item
                        field: sold
                        type: int
            # Ссылка на товар
            - find:
                path: a.s-item__link
                do:
                - parse:
                    attr: href
                - space_dedupe
                - trim
                - if:
                    match: \w+
                    do:
                    - normalize:
                        routine: url
                    - object_field_set:
                        object: item
                        field: url
            # Сохраняем объект
            - object_save:
                name: item
    # Делаем паузу между заборами страниц
    - sleep: 3

Михаил Сисин Со-основатель облачного сервиса по сбору информации и парсингу сайтов Diggernaut. Работает в области сбора и анализа данных, а также разработки систем искусственного интеллекта и машинного обучения  более десяти лет.

One Reply to “Учимся парсить данные с площадки eBay”

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *