Telegram-бот для мониторинга цен конкурентов и автообновления цены товара через API продавца.
| Engineering focus | Reliability controls | Verification |
|---|---|---|
| Independent GGSEL and DigiSeller profiles, competitor parsing and price automation | Price floors, rate limits, cooldowns, idempotent updates and safe parser fallbacks | Docker deployment, GitHub Actions and a pytest suite covering core behavior |
Start here: customer guide · deployment · tests · source
Простая инструкция для заказчика (без тех. терминов):
Поддерживаются независимые профили:
ggseldigiseller
Для каждого профиля хранятся отдельно:
- API-ключи/токен
- основной ID товара
- список отслеживаемых товаров (
tracked_products) - список конкурентов
- runtime-настройки
- state/история/алерты
- В боте нажмите
🧩 Профильи выберите площадку (GGSELилиDIGISELLER). - Нажмите
📦 Товары. - Добавьте товар и конкурента одной строкой:
<product_id> <url_конкурента>- пример:
4697439 https://ggsel.net/catalog/product/102124601
- Нажмите
⚙ Настройкии проверьте, что показан нужныйАктивный товар. - Включите
🔔 Автоцена. - Выберите режим:
Следование— цена как у конкурента.Демпинг— чуть ниже конкурента.Повышение— чуть выше конкурента.
- При необходимости задайте основные лимиты:
📉 Мин— минимальная цена.📈 Макс— максимальная цена.↘️ Шаг-и↗️ Шаг+— шаг изменения цены.🔘 Округление— шаг округления (например0.01или0.0001).
- Откройте
📊 Статуси убедитесь, что:- URL конкурента правильный.
- Цена конкурента парсится.
- Режим и лимиты те, которые вы выставили.
Важно:
- Все настройки применяются только к активному товару (текущей паре товар↔конкурент).
- Переключение товара (
⬅/➡) меняет только товар для редактирования, а не профиль. - Авто-инструкции держите выключенными, пока не начнётся отдельное тестирование.
Базовое правило цены:
my_price = competitor_min - UNDERCUT_VALUE- по умолчанию
UNDERCUT_VALUE=0.0051 - пример: конкурент
0.3400-> мы0.3349
Ограничения и защита:
MIN_PRICE,MAX_PRICEMODE=FOLLOW|DUMPING|RAISEFOLLOW: ставить ровно цену конкурента (4 знака)DUMPING:round(конкурент, 2) - 0.0051RAISE:round(конкурент, 2) + 0.0049MAX_DOWN_STEPдля ограничения резкого паденияFAST_REBOUND_DELTA+ bypass cooldown для быстрого отката вверх- при
POSITION_FILTER_ENABLED=trueиrank=N/Aвключается эвристикаWEAK_UNKNOWN_RANK_*(абсолютный/относительный gap между 1-й и 2-й ценой), чтобы не демпинговать за "слабым" конкурентом
Идемпотентность апдейтов:
- при неизменной цене конкурента повторный API update не выполняется
- если целевая цена уже была применена, бот делает
skip(без лишнего шума) - если у профиля пустой список конкурентов, цикл делает безопасный
skipбез отправки error-алертов - профиль может работать и без
COMPETITOR_URLS(ручные операции и API smoke)
Точность цен:
- расчёт/сохранение/отображение в боте:
4знака после запятой - в GGSEL update payload цена отправляется в формате
0.0000 - API чтение у площадки может возвращать округлённое значение, это учитывается
Pipeline:
stealth_requests+ HTML (BeautifulSoup)- извлечение unit-price (
unitsToPay / unitsToGet) если доступно - fallback по CSS-селекторам цены
- fallback на публичный endpoint:
https://api4.ggsel.com/goods/<id>(используется только для доменовggsel.*)
Что пишется в state:
last_competitor_minlast_competitor_urllast_competitor_methodlast_competitor_parse_at- ошибки/причины блокировок парсера
Поведение cookies:
- бот синхронизирует cookies из
.envна каждом цикле без рестарта - если cookies протухли и парсинг без cookies успешен, runtime cookies очищаются автоматически (чтобы не повторять битый запрос)
- если retry без cookies тоже неуспешен, stale runtime cookies сбрасываются,
чтобы в следующем цикле не застревать на
401/403с тем же значением - путь к env-файлу можно переопределить через
ENV_FILE_PATH
python3 -m venv .venv
source .venv/bin/activate
# runtime deps
pip install -r requirements.txt
# для запуска тестов/линтеров
pip install -r requirements-dev.txt
cp .env.example .envМинимум в .env:
TELEGRAM_BOT_TOKENTELEGRAM_ADMIN_IDS- для включённого профиля:
*_API_KEY/*_ACCESS_TOKEN,*_SELLER_ID,*_PRODUCT_ID - если
*_API_KEYэто JWT access token, задайте*_API_SECRETдляApiLogin(автообновление токена) GGSEL_COMPETITOR_URLSи/илиDIGISELLER_COMPETITOR_URLS(для GGSEL есть fallback наCOMPETITOR_URLSдля обратной совместимости)- cookies конкурента:
GGSEL_COMPETITOR_COOKIES/DIGISELLER_COMPETITOR_COOKIES(если не заданы, используется общийCOMPETITOR_COOKIES) - при нестандартном запуске можно явно задать
ENV_FILE_PATH
Если у включённого профиля не задан *_PRODUCT_ID, такой профиль не
запускается (fail-safe защита от шумных циклов и пустых API-обновлений).
Профильные дефолты DigiSeller (опционально):
DIGISELLER_MIN_PRICEDIGISELLER_MAX_PRICEDIGISELLER_DESIRED_PRICEDIGISELLER_UNDERCUT_VALUEDIGISELLER_MODEDIGISELLER_WEAK_PRICE_CEIL_LIMITDIGISELLER_POSITION_FILTER_ENABLEDDIGISELLER_WEAK_POSITION_THRESHOLDDIGISELLER_WEAK_UNKNOWN_RANK_ENABLEDDIGISELLER_WEAK_UNKNOWN_RANK_ABS_GAPDIGISELLER_WEAK_UNKNOWN_RANK_REL_GAPDIGISELLER_CHECK_INTERVALDIGISELLER_FAST_CHECK_INTERVAL_MINDIGISELLER_FAST_CHECK_INTERVAL_MAXDIGISELLER_COOLDOWN_SECONDSDIGISELLER_IGNORE_DELTADIGISELLER_NOTIFY_SKIPDIGISELLER_NOTIFY_SKIP_COOLDOWN_SECONDSDIGISELLER_NOTIFY_COMPETITOR_CHANGEDIGISELLER_COMPETITOR_CHANGE_DELTADIGISELLER_COMPETITOR_CHANGE_COOLDOWN_SECONDSDIGISELLER_UPDATE_ONLY_ON_COMPETITOR_CHANGEDIGISELLER_NOTIFY_PARSER_ISSUESDIGISELLER_PARSER_ISSUE_COOLDOWN_SECONDSDIGISELLER_HARD_FLOOR_ENABLEDDIGISELLER_MAX_DOWN_STEPDIGISELLER_FAST_REBOUND_DELTADIGISELLER_FAST_REBOUND_BYPASS_COOLDOWN
Эти значения применяются только если соответствующий runtime-ключ ещё не был
задан ранее в БД (runtime_settings).
Общий флаг уведомлений об ошибках в Telegram:
NOTIFY_ERRORS=false— не отправлять❌ Ошибкав Telegram, писать только в серверные логи.
Минимальный набор переменных:
DIGISELLER_ENABLED=trueDIGISELLER_API_KEY(илиDIGISELLER_ACCESS_TOKEN)DIGISELLER_API_SECRET(еслиDIGISELLER_API_KEYхранится как JWT)DIGISELLER_SELLER_IDDIGISELLER_PRODUCT_ID
Рекомендуемо сразу указать:
DIGISELLER_COMPETITOR_URLS(если нужен авто-режим мониторинга)DIGISELLER_REQUIRE_API_ON_START=true(чтобы процесс не стартовал с битым API)
Опционально для авто-инструкций в переписке заказа (DigiSeller):
DIGISELLER_CHAT_AUTOREPLY_ENABLED=trueDIGISELLER_CHAT_AUTOREPLY_PRODUCT_IDS=5077639,5104800DIGISELLER_CHAT_AUTOREPLY_INTERVAL_SECONDS=30DIGISELLER_CHAT_AUTOREPLY_DEDUPE_BY_MESSAGES=trueDIGISELLER_CHAT_AUTOREPLY_ONLY_EMPTY_CHAT=trueDIGISELLER_CHAT_AUTOREPLY_REQUIRE_RULES=trueDIGISELLER_CHAT_AUTOREPLY_ALLOW_CUSTOM_TEXT=falseDIGISELLER_CHAT_AUTOREPLY_ALLOW_TEMPLATE_FALLBACK=falseDIGISELLER_CHAT_AUTOREPLY_LOOKBACK_MESSAGES=30DIGISELLER_CHAT_AUTOREPLY_SENT_TTL_DAYS=30DIGISELLER_CHAT_AUTOREPLY_CLEANUP_EVERY_HOURS=24DIGISELLER_CHAT_TEMPLATE_RU_ALREADY,DIGISELLER_CHAT_TEMPLATE_RU_ADDDIGISELLER_CHAT_TEMPLATE_EN_ALREADY,DIGISELLER_CHAT_TEMPLATE_EN_ADD
Если шаблоны не заданы, бот берёт текст из полей товара:
- для RU:
info_ru/instruction_ru/add_info_ruс fallback наinfo/instruction/add_info - для EN:
info_en/instruction_en/add_info_enс fallback наinfo/instruction/add_info
Для режима добавит приоритет у add_info*, иначе у info*.
Инструкция отправляется для каждого нового заказа (order_id) отдельно.
Для одного и того же заказа бот отправляет инструкцию только один раз
(антидубль по order_id + dedupe по тексту в истории сообщений).
Если включён *_CHAT_AUTOREPLY_ONLY_EMPTY_CHAT=true, бот отправляет инструкцию
только в пустой чат заказа (без предыдущих сообщений).
Перед отправкой бот проверяет права chat API; при нехватке прав
отправка не выполняется, причина пишется в /diag (Chat perms).
При *_CHAT_AUTOREPLY_ALLOW_CUSTOM_TEXT=false кастомные тексты правил
игнорируются (в чат уйдёт только спарсенная инструкция товара по выбранному параметру).
При *_CHAT_AUTOREPLY_ALLOW_TEMPLATE_FALLBACK=false шаблонные fallback-сообщения
не используются.
Если в заказе у выбранного параметра (option/variant) есть свой текст
инструкции, бот отправляет именно его (приоритет над общим info/add_info).
Если по выбранному параметру текста нет — отправка пропускается, причина пишется в лог.
Логика одинакова для DigiSeller и GGSEL при включённом *_CHAT_AUTOREPLY_ENABLED.
Аналогичные параметры есть и для GGSEL:
GGSEL_CHAT_AUTOREPLY_ENABLEDGGSEL_CHAT_AUTOREPLY_PRODUCT_IDSGGSEL_CHAT_AUTOREPLY_INTERVAL_SECONDSGGSEL_CHAT_AUTOREPLY_DEDUPE_BY_MESSAGESGGSEL_CHAT_AUTOREPLY_ONLY_EMPTY_CHATGGSEL_CHAT_AUTOREPLY_REQUIRE_RULESGGSEL_CHAT_AUTOREPLY_ALLOW_CUSTOM_TEXTGGSEL_CHAT_AUTOREPLY_ALLOW_TEMPLATE_FALLBACKGGSEL_CHAT_AUTOREPLY_LOOKBACK_MESSAGESGGSEL_CHAT_AUTOREPLY_SENT_TTL_DAYSGGSEL_CHAT_AUTOREPLY_CLEANUP_EVERY_HOURSGGSEL_CHAT_TEMPLATE_RU_ALREADY,GGSEL_CHAT_TEMPLATE_RU_ADDGGSEL_CHAT_TEMPLATE_EN_ALREADY,GGSEL_CHAT_TEMPLATE_EN_ADD
Быстрая проверка только DigiSeller:
python3 scripts/smoke_profiles_api.py --profile digiseller --verify-readЗапуск:
python3 -m srcdocker compose up -d --build
docker compose logs -fКоманды:
/start/status— статус активного профиля/status <profile>— статус выбранного профиля/diag— диагностика активного профиля/diag <profile>— диагностика выбранного профиля/smoke— безопасный API smoke для активного профиля (read + noop write + verify) для DigiSeller дополнительно показываетtoken/perms. Можно указать профиль аргументом:/smoke ggselили/smoke digiseller.
Алиасы профилей в аргументах команд:
- GGSEL:
gg,ggsel - DigiSeller:
digi,dg,digiseller,plati
📊 Статус📦 Товары⬅ Пред. товар/➡ След. товар🧩 Профиль⚙ Настройки
Важно по UX:
- кнопки
⬅/➡только переключают активный товар для управления и оставляют пользователя в главном меню (без автоперехода в настройки) - это защита от случайных мискликов и лишних ручных изменений цены
- при смене профиля незавершённый ввод (pending action) сбрасывается автоматически
Кнопка 📦 Товары (из главного меню) открывает ввод:
- отправьте
product_id— добавить/выбрать товар - отправьте
list— список товаров профиля - отправьте сразу пару:
<product_id> <url_конкурента> - задайте понятное имя:
name <product_id|active> <название> - сбросьте имя:
clearname <product_id|active>
Примеры:
46974394697439 https://ggsel.net/catalog/product/102124601name active Скины подарком
- один профиль может мониторить несколько товаров одновременно
- у каждого товара свой список URL конкурентов
- бот мониторит все товары из списка
- в списке показывается
ID + имя(если имя найдено в карточке или задано вручную) активный товарнужен для редактирования его настроек: режим, автоцена и лимиты- в
📊 Статуспоказываются:- активный товар
- позиция активного товара в списке (
1/N) - ключевые цены (моя/выставленная/конкурента)
- текущий URL/метод/время последнего парса
Доступные кнопки:
🔔 Авто: ВКЛ/ВЫКЛ— для активного товара🎯 Цена🔀 Режим📦 Товары— добавить/выбрать товар и сразу привязать URL конкурента🗑 Удалить товар— удалить один товар (active/id) или сразу все (all)💬 Инструкции: ВКЛ/ВЫКЛ— если профиль поддерживает chat API📭/📨 Только пустой чат— отправлять авто-инструкцию только в пустой чат📝 Правила инстр.— правила отправки по конкретным вариантам параметров
Следование: ставим ровно цену конкурента (4 знака), например0.3560 -> 0.3560Демпинг: считаем от витринной цены конкурента (2 знака):round(конкурент, 2) - 0.0051, например витрина0.35 -> 0.3449Повышение: также от витринной цены (2 знака):round(конкурент, 2) + 0.0049, например витрина0.35 -> 0.3549
Важно:
- стратегии, авто-режим и лимиты изолированы по товару внутри профиля
- настройки одного товара не переносятся на другой товар автоматически
- при добавлении/удалении товара через Telegram новый список scheduler’ов применяется после перезапуска процесса/контейнера
- Откройте
⚙ Настройки->📝 Правила инстр. - Бот покажет список вариантов параметров выбранного товара.
- Если включено хотя бы одно правило, инструкция уходит только по совпавшим правилам.
- Вкл/выкл, сброс и выход делаются кнопками под сообщением со списком правил.
- Кастомный текст опционален: если не задан, бот берет текст из карточки товара.
- Текстовые команды (по желанию):
text <N> <текст>— задать свой текст для вариантаclear <N>— убрать свой текст (оставить текст из карточки товара)done— выйти из редактора
Проверка GGSEL apilogin (использует GGSEL_API_SECRET или fallback на
GGSEL_API_KEY):
python3 scripts/check_apilogin.pyЕсли GGSEL_API_KEY у вас JWT access token, обязательно задайте
GGSEL_API_SECRET, иначе apilogin недоступен.
Выпуск access token через apilogin:
python3 scripts/issue_access_token.pySmoke API активных профилей:
python3 scripts/smoke_profiles_api.pyRead-only smoke (без write probe):
python3 scripts/smoke_profiles_api.py --profile all --verify-readПроверить только DigiSeller:
python3 scripts/smoke_profiles_api.py --profile digisellerЕсли профиль запрошен явно (--profile ggsel|digiseller) и выключен в .env,
скрипт завершится с ошибкой (код 1).
С реальным тестовым изменением и rollback:
python3 scripts/smoke_profiles_api.py --profile digiseller --mutate --delta 0.0001 --verify-readSmoke прав чатов/переписки (без отправки сообщений):
python3 scripts/smoke_chat_api.py --profile allС безопасной POST-пробой chat.send (id_i=0):
python3 scripts/smoke_chat_api.py --profile digiseller --send-probeSmoke доступности текстов инструкций (без отправки сообщений):
python3 scripts/smoke_instruction_data.py --profile allpytest -q
python3 -m compileall src scripts healthcheck.pyЕсли у вас Python 3.14+, используйте версии из requirements-dev.txt
(pytest==8.4.2, pytest-asyncio==1.2.0), чтобы избежать deprecated warning
от старого pytest-asyncio.
src/main.py— запуск профилей и orchestrationsrc/scheduler.py— цикл парсинг -> расчёт -> update/skipsrc/logic.py— бизнес-формулы ценыsrc/rsc_parser.py— парсер конкурентаsrc/api_client.py— GGSEL API клиентsrc/digiseller_client.py— DigiSeller API клиентsrc/telegram_bot.py— Telegram reply UI + handlerssrc/storage.py— SQLite state/runtime/history/alerts
- GGSEL Seller API:
https://seller.ggsel.com/docs/seller-api-v-1 - DigiSeller API:
https://my.digiseller.com/inside/api.asp