# Парсер List.am Модуль взят из прежней CRM (`apps/api/src/import` → `apps/api/src/parser`) и доработан. Конвейер, который им пользуется, — `apps/api/src/ingest/service.ts`. ## Как это работает 1. **Все категории недвижимости** list.am читаются по кругу: квартиры, дома, коммерческая, участки, гаражи — продажа и аренда (10 категорий). Напрямую на list.am сервис не ходит: сайт закрыт Cloudflare, страницы читаются через публичный ридер Jina (`r.jina.ai`). 2. **Свежесть.** Список list.am отсортирован по «поднятию», а блок «Топ» ротируется, поэтому незнакомое объявление ещё не значит новое. Возраст определяется по дате «Размещено» на странице объявления. В базу попадают объявления, размещённые **не раньше чем 3 дня назад** (`FRESH_DAYS`). Более старые запоминаются только по ID (`seen_listings`), чтобы не перечитывать их при каждом «поднятии». 3. **Общая база.** Свежее объявление сохраняется в таблицу `listings` один раз, независимо от того, есть ли под него фильтр у клиентов. 4. **Раздача.** Объявление из базы сопоставляется с фильтрами всех компаний с действующей подпиской; где подошло — создаётся лид в пуле и уходят уведомления в Telegram. Новый или изменённый фильтр сразу получает подходящие объявления из базы за последние 3 дня — в пул, без уведомлений. 5. **Первый проход** по категории наполняет базу без лидов: эти объявления висели на сайте до подключения. ## Что собирается ID, ссылка, тип сделки, тип объекта, область и район (или населённый пункт), адрес, цена и валюта, комнаты, площадь, этаж и этажность, описание, обложка фото, метка «Агентство», дата размещения. ## Ключ ридера Jina (`JINA_API_KEY`) Ключ оплачивается по объёму ответа (токенам), поэтому используется экономно: | Что читается | Как | Объём | | --- | --- | --- | | Страница категории (10 штук за круг) | **без ключа**, бесплатно, не чаще ~20 запросов в минуту | ~22 000 токенов за страницу — с ключом это сожгло бы баланс за часы | | Дата размещения незнакомого объявления | с ключом: HTML-вырезка подвала страницы | ~330 токенов | | Детали свежего объявления | с ключом: HTML-вырезка блоков с адресом, характеристиками, описанием | 1 000–6 000 токенов | Если ключ отклонён или баланс кончился, парсер на час переходит на режим без ключа и продолжает работать. Без ключа совсем: дата известна с точностью до дня, детали подтягиваются примерно у половины объявлений, круг длится 2–3 минуты. ## Замеры (08.10.2026, локальный стенд) | Что | С ключом | Без ключа | | --- | --- | --- | | Проход парсера по 10 категориям | 31–35 с | 107–200 с | | Полный круг (проход + пауза 5 с) | около 37 с | 2–3,5 мин | | Ошибки чтения за проход | 0 | 0–2 | | Первый проход (наполнение базы) | — | около 10 минут | | Задержка «размещено → лид» | 41 с (один замер: размещено в 20:50:05, лид в 20:50:46) | не измеряется: нет времени размещения | Объявление попадает в базу в течение одного круга после появления на первой странице категории, то есть обычно быстрее минуты. Круг ограничен бесплатным чтением страниц категорий: 10 страниц с паузой ~3 с. Замер задержки пока единичный — медиана за сутки видна суперадмину в разделе «Парсер». Расход ключа на длинном отрезке не измерен: баланс в кабинете Jina обновляется с задержкой. Оценка по объёму ответов — несколько миллионов токенов в сутки. ## Ограничения | Что | Состояние | | --- | --- | | Телефон и имя автора | Не собираются: list.am отдаёт телефон отдельным запросом по нажатию «Позвонить». Решение заказчика: агент берёт номер по ссылке на объявление | | Фото | Только обложка | | Область вне Еревана | Определяется по названию населённого пункта в конце карточки и справочнику городов (`packages/shared/src/regions.ts`). Село, которого нет в справочнике, остаётся без области и не проходит фильтр по области | | Площадь домов | Иногда берётся площадь участка вместо площади дома | | Устойчивость | Своей защиты от блокировок нет: всё зависит от ридера. Если страница объявления не прочиталась, объявление не теряется — попытка повторится на следующем круге. При сбое всех категорий суперадмин получает алерт в Telegram (не чаще раза в час) | ## Наблюдения о list.am - ID объявлений растут по порядку, около 4 000 в день. Это используется как дешёвый предварительный отсев: ID, отстающий от последних больше чем на 20 000, заведомо старше 3 дней. - У list.am две нумерации: старая (до ~24,3 млн, закончилась в сентябре 2026) и новая «995…». ## Запасной источник: фид (`LISTING_SOURCE=feed`) `FeedListingSource` читает JSON-фид по `LISTING_FEED_URL` (официальный API или провайдер данных). К конвейеру он пока не подключён — понадобится, если появится источник с телефонами. Формат: `GET {url}?city=…&category=…&ownerOnly=…[&cursor=…]`, заголовок `Authorization: Bearer {LISTING_FEED_TOKEN}`; ответ `{ "items": [...], "next": "курсор или null" }`. Обязательны только `id` и `url`; остальные поля: `title`, `description`, `price`, `currency`, `dealType`, `propertyType`, `region`, `district`, `address`, `rooms`, `floor`, `floorsTotal`, `area`, `ownerName`, `phone`, `isAgency`, `publishedAt`, `photos`. ## Снятые и повторно опубликованные объявления - Раз в 10 минут проверяется пачка из 20 объявлений, по которым есть лиды; каждое объявление — раз в сутки, сначала те, что кто-то брал в работу. Запросы идут без ключа ридера. - Признак снятия (замер 08.10.2026): у страницы нет подвала с номером объявления (ридер отвечает 422), а заголовок — «Данного объявления не существует». Если подвала нет, но и такого заголовка нет, решение откладывается на час. - Снятое объявление снова появилось в списке категории под тем же номером — оно становится активным, и компании, у которой прошлый лид закрыт сделкой или отказом, приходит новый лид. - Объект под новым номером связывается с прошлым лидом, если совпали адрес и телефон автора. Телефон парсер не собирает, поэтому на практике работает запасное правило: совпали адрес, тип, комнаты, площадь и этаж, а прошлое объявление снято. Возможна ложная связь для одинаковых квартир в одном доме — она влияет только на блок «Уже был в работе». - 08.10.2026 пробный баланс ключа Jina закончился (ответ 402): парсер сам работает без ключа.