Данные о доме с площадок не доезжают: консьерж заполнен у 5 домов из 10 131, а парсим мы его на каждой карточке #3036

Closed
opened 2026-08-21 13:32:08 +00:00 by lekss361 · 3 comments
Owner

Найдено 2026-08-21 сверкой живого браузера со скраппером.

Замер прода

Из 10 131 дома заполнено:

Поле Заполнено Откуда должно приходить
year_built 7 380 (73%) ЖКХ / ГАР / ДомРФ
total_floors 5 799 (57%) справочники
material_walls 5 018 (50%) справочники
house_type 1 045 (10%) площадки
parking_type 30 площадки
reviews_count 30 Авито
closed_yard 19 площадки
rating_score 19 Авито
avito_validated_at 14 Авито
has_concierge 5 площадки
cian_validated_at 0 ЦИАН

Закономерность чёткая: что приходит из справочников — заполнено, что должно приходить с площадок — нет.

Два независимых обрыва

1. Парсим и выбрасываем

Шапка packages/scraper-kit/src/scraper_kit/providers/avito/detail.py:8-9, дословно:

House params (house_type, total_floors_house, lifts, concierge, closed_yard, house_catalog_url) — собираются но НЕ сохраняются в БД (Stage 2c)

То есть на каждой детальной странице эти поля уже извлекаются — и отбрасываются, с расчётом на отдельную стадию.

2. Та стадия почти не отрабатывает

fetch_house_catalog подключён — orchestration/pipeline.py зовёт его в четырёх местах в стадии ENRICH_HOUSES. Но отдельного расписания у каталога домов нет, он живёт внутри пайплайна, и результат соответствующий:

  • house_reviews — 188 строк
  • sellers — 131
  • listings.seller_id_fk заполнен у 287 из 105 958 объявлений (0,27%)

Почему это бьёт по продукту

backend/app/services/matching/conflict_resolution.py:57-58 объявляет has_concierge и closed_yard полями, источники которых — ["cian", "avito"]. backend/app/api/v1/trade_in.py:1219 их выбирает и отдаёт наружу.

То есть система спроектирована в расчёте на эти данные, отдаёт их в API и показывает пользователю — а фактически у 99,95% домов там «неизвестно». Это не «нет консьержа», это отсутствие данных, и на экране разница неочевидна.

Предлагаемое решение

Не чинить Stage 2c, а перестать выбрасывать уже распарсенное. Детальные страницы мы и так качаем, поля там лежат — сохранять их апсертом в houses по адресному отпечатку прямо из save_detail_enrichment. Это несравнимо дешевле, чем гонять отдельный обход каталога домов, который и без того тонет в банах.

Жёсткий порядок: сначала починить сам detail-путь. Сейчас avito_detail_backfill отказывает в 91-96% попыток, так что сохранять пока просто нечего. Зависимость от задачи про потерю ?context=.

Каталог домов при этом оставить: он даёт то, чего на карточке нет — отзывы, рейтинг, историю размещений, продавца. Но это второй приоритет и та же проблема банов.

Приёмка

  • House-поля из detail.py сохраняются в houses, а не отбрасываются
  • Разрешение конфликтов между источниками соблюдено (conflict_resolution.py)
  • Замер покрытия has_concierge / closed_yard / parking_type до и после
  • Решено, что делать с ENRICH_HOUSES: отдельное расписание или отказ от стадии

Scope: packages/scraper-kit/src/scraper_kit/providers/avito/detail.py, backend/app/services/matching/.

