Привет! Маркетплейсы очень сильно изменили нашу жизнь, сделав ее супер удобной. Это классно, но думаю всем знакома картина, когда добавил товар в корзину, отвлекся, а он уже на 500р дороже. Или дешевле. Или вообще продается на косарь меньше на другом маркетплейсе. Ах да, как насчет «зачеркнутых выгодных» цен вида ̶1̶7̶0̶0̶0̶ 800р?

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

Пример интерфейса, мы еще к нему вернемся
Пример интерфейса, мы еще к нему вернемся

Приступим!

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

Пока космические AI-корабли бороздят просторы вселенной, а именно — весь мир рассуждает, как изменится экономика планеты и паттерны покупок и насколько сильно они изменятся AI-агентами (которые будут честно сами ходить и искать нам лучшие предложения, отчаянно торгуясь и отстаивая свою точку зрения через MCP или A2A с поставщиками), покупать на этой бирже товаров что-то на сотку дешевле хочется уже сейчас.

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

Знакомо, д��?
Знакомо, да?

Давайте посмотрим ̶к̶а̶к̶ ̶я̶ ̶с̶о̶б̶р̶а̶л̶ ̶с̶о̶б̶с̶т̶в̶е̶н̶н̶о̶г̶о̶ ̶A̶I̶-̶а̶г̶е̶н̶т̶а̶, что с этим можно сделать.

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

Ключевые требования и парсинг

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

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

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

Вот наши ключевые требования:

  • Нам нужен залогиненный в мой акк браузер

  • Нужно периодически обновлять корзину (по сути — просто заходить на страницу корзины каждый раз)

  • Парсить ее содержимое, различать что изменилось

  • Нужен какой-то автоматический кросс-маркетплейсный матчинг

  • Нужна БД и какой-то приниматор решения, которому не надо детально ставить задачу

  • Нужен крон и выполнятор логики

  • ТГ-бот: ну куда же без уведомляхи в тг-бота

Все это нужно проделать для трех маркетплейсов (озон и я.маркет — мои основные, плюс немножко wb).

Звучит как задачка на несколько часов (так и вышло, а на пост ушло сильно-сильно больше). God save the LLMs&Cursor.

Делаем себе реальный браузер

Итак, нам нужен браузер. Мой самый любимый инструмент — это питоновская либа Playwright. В нем можно запустить браузер, понасохранять в него что угодно, а потом в таком состоянии запускать его серверно (через xvfb, например), проделывая любые визуальные действия, которые будут реальными действиями из реального браузера. Здесь есть социальный договор — ты не создаешь проблем маркетплейсам, они не банят тебя, поэтому для нашей задачи годится.

Для начала нужно запустить из кода визуальный браузер с gui, чтобы честно авторизоваться и сохранить честные и настоящие куки и в целом обозначиться. При первом старте мы видим «antibot challenge page», которую быстро проходим и попадаем на главную страницу, потому что, перефразируя классиков, «кто ж его заблочит — он же браузер!».

Это шутка. Кажется, ну а что они нам сделают — это же настоящий браузер с руками-ногами! Но на самом деле антибот-системы отлично умеют палить любые сценарии: headless-браузеры, одинаковые фингерпринты, слишком ровные движения мышки и чего только нет. Антибот-с��стемы делают очень умные ребята и они, конечно же, про все это в курсе. И мы для них скорее «неуловимый джо» из анекдота — отловить можно, но смысла нет. И это ровно то, что нам нужно.

В нашем случае мы имитируем сценарий «честный пользователь с закладкой на корзину» и это действие от полноценного пользователя, банить нас пока не за что. Фермы ботов делаются схожими, но более профессиональными способами, им долго добавляют поведенческие паттерны, историю, разные fingerprints, эмуляторы устройств ios/android и прочее разное сложное прикольное (там вечная борьба щита и меча). По таким же эвристикам могут отловить и нас, но как будто бы это еще надо заслужить.

Ок, мы пробились, авторизовались, а дальше заходим несколько раз в день (ну все как в жизни, ну), создавая минимальную нагрузку, но получая все, что нам нужно.

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

Ну, конечно же, это шутейка. Все что нам нужно — это годами проверенный дедовский способ — один мутный скрипт, который делает все, что нужно.

Как парсить — да через любой парсер просто цепляясь за нужные теги. Но здесь очень-очень важно то, что парсить можно только после полного рендеринга DOM и какого-то ощутимого таймаута после него (чтобы избежать lazy-load чего-то или shadow DOM), поэтому playwright должен все это проделать и скидывать финальный html. Иногда быстрее (но не всегда лучше) вытащить из XHR, но для нашего случая сейчас — пойдет. И так же важно сделать простой sanity-check на стабильность нужного блока с товарами, чтобы в случае изменений на маркетплейсах мы о них быстро узнавали.

Итого, у нас уже есть возможность сохранять наши товары в базу (взята SQLite за ее простоту). Табличка одна (для упрощения логики) и тривиальная: название, цена, дата, маркетплейс, упакованным текстом важные описашки (типа скидок, последних товаров и тд), активность 0/1. Тут важно сохранять именно движение цены, но и любое движение цены нам тоже не важно — первый прогон показал, что цены могут меняться даже на 2р, что не годится — поэтому % от цены не меньше какого-то фикса типа 20р.

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

При запуске скрипт получает html корзин со всех маркетплейсов, разбирает все на товары и вставляет в базу прям как есть. Если я что-то купил, то товара в корзине уже нет, оно зануляет активность и товар перестает мониториться для всех.

