на главную Dmitrii Lu
Case Study · 04 / 2026

Парсер биржи Kwork — Telegram-нотификатор

Личный инструмент для фриланса: бот следит за лентой новых заказов на Kwork по моим любимым рубрикам и присылает каждый новый заказ в Telegram через 70 секунд после появления. Вместо ручного мониторинга вкладок — мгновенное уведомление в телефоне: заголовок, бюджет, срок, описание, ссылка. Прочитал → понравилось → открыл → предложил. Написан на Python + aiogram + SQLite, работает 24/7 локально или на VPS.

Скриншот сайдбара Kwork — любимые рубрики пользователя: Создание сайта, Скрипты и боты, ИИ-генерация и др.
отправная точка — мои любимые рубрики на Kwork, из которых бот и выбирает заказы
Продукт
kwork-notifier · персональный инструмент, open-source · бот @Kworkovich_007
Роль
Backend-разработчик — архитектура, Kwork-клиент, планировщик, Telegram-бот, тесты
Разработка
2026 · разработка в связке с Claude Code · github.com/…/kwork-telegram-notifier-mvp ↗
Стек
Python 3.12 · aiogram 3 · kwork · SQLite (aiosqlite) · pydantic 2 · asyncio · APScheduler-подобный tick-loop · Docker-ready · screen + caffeinate (MacBook-host) / systemd (VPS)
  1. 70с частота опроса днём · безопасный интервал, подобранный под anti-abuse лимиты Kwork
  2. 6 рубрик мониторинга одновременно — один запрос на тик, без лишних обращений
  3. 24/7 работа в фоне · screen + caffeinate локально либо systemd на VPS
  4. 0 потерянных или дублирующихся заказов — SeenStore на SQLite ведёт ID всех показанных

* интервал 70 сек днём и 5 мин ночью — сознательный trade-off: быстрее не требуется, а меньше — риск бана аккаунта

1

Задача

На Kwork заказы появляются непрерывно, а хорошие — разбирают в первые минуты. Держать сайт постоянно открытым и жать F5 — неэффективно: уходит всё внимание, а половину вкладок всё равно пропускаешь. Нужен был фоновый процесс, который сам следит за лентой и приносит готовый заказ туда, где я и так сижу — в Telegram.

  • Скорость реакции. Быть первым в ленте до конкурентов — значит успеть откликнуться, пока заказчик ещё выбирает.
  • Фокус по рубрикам. Отслеживать нужно не всю биржу, а строго мои рабочие рубрики: Создание сайта, Доработка и настройка сайта, Скрипты и боты, ИИ-генерация видео, ИИ-тексты, ИИ-генерация изображений.
  • Устойчивость. Бот должен переживать закрытые крышки ноутбука, разрывы сети, слёт сессии на Kwork, дубли апдейтов от Telegram и не терять заказы.
  • Антибан. Частые обращения к внутреннему API Kwork — прямой путь к блокировке. Нужна честная частота, которая не раздражает их anti-abuse.
  • Управление из кармана. Ночной режим, пауза, возобновление — всё из того же Telegram, без SSH и правок конфига.
2

Архитектура — одно приложение, четыре таска

Всё приложение — один процесс Python с общим event-loop, внутри которого крутятся четыре независимые задачи: основной tick-loop, polling Telegram-бота, watchdog и периодический GC старых записей. Общее состояние живёт в памяти, персистится в SQLite и JSON, снаружи — никаких зависимостей, кроме Kwork и Telegram API.

  • tick-loop — fetch → dedupe → format → send, с jitter ±5 сек, чтобы не стучаться в биржу «по будильнику».
  • aiogram polling — параллельный таск, обрабатывает /day, /night, /pause, /resume, /status без остановки парсинга.
  • watchdog — раз в 30 секунд проверяет, что последний тик был недавно; если MacBook ушёл в sleep — в лог идёт CRITICAL.
  • gc-loop — раз в сутки чистит SeenStore от записей старше 30 дней, чтобы база не пухла.
  • graceful shutdown — SIGINT/SIGTERM → shutdown_event → cancel всех задач → финальное «⏹ остановлен» в Telegram.
             ┌───────────────────────────┐
             │   kwork-notifier (one)    │
             │   python · asyncio loop   │
             └───────────────┬───────────┘
                             │
      ┌──────────────────────┼──────────────────────┐
      │                      │                      │
      ▼                      ▼                      ▼
┌──────────────┐     ┌──────────────┐       ┌──────────────┐
│ tick-loop    │     │ bot polling  │       │ watchdog     │
├──────────────┤     ├──────────────┤       ├──────────────┤
│ fetch→filter │     │ /day /night  │       │ 30s check    │
│ dedupe→send  │     │ /pause       │       │ «тика нет?»  │
│ 70s / 300s   │     │ /resume      │       │ → log CRIT   │
└──────┬───────┘     │ /status      │       └──────────────┘
       │             └──────┬───────┘
       │                    │                  ┌──────────────┐
       │                    │                  │ gc-loop      │
       │                    │                  ├──────────────┤
       │                    │                  │ once per day │
       │                    │                  │ seen TTL 30d │
       │                    │                  └──────────────┘
       ▼                    ▼