Найдено 2026-08-21 сверкой живого браузера со скраппером. ## Замер прода Из **10 131** дома заполнено: | Поле | Заполнено | Откуда должно приходить | |---|---|---| | `year_built` | 7 380 (73%) | ЖКХ / ГАР / ДомРФ | | `total_floors` | 5 799 (57%) | справочники | | `material_walls` | 5 018 (50%) | справочники | | `house_type` | 1 045 (10%) | площадки | | `parking_type` | **30** | площадки | | `reviews_count` | **30** | Авито | | `closed_yard` | **19** | площадки | | `rating_score` | **19** | Авито | | `avito_validated_at` | **14** | Авито | | `has_concierge` | **5** | площадки | | `cian_validated_at` | **0** | ЦИАН | Закономерность чёткая: **что приходит из справочников — заполнено, что должно приходить с площадок — нет.** ## Два независимых обрыва ### 1. Парсим и выбрасываем Шапка `packages/scraper-kit/src/scraper_kit/providers/avito/detail.py:8-9`, дословно: > House params (house_type, total_floors_house, lifts, concierge, closed_yard, house_catalog_url) — **собираются но НЕ сохраняются в БД (Stage 2c)** То есть на каждой детальной странице эти поля уже извлекаются — и отбрасываются, с расчётом на отдельную стадию. ### 2. Та стадия почти не отрабатывает `fetch_house_catalog` подключён — `orchestration/pipeline.py` зовёт его в четырёх местах в стадии ENRICH_HOUSES. Но отдельного расписания у каталога домов нет, он живёт внутри пайплайна, и результат соответствующий: - `house_reviews` — 188 строк - `sellers` — 131 - `listings.seller_id_fk` заполнен у **287 из 105 958** объявлений (0,27%) ## Почему это бьёт по продукту `backend/app/services/matching/conflict_resolution.py:57-58` объявляет `has_concierge` и `closed_yard` полями, источники которых — `["cian", "avito"]`. `backend/app/api/v1/trade_in.py:1219` их выбирает и отдаёт наружу. То есть система спроектирована в расчёте на эти данные, отдаёт их в API и показывает пользователю — а фактически у 99,95% домов там «неизвестно». Это не «нет консьержа», это отсутствие данных, и на экране разница неочевидна. ## Предлагаемое решение **Не чинить Stage 2c, а перестать выбрасывать уже распарсенное.** Детальные страницы мы и так качаем, поля там лежат — сохранять их апсертом в `houses` по адресному отпечатку прямо из `save_detail_enrichment`. Это несравнимо дешевле, чем гонять отдельный обход каталога домов, который и без того тонет в банах. **Жёсткий порядок:** сначала починить сам detail-путь. Сейчас `avito_detail_backfill` отказывает в 91-96% попыток, так что сохранять пока просто нечего. Зависимость от задачи про потерю `?context=`. Каталог домов при этом оставить: он даёт то, чего на карточке нет — отзывы, рейтинг, историю размещений, продавца. Но это второй приоритет и та же проблема банов. ## Приёмка - [ ] House-поля из `detail.py` сохраняются в `houses`, а не отбрасываются - [ ] Разрешение конфликтов между источниками соблюдено (`conflict_resolution.py`) - [ ] Замер покрытия `has_concierge` / `closed_yard` / `parking_type` до и после - [ ] Решено, что делать с ENRICH_HOUSES: отдельное расписание или отказ от стадии Scope: `packages/scraper-kit/src/scraper_kit/providers/avito/detail.py`, `backend/app/services/matching/`.
lekss361 added the
data
priority/p1
scope/backend
scrapers
tradein
labels 2026-08-21 13:32:32 +00:00
Collaborator

Сделано в PR #3040 — «перестать выбрасывать уже распарсенное»: save_detail_enrichment вторым оператором fill-only заполняет houses.has_concierge / closed_yard / total_floors / house_type через listings.house_id_fk (под SAVEPOINT; заполненное не затирается — канон Stage 2c сохраняется).

Две поправки к постановке по ходу:

  1. Лифтов в houses нет. conflict_resolution объявляет passenger_lifts_count/cargo_lifts_count с источниками ["cian","avito"], но колонок в схеме нет (information_schema на проде). Писать некуда — нужен отдельный пункт (миграция + решение, нужны ли они).
  2. cian как источник has_concierge/closed_yard — тоже декларация: в коде ЦИАН эти поля не парсятся вовсе (grep по providers/cian пуст). Так что «данные с площадок» сегодня = только Авито-детали.

