[Laraue.Apps.RealEstate](https://github.com/win7user10/Laraue.Apps.RealEstate) — open-source proof-of-concept, который собирает объявления об аренде квартир в Санкт-Петербурге, анализирует каждое фото с помощью локальной визуальной модели Ollama и ранжирует результаты по составному баллу идеальности. В этой статье разбираем архитектуру, каждый сервис, формулу ранжирования и эволюцию от самостоятельно обученных TensorFlow-моделей до open-source LLM.
Приложение можно увидеть здесь apartments.laraue.com.
Стек технологий
| Слой | Технология |
|---|---|
| Язык | C# |
| Фреймворк | .NET 9 |
| API | ASP.NET Core |
| База данных | PostgreSQL |
| Краулер | Laraue.Crawler (собственная библиотека) |
| Визуальный ИИ | Ollama — визуальная модель qwen2.5 |
| Лицензия | MIT |
Система разделена на три отдельных хоста, каждый с чёткой зоной ответственности.
Обзор архитектуры
WorkerHost → сбор объявлений + расчёт рейтингов
GpuWorkerHost → запуск задач предсказания через Ollama
ApiHost → обработка запросов фронтенда
Ключевое архитектурное решение — вынести GpuWorkerHost в отдельный процесс. Инференс на изображениях GPU-bound и медленный. Изоляция в собственный хост означает, что краулер и задачи ранжирования не блокируются пропускной способностью предсказателя, а GPU-хост можно масштабировать или переносить на выделенную машину независимо.
Сервис 1: Сборщик объявлений (WorkerHost)
Краулер запускается каждые 4 часа как планировщик внутри WorkerHost. Построен на собственной библиотеке [Laraue.Crawler](https://github.com/win7user10/Laraue.Crawling) — .NET-краулер с поддержкой как статического HTML (через AngleSharp), так и JavaScript-рендеринга (через PuppeteerSharp).
Каждый источник реализован в виде CrawlerJob с соответствующей CrawlingSchema. Для Циан (основной российский агрегатор недвижимости):
-
CianCrawlerJob— джоб, запускающий обход -
CianCrawlingSchema— схема извлечения данных, сопоставляющая элементы DOM с моделью данных
Логика раннего завершения
Краулер запрашивает объявления, отсортированные по дате (сначала новые). На каждом прогоне он вставляет новые записи до тех пор, пока не встретит объявление, которое уже есть в базе — после чего останавливается. Простая, но эффективная стратегия дедупликации и терминации: не нужно обходить весь результат, только дельту с последнего запуска.
Добавить новый источник = реализовать новую пару CrawlerJob + CrawlingSchema. Система поддерживает произвольное количество источников одновременно.
Сервис 2: Предсказатель изображений (GpuWorkerHost)
Предсказатель запускается каждую минуту как джоб внутри GpuWorkerHost. Он забирает следующий батч непроанализированных изображений и последовательно отправляет их в Ollama.
Модель предсказания
Основной класс — OllamaRealEstatePredictor, вызывающий локально запущенную визуальную модель qwen2.5. Байты изображения передаются в Ollama вместе со структурированным промптом, в котором описано, что считается хорошим и плохим признаком на фото квартиры.
Результат предсказания
Каждое фото даёт OllamaPredictionResult:
public record OllamaPredictionResult
{
public double RenovationRating { get; init; } // от 0.0 до 1.0
public string[] Tags { get; init; } = []; // например ["clean", "new_windows", "dark"]
public string Description { get; init; } = string.Empty; // рассуждение модели
}
В итоговом ранжировании используется только RenovationRating. Tags и Description сохраняются для настройки промпта и отладки — они показывают, на что именно реагирует модель в каждом фото, без повторного запуска инференса.
Почему Ollama, а не облачный API
Весь инференс работает локально. Фотографии не покидают машину, нет платы за каждый вызов, а модель можно сменить изменением одной настройки. Визуальная модель qwen2.5 показывает достаточное качество для данной задачи при приемлемой скорости на потребительском GPU.
Сервис 3: Система ранжирования (WorkerHost)
Когда все фотографии объявления получили предсказания, задача ранжирования забирает его и вычисляет итоговый балл идеальности через AdvertisementComputedFieldsCalculator.
Формула идеальности
Балл начинается с максимального значения и накапливает штрафы за негативные сигналы:
| Сигнал | Штраф |
|---|---|
| Нет ближайшей станции метро | Штраф |
| Метро слишком далеко пешком | Штраф |
| Далеко от центра города | Штраф |
| Низкий рейтинг ремонта | Штраф |
Чем больше штрафов — тем ниже идеальность. Штрафная система выбрана намеренно вместо взвешенной суммы: каждый штраф имеет изолированный, понятный эффект, что упрощает отладку и настройку.
Рейтинг ремонта
Оценка ремонта для объявления — это среднее значение `RenovationRating` по всем его фотографиям. Объявления с очень малым количеством фото исключаются из ранжирования по ремонту, поскольку одна нерепрезентативная фотография может сильно исказить среднее.
После расчёта идеальности и рейтинга ремонта объявление попадает в результирующий набор, который возвращает API.
Сервис 4: API Host
Стандартный ASP.NET Core API. Поддерживает фильтрацию по количеству комнат, цене, району и сортировку по баллу ИИ или идеальности. Ничего архитектурно интересного — вся сложность в трёх других сервисах.
Эволюция: от TensorFlow к Ollama
Подход к оценке изображений существенно менялся на протяжении проекта:
| Дата | Подход |
|---|---|
| Фев 2023 | Попытки собрать датасет и обучить модель с TensorFlow |
| Окт 2023 | Первая живая версия на трёх собственных моделях (~22М параметров суммарно). Быстрый инференс, но низкая точность — сложно собрать достаточно корректно размеченных данных |
| Сен 2025 | Собственные модели заменены на Ollama + qwen2.5 |
Самостоятельно обученные модели оказались тупиком: собрать большой и корректно аннотированный датасет фотографий квартир реально сложно, а модели упёрлись в точность, которой не хватало для полезного ранжирования. Переход на предобученную визуальную модель через Ollama полностью снял проблему датасета и существенно улучшил качество предсказаний — ценой более медленного инференса, который компенсируется выделенным GpuWorkerHost.
Известные ограничения
Проект — proof-of-concept, используется неформально. Ключевые ограничения:
- Предсказания часто ошибаются — оценка фото работает хорошо в среднем, но на отдельных снимках, особенно неоднозначных, модель может ошибиться
- Усреднение сглаживает ошибки — агрегация по объявлению на практике нивелирует ошибочные предсказания по отдельным фото
- Только данные по Санкт-Петербургу — схема краулера написана под листинги Циан по СПб; другие города потребуют отдельных реализаций схем
-
Нужен локальный GPU — для полезной пропускной способности
GpuWorkerHostтребуется машина с производительным GPU
Планируемые статьи
В README отмечены темы, заслуживающие отдельных разборов:
- Как выбрать подходящую визуальную модель для задачи оценки фото
- Как проектировать и настраивать штрафную формулу ранжирования
- Как интеграционно тестировать многосервисный пайплайн
Контрибьюция и запуск локально
Проект под лицензией MIT. Наиболее полезные вклады — новые схемы краулера для других сайтов недвижимости или улучшения промпта Ollama для более точной оценки ремонта.
- Репозиторий: github.com/win7user10/Laraue.Apps.RealEstate
- Библиотека краулера: github.com/win7user10/Laraue.Crawling
- Приложение: apartments.laraue.com