О чем этот проект

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

Проблема и первая идея

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

Куда двигаться

Так как ручной способ не помогает, мной был выбран способ автоматического получения цен. У большинства крупных магазинов есть онлайн каталог с актуальными ценами (чаще всего для доставки домой). Это стало отправной точкой для исследования, как страница открытая в браузере получает актуальные цены. В итоге нужно было ответить на вопрос, можно ли повторить механизм получения информации о цене без открытия страницы в браузере.

Исследование

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

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

https://magnit.ru/product/{product_id}-{product_name}/ - для Магнита.
https://lenta.com/product/{product_name}-{product_id}/ - для Ленты.
https://yarcheplus.ru/product/{product_name}-{product_id}/ - для Ярчи.
https://5ka.ru/product/{product_name}--{product_id}/ - для Пятерочки.

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

Из этой информации у меня получилась следующая структура:

  • Название продукта
  • Ссылка на продукт
  • Уникальное имя продукта (получается из ссылки)
  • Идентификатор продукта (аналогично получается из ссылки)

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

Наблюдая за поведением сайтов я понял, что каждый магазин реализовал это поведение по-своему. На этом этапе я начал сталкиваться с проблемами получения доступа. Я остановился на двух сайтах - Магнит и Ярче (самый простой способ получать страницу).

Магнит

На мой взгляд у этого сайта самый простой способ указать магазин, он просто указывается в виде id конкретного магазина в query параметре: shopCode=54****. Наблюдая за разными адресами в разных городах я понял, что идентификатор формируется в виде кода региона и уникального номера, но встречаются исключения… Как же их получить? Я пробовал вводить разные адреса магазинов, используя консоль отладки браузера я заметил частые запросы вида:

url: https://magnit.ru/webgate/v1/stores-facade/search
data: {
    "filters": {
        "query": "Новосибирская обл, Город, Улица, Дом",
        "storeTypeListV2": [
            "MM",
            "GM",
            "MO",
            "ME",
            "MC",
            "MM_MINI",
            "ZARYAD"
        ]
    }
}

Из url и данных понятно, что для поиска используется обычные API запросы, в примере в параметре query хранится строка - адрес магазина, а в параметре storeTypeListV2 фильтры поиска. В ответ обычно приходят следующие данные:

{
    "items": {
        "items": [
            {
                "coordinates": {
                    "latitude": 54.******,
                    "longitude": 83.******
                },
                "externalId": {
                    "owner": "OWNER_MAGNIT",
                    "storeCode": "54****"
                },
                "isActive": true,
                "isFavorite": false,
                "status": "STATUS_ACTIVE",
                "storeType": "STORE_TYPE_MM",
                "storeTypeV2": "MM"
            }
        ],
        "typeName": "basic"
    }
}

Как видно, в ответе явно присутствует код магазина (storeCode). Дополнительно упомяну, вместе с search сайт вызывает запрос detail, он аналогичный, но в ответ приходит более развернутый ответ с полным адресом и доп. информацией, но в рамках получения цены товара это не так важно.

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

Далее логичным шагом реализовать этот механизм в коде и попробовать его в деле.

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

Ярче

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

Следующие шаги

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

Итог

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

  • Одной ссылки на товар недостаточно.
  • У разных магазинов схожая система ссылок на товар.
  • У разных магазинов разный механизм указания адреса доставки/магазина.
  • Стала ясна минимальная структура, необходимая для описания модели данных.

С этим набором уже можно пробовать реализовывать MVP прототип.

Послесловие

В ходе анализа и исследования я понял, что задача вполне выполнимая и не такая крупная, как я думал вначале. Но в итоге она развилась в большой проект, который я делал на протяжении ~3 месяцев, сейчас он развился до полноценной аналитической системы и вот небольшой спойлер, о чем я хочу писать тут.

Во что это развилось
История изменения
История изменения
График цены
График цены
Изменения цен по категориям
Изменения цен по категориям