Ок, цены и товары есть. И тут мы по касательной встречаемся с одной из главных сложностей маркетплейсов — матчингом. Матчингом и поиском в маркетплейсах занимаются тоже чертовски умные ребята и там проблем и без нас хватает. Наши же масштабы сведения товаров прям крошечные и тут нам помогут LLM.

Здесь нужно сделать микро-отступление про LLM.

""" начало отступления

LLMки сейчас пихают (или даже скорее «пытаются запихнуть») куда только не попадя, в том числе туда, где неплохо справится и классический NLP ̶,̶ ̶н̶у̶ ̶в̶о̶т̶ ̶и̶ ̶я̶ ̶т̶о̶ж̶е̶ ̶з̶а̶п̶и̶х̶н̶у̶л̶. На самом деле, у меня было именно осознанное решение, потому что я обожаю LLMки за то, что они дают единый интерфейс к большому количество решаемых задач. Да, для многих штук LLM в проде это настоящий оверкил, но для прототипов, концептов и небольшого софтика — лучше не придумать. Их сила в том, что единым типовым запросом по API можно порешать огромное количество разных штук, которые пришлось бы упорно писать самим.

Да, если концепт вырастает в нечто большое — окей, разбираем все по кирпичикам, делаем промышленно и оптимизируем. Но иногда проекту просто не суждено вырасти (например, какой-нибудь сгружатор тикетов из гитлаба для небольшой команды), у него by design нет нагрузки и больших данных, но какую-то полезную бизнес-функцию он все же выполняет.

Кстати, здесь ровно такой случай: есть мини-матчинг и затем принятие решения.

Для своих всяких штучек-экспериментов я обожаю mini-версии флагманских LLM, так как они мощные, дешевые, легко подключаемые и легко доступные по API. Production-ready magic box.

конец отступления """

Интерфейс и подключение LLM

Сначала про данные. Мы знаем, что товары по своей сути одинаковые, но называться они могут максимально по-разному и без какой-либо обработки строк LLM это все легко съест. То есть, нам достаточно иметь в базе «Соус Kikkoman Соевый натурально сваренный, диспенсер, 150 мл» и «Соевый соус Kikkoman классический, 150 мл., Япония», а дальше все это кидануть в ллмку, она разберется. Никакой обработки, автоматический «матчинг» из коробки, если это можно так назвать.

Чтобы понимать, что мы вообще мониторим, можно собрать два простеньких контейнера фронт-бек для показа товаров и отдельную историю цен по каждому товару. Разворачиваться мне есть где, поэтому проблемы инфры не стоит. И да, цены все в интах, чтобы помочь ллмке разобраться в ее любимой проблеме 9.9 / 9.11.

Сталкеринг цены конкретного товара
Сталкеринг цены конкретного товара

К товарам в корзине и к такой динамике цен можно написать текущий промпт из моих пожеланий — он накладывается на хорошо подготовленный системный промпт.

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

Для примера: «дождись цены на кофе меньше 1000р», «пришли когда соус подешевет на 150р» (куплю ящик), «пришли если на тумбу будет скидка больше 5%», «пришли товар, который сильно подорожал» (нет особо времени ждать, куплю аналог), «вся корзина дешевле на 500». Ну кайф же.

Теперь заглавный скрин выглядит попонятнее
Теперь заглавный скрин выглядит попонятнее

В итоге в LLM собирается гранд-промпт: системный промпт, корзина товаров, история цен, текущий промпт под задачу. Все запромчено так, что бы возвращалось либо 0, либо расшифровка того, что нужно сделать и послать в тг: эй, тут щепа подешевела до 2 тыщ как ты хотел, можно и брать. Да, стоит просить присылать json, чтобы дополнительно ответ отфильтровать.

Пример того, как промптится корзина и динамика цен
Пример того, как промптится корзина и динамика цен

Если мы готовы сталкерить товар очень долго, то вместо просто списка цен подойдут какие-то фичи из цен.

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

Вот так:

Общая полученная схема
Общая полученная схема

Экономика: типичный запрос для трех товаров около 700-1000 токенов (90% in и 10% out), цена gemini 2.0 flash-lite 1M токенов $0.1 in / $0.40 out, три запуска в день это грубо 3к токенов, 30 дней в месяц. И мы получаем что-то около $0.012 за месяц, со всеми накладными расходами — полтора рубля. Значит, как только мы экономим 2 рубля, ̶м̶ы̶ ̶п̶о̶к̶а̶з̶ы̶в̶а̶е̶м̶ ̶п̶р̶и̶б̶ы̶л̶ь̶ ̶и̶н̶в̶е̶с̶т̶о̶р̶а̶м̶ ̶и̶ ̶в̶ы̶х̶о̶д̶и̶м̶ ̶н̶а̶ ̶I̶P̶O̶, система экономически целесообразна 🤣

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

А пока писал пост, на почти две сотки отследил снижение. Современные проблемы требуют современных решений!

Спасибо!

P.S.: мне нравится писать всякое разное, но большие статьи отнимают очень много времени, поэтому я совсем недавно завел себе крошечный канальчик в тг Agentic World, в котором хочу делиться более короткими заметочками про ии, агентов, продукты и людей. И буду очень рад вашей подписке 🫶

Мои другие статьи:

Бенчмарк качества распознавания речи (ASR) в телефонии: как мы сравниваемся с Whisper, GigaAM и T-One

Переизобретая аналитику будущего: как и почему LLM-агенты меняют анализ продуктов, но все не так просто