┌──────────────┐     ┌──────────────┐
│ Kwork API    │     │ Telegram API │
│ (kwork lib)  │     │ (aiogram 3)  │
└──────────────┘     └──────────────┘

persistence:
   ┌─────────────────────────────────────────────────┐
   │  data/seen.sqlite   — ID + title + category_id  │
   │  data/state.json    — mode (day/night), paused  │
   │  logs/notifier.log  — rotating 10MB × 5         │
   └─────────────────────────────────────────────────┘

один процесс, четыре конкурентные задачи на одном event-loop — без очередей и брокеров

3

Как это работает на стороне пользователя

Интерфейс сведён к четырём действиям: открыл Telegram, прочитал заказ, решил — подошёл / не подошёл, нажал «Открыть на Kwork». Никакой вкладки с биржей открытой не держу — всё прилетает само, по мере появления.

  1. 01 · заказчик публикует новое задание на Kwork в одной из 6 рубрик.
  2. 02 · в течение 70 секунд tick-loop обнаруживает его и проверяет по SeenStore — раньше не показывали?
  3. 03 · если новый — форматирует, экранирует HTML, бьёт на части при длинном описании и шлёт в мой чат.
  4. 04 · я в Telegram вижу: «🆕 Заголовок · 💰 бюджет · ⏱ срок · 👥 откликов · 📝 описание · 🔗 Открыть на Kwork».
  5. 05 · нравится — тап по ссылке, открывается страница заказа, делаю предложение. Нет — просто пролистываю.

Дополнительный слой управления — прямо из того же Telegram: /night переключает на редкий режим (раз в 5 минут) на время сна, /pause ставит на паузу на время отпуска, /status показывает, жив ли процесс и сколько заказов уже прислано.

Реальная лента уведомлений от бота Kworkovich_007 в Telegram
реальная лента бота @Kworkovich_007 — три подряд заказа 24.04, интервал публикации 2–3 минуты, каждый долетел в течение минуты после появления
4

Планировщик — день, ночь, пауза

Универсальный интервал не работает: днём нужна скорость, ночью — спокойствие (и минимум нагрузки на биржу). Решил дать боту три режима и отдать их переключение в руки самого пользователя через Telegram-команды.

  • /day — боевой режим. Проверка каждые 70 секунд + jitter ±5 сек. Это и есть основной профиль.
  • /night — тихий режим. Раз в 5 минут: нагрузка на биржу минимальная, в Telegram ничего не дребезжит в 3 ночи.
  • /pause — поставить на паузу. Новые заказы по-прежнему фиксируются как seen, но не шлются. При /resume старые не выливаются стеной.
  • /status — текущий режим, paused, время последнего тика, сколько отправлено, сколько ошибок.
  • /start, /help — приветствие и шпаргалка.
5

Рубрики — ровно 6 из любимых

Чтобы уведомления были сигналом, а не шумом, бот слушает только те рубрики, в которых я реально работаю. Список определён один раз в config/categories.json и собран ровно из «Любимых» на Kwork — отсюда и скриншот на hero.

  • 37 · Создание сайта
  • 38 · Доработка и настройка сайта
  • 41 · Скрипты, боты и mini apps
  • 300 · ИИ-генерация видео
  • 303 · ИИ-тексты
  • 306 · ИИ-генерация изображений

Добавить или убрать рубрику — правка JSON + перезапуск. Справочник ID-рубрик Kwork обновляется отдельным скриптом scripts/fetch_categories.py, чтобы не искать коды вручную.

config/categories.json·
{
  "categories": [
    {"id": 37,  "name": "Создание сайта"},
    {"id": 38,  "name": "Доработка и настройка сайта"},
    {"id": 41,  "name": "Скрипты, боты и mini apps"},
    {"id": 300, "name": "ИИ-генерация видео"},
    {"id": 303, "name": "ИИ-тексты"},
    {"id": 306, "name": "ИИ-генерация изображений"}
  ]
}
единственная конфигурация, которую приходится трогать — JSON на 8 строк
6

Устойчивость — как я не ловлю бан и не теряю заказы

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

· честная частота

70 секунд + jitter

Интервал между тиками — 70 секунд днём и 5 минут ночью, плюс случайный разброс ±5 сек. Это и не создаёт паттерна «ровно каждую минуту», и не воспринимается биржей как агрессивный скрейпинг. За месяцы работы — ни одного 403-ответа.

· SeenStore

Дедупликация на SQLite

Каждый ID заказа после отправки пишется в seen.sqlite вместе с заголовком и рубрикой. Повторные появления того же заказа — молча игнорируются. GC раз в сутки чистит записи старше 30 дней, чтобы база не распухала.

· warm-up

Без спама на первом запуске

