Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
10 KiB
Парсер List.am
Модуль взят из прежней CRM (apps/api/src/import → apps/api/src/parser) и доработан.
Конвейер, который им пользуется, — apps/api/src/ingest/service.ts.
Как это работает
- Все категории недвижимости list.am читаются по кругу: квартиры, дома, коммерческая,
участки, гаражи — продажа и аренда (10 категорий). Напрямую на list.am сервис не ходит:
сайт закрыт Cloudflare, страницы читаются через публичный ридер Jina (
r.jina.ai). - Свежесть. Список list.am отсортирован по «поднятию», а блок «Топ» ротируется, поэтому
незнакомое объявление ещё не значит новое. Возраст определяется по дате «Размещено» на
странице объявления. В базу попадают объявления, размещённые не раньше чем 3 дня назад
(
FRESH_DAYS). Более старые запоминаются только по ID (seen_listings), чтобы не перечитывать их при каждом «поднятии». - Общая база. Свежее объявление сохраняется в таблицу
listingsодин раз, независимо от того, есть ли под него фильтр у клиентов. - Раздача. Объявление из базы сопоставляется с фильтрами всех компаний с действующей подпиской; где подошло — создаётся лид в пуле и уходят уведомления в Telegram. Новый или изменённый фильтр сразу получает подходящие объявления из базы за последние 3 дня — в пул, без уведомлений.
- Первый проход по категории наполняет базу без лидов: эти объявления висели на сайте до подключения.
Что собирается
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): парсер сам работает без ключа.