Files
yuriandClaude Opus 5.5 9749da5ae7 Lead Radar: исходный код
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 23:11:04 +04:00

10 KiB
Raw Permalink Blame History

Парсер 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): парсер сам работает без ключа.