Как я подготовился к сезону переездов и написал Telegram-бота для поиска жилья

Предисловие: Готовь сани летом, а парсер — весной

Учебный год близится к концу, и на горизонте у меня замаячил переезд. Я решил не откладывать всё на последний момент и начал заранее поглядывать на рынок аренды. Понаблюдав за досками объявлений, я заметил неприятную тенденцию: интересные варианты по адекватной цене буквально испаряются на глазах.

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

Определение жертвы

Когда я только садился за код, я думал, что сейчас начнется боль. Я ожидал увидеть жесткий антифрод, скрытые API, капчи на каждый чих и необходимость поднимать тяжеленный Selenium, чтобы хоть что-то вытащить.

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

Эволюция костылей: от while True к Celery

Естественно, моя первая версия бота была типичным скриптом новичка. Это был один файл, внутри которого крутился бесконечный while True с time.sleep(900).

Оно работало, но:

  1. Это было жутко некрасиво и не масштабируемо.
  2. При любых сбоях сети (или таймаутах со стороны серверов Telegram) скрипт крашился и лежал мертвым грузом, пока я его не перезапускал.

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

Я разбил проект на 5 Docker-контейнеров:

  • PostgreSQL — чтобы запоминать уже отправленные ссылки и не спамить дублями.
  • Redis — в качестве брокера сообщений.
  • Celery Beat — планировщик, который раз в 15 минут генерирует задачу.
  • Celery Worker — работяга, который ходит на Яндекс, парсит HTML и отправляет мне сообщение.
  • Бот (aiogram) — висит асинхронно и слушает мои команды.

Изначально целевой URL с фильтрами Яндекса был просто захардкожен в скрипте. Поменялся бюджет или захотел посмотреть другой район? Иди переписывай код и перезапускай Докер. Естественно, это плохо

Чтобы сделать инструмент удобным в быту, я вынес управление в интерфейс бота. Теперь я просто настраиваю фильтры в браузере с телефона, кидаю боту команду /set <ссылка>, и он обновляет запись в Postgres. При следующем запуске воркер просто подхватывает новый URL.

Зачем я это написал?

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

Поэтому я выложил всё в Open Source и пришел сюда.

Буду очень рад, если более опытные ребята посмотрят код и подскажут:

  • Правильно ли я вообще организовал работу с Celery и БД?
  • Как лучше защититься от банов Яндекса в будущем (кроме очевидной покупки прокси)?
  • Что можно улучшить в архитектуре перед тем, как добавлять парсеры других площадок (Авито, Циан)?

Жду вашей конструктивной критики, код-ревью и пул-реквестов.
PS. Да, скрипты на python почти полностью написаны нейронкой, потому что в этом я слабо разбираюсь. В моих интересах больше было поковыряться с контейнерами и в Linux потыкать разное т.к. в дальнейшем хочу развиваться в инфре

2