Парсер биржи Kwork — Telegram-нотификатор
Личный инструмент для фриланса: бот следит за лентой новых заказов на Kwork по моим любимым рубрикам и присылает каждый новый заказ в Telegram через 70 секунд после появления. Вместо ручного мониторинга вкладок — мгновенное уведомление в телефоне: заголовок, бюджет, срок, описание, ссылка. Прочитал → понравилось → открыл → предложил. Написан на Python + aiogram + SQLite, работает 24/7 локально или на VPS.
- 70с частота опроса днём · безопасный интервал, подобранный под anti-abuse лимиты Kwork
- 6 рубрик мониторинга одновременно — один запрос на тик, без лишних обращений
- 24/7 работа в фоне · screen + caffeinate локально либо systemd на VPS
- 0 потерянных или дублирующихся заказов — SeenStore на SQLite ведёт ID всех показанных
* интервал 70 сек днём и 5 мин ночью — сознательный trade-off: быстрее не требуется, а меньше — риск бана аккаунта
Задача
На Kwork заказы появляются непрерывно, а хорошие — разбирают в первые минуты. Держать сайт постоянно открытым и жать F5 — неэффективно: уходит всё внимание, а половину вкладок всё равно пропускаешь. Нужен был фоновый процесс, который сам следит за лентой и приносит готовый заказ туда, где я и так сижу — в Telegram.
- Скорость реакции. Быть первым в ленте до конкурентов — значит успеть откликнуться, пока заказчик ещё выбирает.
- Фокус по рубрикам. Отслеживать нужно не всю биржу, а строго мои рабочие рубрики: Создание сайта, Доработка и настройка сайта, Скрипты и боты, ИИ-генерация видео, ИИ-тексты, ИИ-генерация изображений.
- Устойчивость. Бот должен переживать закрытые крышки ноутбука, разрывы сети, слёт сессии на Kwork, дубли апдейтов от Telegram и не терять заказы.
- Антибан. Частые обращения к внутреннему API Kwork — прямой путь к блокировке. Нужна честная частота, которая не раздражает их anti-abuse.
- Управление из кармана. Ночной режим, пауза, возобновление — всё из того же Telegram, без SSH и правок конфига.
Архитектура — одно приложение, четыре таска
Всё приложение — один процесс 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 — без очередей и брокеров
Как это работает на стороне пользователя
Интерфейс сведён к четырём действиям: открыл Telegram, прочитал заказ, решил — подошёл / не подошёл, нажал «Открыть на Kwork». Никакой вкладки с биржей открытой не держу — всё прилетает само, по мере появления.
- 01 · заказчик публикует новое задание на Kwork в одной из 6 рубрик.
- 02 · в течение 70 секунд tick-loop обнаруживает его и проверяет по SeenStore — раньше не показывали?
- 03 · если новый — форматирует, экранирует HTML, бьёт на части при длинном описании и шлёт в мой чат.
- 04 · я в Telegram вижу: «🆕 Заголовок · 💰 бюджет · ⏱ срок · 👥 откликов · 📝 описание · 🔗 Открыть на Kwork».
- 05 · нравится — тап по ссылке, открывается страница заказа, делаю предложение. Нет — просто пролистываю.
Дополнительный слой управления — прямо из того же Telegram: /night переключает на редкий режим (раз в 5 минут) на время сна, /pause ставит на паузу на время отпуска, /status показывает, жив ли процесс и сколько заказов уже прислано.
Планировщик — день, ночь, пауза
Универсальный интервал не работает: днём нужна скорость, ночью — спокойствие (и минимум нагрузки на биржу). Решил дать боту три режима и отдать их переключение в руки самого пользователя через Telegram-команды.
-
/day— боевой режим. Проверка каждые 70 секунд + jitter ±5 сек. Это и есть основной профиль. -
/night— тихий режим. Раз в 5 минут: нагрузка на биржу минимальная, в Telegram ничего не дребезжит в 3 ночи. -
/pause— поставить на паузу. Новые заказы по-прежнему фиксируются как seen, но не шлются. При/resumeстарые не выливаются стеной. -
/status— текущий режим, paused, время последнего тика, сколько отправлено, сколько ошибок. -
/start,/help— приветствие и шпаргалка.
BotFather-описания
/help · MarkdownV2, моноширинные команды, короткие описания
Рубрики — ровно 6 из любимых
Чтобы уведомления были сигналом, а не шумом, бот слушает только те рубрики, в которых я реально работаю. Список определён один раз в config/categories.json и собран ровно из «Любимых» на Kwork — отсюда и скриншот на hero.
- 37 · Создание сайта
- 38 · Доработка и настройка сайта
- 41 · Скрипты, боты и mini apps
- 300 · ИИ-генерация видео
- 303 · ИИ-тексты
- 306 · ИИ-генерация изображений
Добавить или убрать рубрику — правка JSON + перезапуск. Справочник ID-рубрик Kwork обновляется отдельным скриптом scripts/fetch_categories.py, чтобы не искать коды вручную.
{ "categories": [ {"id": 37, "name": "Создание сайта"}, {"id": 38, "name": "Доработка и настройка сайта"}, {"id": 41, "name": "Скрипты, боты и mini apps"}, {"id": 300, "name": "ИИ-генерация видео"}, {"id": 303, "name": "ИИ-тексты"}, {"id": 306, "name": "ИИ-генерация изображений"} ] }
Устойчивость — как я не ловлю бан и не теряю заказы
Главные риски персонального парсера — это не «красивый UI», а четыре скучных вещи: бан аккаунта, дублированные уведомления, зависший процесс и слетевшая сессия. Ниже — как каждая закрыта.
70 секунд + jitter
Интервал между тиками — 70 секунд днём и 5 минут ночью, плюс случайный разброс ±5 сек. Это и не создаёт паттерна «ровно каждую минуту», и не воспринимается биржей как агрессивный скрейпинг. За месяцы работы — ни одного 403-ответа.
Дедупликация на SQLite
Каждый ID заказа после отправки пишется в seen.sqlite вместе с заголовком и рубрикой. Повторные появления того же заказа — молча игнорируются. GC раз в сутки чистит записи старше 30 дней, чтобы база не распухала.
Без спама на первом запуске
При первом старте (пустая БД) бот не шлёт 300 старых заказов — он их молча отмечает как seen. Первое уведомление приходит только на реально новые заказы — те, что появились после запуска.
Watchdog + caffeinate
Отдельный таск каждые 30 секунд проверяет: когда был последний успешный тик? Если больше 2.5× интервала — в лог идёт CRITICAL «возможно, MacBook ушёл в sleep». Скрипт запуска использует caffeinate -i, чтобы Mac не усыплял процесс при закрытой крышке.
Авто-релогин
Библиотека kwork сконфигурирована с relogin_on_auth_error=True и экспоненциальным бэкоффом до 15 сек. Если сессия слетает (параллельный логин с другого устройства) — клиент сам перелогинивается, тик не пропускается.
Escalation на 403
Первый 403 — пауза 5 минут и повтор. Второй — 30 минут. Третий подряд — аварийный останов с уведомлением в Telegram: «проверь аккаунт на kwork.ru». Это защищает от долбёжки в закрытый вход и эскалации бана.
Лог, который не съедает диск
Ротация 10 МБ × 5 файлов: свежий — текущий, пять прошлых в архиве. Параллельно aiogram / aiohttp сбиты на WARNING, чтобы в логе было видно только нашу логику, без шума от фреймворков.
SIGTERM без потерь
На SIGINT/SIGTERM срабатывает общий shutdown_event, все четыре таски корректно сворачиваются, клиенты закрываются, и в чат уходит прощальное «⏹ Парсер остановлен». Никаких полу-отправленных заказов и болтающихся соединений.
Формат сообщения — полный текст, не превью
Смысл бота — чтобы решение «подхожу / не подхожу» принималось в самом Telegram, без похода на сайт. Поэтому уведомление содержит полный текст заказа, а не сжатую выжимку. Обработку «длинных» сообщений я вынес в отдельный форматтер.
- HTML parse_mode — заголовок жирным, метки bold + эмодзи, ссылка на карточку на Kwork.
-
Очистка описания
— Kwork отдаёт
<br>, и 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 при всплеске.
Разворачивание — локально или VPS
Приложение сознательно не требует никакой инфры, кроме Python 3.12 и файловой системы. Два профиля развёртывания: «у меня на MacBook» (сейчас) и «на VPS 24/7» (план) — переключение без правок кода.
Локально, в фоне
.venv+pip install -e ../scripts/start.sh→ screen-сессияkworkcaffeinate -iдержит Mac не в sleepscreen -r kwork— посмотреть живые логиtail -f logs/notifier.log— из файла./scripts/stop.sh— корректный останов
На сервере, через systemd
- тот же код, тот же
.env systemd-unit— автозапуск + restart=always- нет риска sleep — 24/7 стабильно
- логи через
journalctl -u kwork-notifier - при необходимости — упаковка в Docker, инстанс поднимается на любом VPS за 5 минут
Результат
- Биржу больше не нужно держать открытой. Уведомления приходят в Telegram — там же я и работаю. Рутинный мониторинг сведён к нулю.
- Время реакции — минуты, а не часы. От появления заказа до моего уведомления — 70 секунд в боевом режиме; открываю ссылку и откликаюсь первым, пока лента ещё тёплая.
- Только нужные рубрики. Шесть моих рабочих категорий вместо всей биржи — в ленте Telegram нет случайных «уборщиц» или «написать диплом».
-
Управляется из кармана.
/pauseна время встреч,/nightна сон,/status— всё ли живо. Без SSH, без правок конфига. - Не требует моего внимания как процесса. Watchdog, авто-релогин, дедупликация и warm-up делают бота «поставил и забыл» — именно то, чего я хотел от инструмента.
-
Готов к VPS.
Сейчас работает локально на MacBook, но в любой момент переносится на VPS без правок кода — тот же
.env, systemd-юнит, дальше работает 24/7 без меня.
Нужен похожий парсер?
Если у вас есть биржа, маркетплейс, сайт объявлений или любой источник, из которого нужно ловить новое в Telegram — соберу такой же инструмент под вашу задачу. С фильтрами, антибаном, дедупликацией и панелью управления в Telegram.
← все работы