Приёмка — числом: count(*) FILTER (WHERE has_concierge IS NOT NULL) в houses (сейчас 5 из 10 131) должен расти с каждым успешным detail-обогащением. Оговорка: detail Авито сейчас отказывает в 91–96 % (#3035/#2827), эффект проявится, когда карточки снова откроются; 10 051 уже обогащённую карточку без повторного скачивания не дозаполнить.

Сделано в PR #3040 — «перестать выбрасывать уже распарсенное»: `save_detail_enrichment` вторым оператором fill-only заполняет `houses.has_concierge / closed_yard / total_floors / house_type` через `listings.house_id_fk` (под SAVEPOINT; заполненное не затирается — канон Stage 2c сохраняется). Две поправки к постановке по ходу: 1. **Лифтов в `houses` нет.** `conflict_resolution` объявляет `passenger_lifts_count`/`cargo_lifts_count` с источниками `["cian","avito"]`, но колонок в схеме нет (information_schema на проде). Писать некуда — нужен отдельный пункт (миграция + решение, нужны ли они). 2. **`cian` как источник has_concierge/closed_yard — тоже декларация**: в коде ЦИАН эти поля не парсятся вовсе (grep по `providers/cian` пуст). Так что «данные с площадок» сегодня = только Авито-детали. Приёмка — числом: `count(*) FILTER (WHERE has_concierge IS NOT NULL)` в `houses` (сейчас 5 из 10 131) должен расти с каждым успешным detail-обогащением. Оговорка: detail Авито сейчас отказывает в 91–96 % (#3035/#2827), эффект проявится, когда карточки снова откроются; 10 051 уже обогащённую карточку без повторного скачивания не дозаполнить.
Collaborator

На проде с 21.08 14:59 UTC (17a6dae3), база для приёмки

houses на 15:03 UTC: has_concierge 5, closed_yard 19, total_floors 5 799, house_type 1 045 из 10 132. Код в tradein-scraper и tradein-backend — с _HOUSE_PARAMS_SQL (проверено по модулю в контейнерах).

Дальше числа должны расти с каждым успешным detail-обогащением Авито (avito_detail_backfill ежедневно ~01:30 UTC + detail-стадия свипов). Проверю 23.08 ~09:10 UTC и запишу дельту; если прирост нулевой при ненулевых detail_enriched в counters прогонов — это дефект, а не «Авито не пустил».

### На проде с 21.08 14:59 UTC (`17a6dae3`), база для приёмки `houses` на 15:03 UTC: `has_concierge` **5**, `closed_yard` **19**, `total_floors` 5 799, `house_type` 1 045 из 10 132. Код в `tradein-scraper` и `tradein-backend` — с `_HOUSE_PARAMS_SQL` (проверено по модулю в контейнерах). Дальше числа должны расти с каждым успешным detail-обогащением Авито (`avito_detail_backfill` ежедневно ~01:30 UTC + detail-стадия свипов). Проверю 23.08 ~09:10 UTC и запишу дельту; если прирост нулевой при ненулевых `detail_enriched` в counters прогонов — это дефект, а не «Авито не пустил».
Collaborator

Приёмка: поля начали доезжать — рост в 8 раз по консьержу за четыре дня

Замер снят с tradein-postgres на Beget (26.08 06:50 UTC). Оговорка о происхождении данных: продукты переехали на Poincare 25.08 в 19:36 UTC, поэтому эта база стоит на данных до cutover — последнее объявление в ней от 25.08 17:23. То есть окно замера — 21.08 15:03 → 25.08 17:23, ровно те четверо суток, когда правка работала на Beget.

поле было (21.08 15:03) стало (25.08 17:23)
has_concierge 5 42
closed_yard 19 144
total_floors 5 799 5 870
house_type 1 045 1 357
домов всего 10 132 10 327

Для сравнения: до правки те же 5 и 19 накапливались месяцами. Механизм из PR #3040 (fill-only UPDATE houses … FROM listings WHERE house_id_fk = h.id под SAVEPOINT) работает, и работает при том, что detail-обогащение Авито в эти дни было в плохом состоянии (#3044: за 7 дней ни одного успешного avito_detail_backfill) — значит поля приезжают и с detail-стадии свипов, а не только с бэкфилла.

Закрываю. Что осталось за рамками задачи и живёт отдельно:

  • лифты (passenger_lifts_count / cargo_lifts_count) — колонок в houses нет, писать некуда; conflict_resolution объявляет их зря;
  • ЦИАН как источник has_concierge / closed_yard — тоже декларация: в коде провайдера эти поля не парсятся;
  • быстрее всего цифры пойдут вверх, когда починится detail Авито (#3044 / #3045).

Если нужен контрольный замер уже на живой базе Poincare — снимите тот же запрос там, счётчики должны быть не ниже.

## Приёмка: поля начали доезжать — рост в 8 раз по консьержу за четыре дня Замер снят с `tradein-postgres` на Beget (26.08 06:50 UTC). Оговорка о происхождении данных: продукты переехали на Poincare 25.08 в 19:36 UTC, поэтому эта база стоит на данных **до cutover** — последнее объявление в ней от 25.08 17:23. То есть окно замера — 21.08 15:03 → 25.08 17:23, ровно те четверо суток, когда правка работала на Beget. | поле | было (21.08 15:03) | стало (25.08 17:23) | |---|---:|---:| | `has_concierge` | 5 | **42** | | `closed_yard` | 19 | **144** | | `total_floors` | 5 799 | 5 870 | | `house_type` | 1 045 | 1 357 | | домов всего | 10 132 | 10 327 | Для сравнения: до правки те же 5 и 19 накапливались месяцами. Механизм из PR #3040 (fill-only `UPDATE houses … FROM listings WHERE house_id_fk = h.id` под SAVEPOINT) работает, и работает при том, что detail-обогащение Авито в эти дни было в плохом состоянии (#3044: за 7 дней ни одного успешного `avito_detail_backfill`) — значит поля приезжают и с detail-стадии свипов, а не только с бэкфилла. Закрываю. Что осталось за рамками задачи и живёт отдельно: - **лифты** (`passenger_lifts_count` / `cargo_lifts_count`) — колонок в `houses` нет, писать некуда; `conflict_resolution` объявляет их зря; - **ЦИАН** как источник `has_concierge` / `closed_yard` — тоже декларация: в коде провайдера эти поля не парсятся; - быстрее всего цифры пойдут вверх, когда починится detail Авито (#3044 / #3045). Если нужен контрольный замер уже на живой базе Poincare — снимите тот же запрос там, счётчики должны быть не ниже.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#3036
No description provided.