При первом старте (пустая БД) бот не шлёт 300 старых заказов — он их молча отмечает как seen. Первое уведомление приходит только на реально новые заказы — те, что появились после запуска.

· anti-sleep

Watchdog + caffeinate

Отдельный таск каждые 30 секунд проверяет: когда был последний успешный тик? Если больше 2.5× интервала — в лог идёт CRITICAL «возможно, MacBook ушёл в sleep». Скрипт запуска использует caffeinate -i, чтобы Mac не усыплял процесс при закрытой крышке.

· session drop

Авто-релогин

Библиотека kwork сконфигурирована с relogin_on_auth_error=True и экспоненциальным бэкоффом до 15 сек. Если сессия слетает (параллельный логин с другого устройства) — клиент сам перелогинивается, тик не пропускается.

· 403 triage

Escalation на 403

Первый 403 — пауза 5 минут и повтор. Второй — 30 минут. Третий подряд — аварийный останов с уведомлением в Telegram: «проверь аккаунт на kwork.ru». Это защищает от долбёжки в закрытый вход и эскалации бана.

· rotating logs

Лог, который не съедает диск

Ротация 10 МБ × 5 файлов: свежий — текущий, пять прошлых в архиве. Параллельно aiogram / aiohttp сбиты на WARNING, чтобы в логе было видно только нашу логику, без шума от фреймворков.

· graceful shutdown

SIGTERM без потерь

На SIGINT/SIGTERM срабатывает общий shutdown_event, все четыре таски корректно сворачиваются, клиенты закрываются, и в чат уходит прощальное «⏹ Парсер остановлен». Никаких полу-отправленных заказов и болтающихся соединений.

7

Формат сообщения — полный текст, не превью

Смысл бота — чтобы решение «подхожу / не подхожу» принималось в самом Telegram, без похода на сайт. Поэтому уведомление содержит полный текст заказа, а не сжатую выжимку. Обработку «длинных» сообщений я вынес в отдельный форматтер.

  • HTML parse_mode — заголовок жирным, метки bold + эмодзи, ссылка на карточку на Kwork.
  • Очистка описания — Kwork отдаёт <br>, &nbsp; и HTML-сущности: <br> → \n, остальные теги срезаются, сущности декодируются через html.unescape.
  • Лимит 4096  — Telegram режет сообщения длиннее. Форматтер сам разбивает описание на чанки по 3800 символов и маркирует продолжения «(продолжение)».
  • Форматирование бюджета — «от 3 000 до 8 000 ₽», «до 50 000 ₽», «не указан» — один формат, один источник правды.
  • Срок 1 день / 3 дня / 5 дней: корректное русское склонение без внешних библиотек.
  • Анти-429 — между сообщениями внутри одного тика случайная пауза 0.5–1.5 сек, чтобы не словить Telegram rate-limit при всплеске.
8

Разворачивание — локально или VPS

Приложение сознательно не требует никакой инфры, кроме Python 3.12 и файловой системы. Два профиля развёртывания: «у меня на MacBook» (сейчас) и «на VPS 24/7» (план) — переключение без правок кода.

Profile · MacBook (текущий)

Локально, в фоне

  • .venv + pip install -e .
  • ./scripts/start.shscreen-сессия kwork
  • caffeinate -i держит Mac не в sleep
  • screen -r kwork — посмотреть живые логи
  • tail -f logs/notifier.log — из файла
  • ./scripts/stop.sh — корректный останов
Profile · VPS (готов к переезду)

На сервере, через systemd

  • тот же код, тот же .env
  • systemd-unit — автозапуск + restart=always
  • нет риска sleep — 24/7 стабильно
  • логи через journalctl -u kwork-notifier
  • при необходимости — упаковка в Docker, инстанс поднимается на любом VPS за 5 минут
9

Результат

  • Биржу больше не нужно держать открытой. Уведомления приходят в Telegram — там же я и работаю. Рутинный мониторинг сведён к нулю.
  • Время реакции — минуты, а не часы. От появления заказа до моего уведомления — 70 секунд в боевом режиме; открываю ссылку и откликаюсь первым, пока лента ещё тёплая.
  • Только нужные рубрики. Шесть моих рабочих категорий вместо всей биржи — в ленте Telegram нет случайных «уборщиц» или «написать диплом».
  • Управляется из кармана. /pause на время встреч, /night на сон, /status — всё ли живо. Без SSH, без правок конфига.
  • Не требует моего внимания как процесса. Watchdog, авто-релогин, дедупликация и warm-up делают бота «поставил и забыл» — именно то, чего я хотел от инструмента.
  • Готов к VPS. Сейчас работает локально на MacBook, но в любой момент переносится на VPS без правок кода — тот же .env, systemd-юнит, дальше работает 24/7 без меня.

Нужен похожий парсер?

Если у вас есть биржа, маркетплейс, сайт объявлений или любой источник, из которого нужно ловить новое в Telegram — соберу такой же инструмент под вашу задачу. С фильтрами, антибаном, дедупликацией и панелью управления в Telegram.

все работы