6.3 KiB
6.3 KiB
Журнал сессии (2026-05-25)
Что построили
Внутренний мониторинг цен конкурентов на propertyfinder.ae и bayut.com для HOME LIGA REAL ESTATE.
Стек: FastAPI + Jinja2 (Bootstrap 5) + SQLAlchemy/SQLite + APScheduler + python-telegram-bot + httpx/BS4.
Три процесса (каждый — свой .bat-лаунчер):
run_web.bat— веб-UI на http://127.0.0.1:8000run_bot.bat— Telegram-бот (polling)run_scheduler.bat— фоновый сканер каждыеSCRAPE_INTERVAL_HOURSчасов
Эволюция архитектуры
Первая попытка (отвергнута)
Идея: сотрудник вводит DLD Permit Number своего объекта → система автоматически ищет «то же permit» на PF и Bayut → находит объявления конкурентов.
Почему не сработало:
- В Дубае каждый брокер получает свой permit на свою публикацию. Два брокера, рекламирующих одну квартиру = два разных permit. Permit не идентифицирует физический объект — он идентифицирует конкретное объявление конкретного брокера.
- PF на странице объявления показывает permit картинкой через сервис верификации, не plain text (anti-scraping).
- PF search
?q=<permit>— free-text по названию/описанию, не структурированный фильтр.
Финальная архитектура
Manual URL list + опциональные подсказки:
- Сотрудник создаёт проект (название, тип сделки, владелец, опц. building/bedrooms/sqft).
- На странице проекта вручную вставляет URL объявления конкурента → система делает single-page fetch, парсит
__NEXT_DATA__, добавляет в трекинг. - Если указано здание — кнопка «🔍 Подобрать похожие» ищет на PF/Bayut по
building + bedroomsи предлагает кандидатов с кнопкой «+ Отслеживать». - Каждые 4 часа фоновый сканер делает refetch каждого отслеживаемого URL → детектит:
- 📈📉 изменение цены
- ❌ удаление (URL отдаёт 404)
- ♻️ возвращение из удалённого статуса
- Уведомления — в Telegram личкой владельцу проекта.
Модель данных
Employee— name, tg_chat_id (опц.), tg_usernameProject— title, deal_type, owner_id, our_price, building, bedrooms, size_sqft, our_url, dld_permit (все послеowner— опционально)CompetitorListing— source (PF|Bayut), external_id, url, current_price, status (active|removed), agent_name, agency_name, first_seen, last_seenPriceHistory— listing_id, price, recorded_at
Как подключиться сотруднику
- В TG найти бота → отправить
/start. - Отправить
/whoami→ бот пришлёт chat_id. - В вебе http://127.0.0.1:8000/employees → найти/создать запись → вставить chat_id → Сохранить.
Известные ограничения
- PF/Bayut могут блокировать при частых запросах (видно как
Blocked by site (403/429)в логах). Решение — увеличить интервал; если уже не помогает — добавить Playwright fallback. - Подсказки эвристические: ищем по совпадению building name в title + bedrooms-фильтр. Могут попасть «другие квартиры в том же здании» — поэтому добавление в трекинг через ручное подтверждение.
- Permit как plain text на PF не отдаётся. Если когда-нибудь понадобится — нужен OCR на verification-image.
Что в .env
TG_BOT_TOKEN=<токен от @BotFather>
SCRAPE_INTERVAL_HOURS=4
ADMIN_CHAT_ID= # опц.
Структура проекта
DLD Permit Number/
├── run_web.bat, run_bot.bat, run_scheduler.bat
├── run_web.py
├── requirements.txt
├── .env (не в git — содержит токен)
├── data/monitor.db (создаётся автоматически)
├── app/
│ ├── config.py ← settings + резолвит относительные SQLite-пути в абсолютные
│ ├── db.py, models.py
│ ├── web.py ← FastAPI: CRUD проектов, /listings add/delete, /suggest
│ ├── bot.py ← /start, /whoami, /list, /check
│ ├── scheduler.py
│ ├── scrapers/{base,propertyfinder,bayut}.py
│ ├── services/{monitor,notifier}.py
│ └── templates/ ← projects_list, project_form, project_detail, suggest, employees, base
Что протестировать в первую очередь
- Удалить старый
data/monitor.db(схема изменилась). - Запустить три .bat файла.
/startботу →/whoami→ chat_id → вписать в Сотрудники.- Создать тестовый проект (Aykon City Tower B, 2BR).
- Вставить URL реального объявления конкурента → проверить, что добавилось с ценой/брокером.
- Жмякнуть «Подобрать похожие» → посмотреть кандидатов.
- Жмякнуть «Проверить сейчас» → если цена не менялась, изменений не будет (это нормально); для теста алертов можно поменять цену в БД руками или подождать реального изменения.