Назад к articles
article

ИИ-система ранжирования недвижимости на C#, Ollama и собственном краулере

[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 для более точной оценки ремонта.