Files
CRM-LeadRadar/docs/PARSER.md
T
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

102 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Парсер 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): парсер сам работает без ключа.