fix(scrapers): avito detail parser — захват Жилая площадь / Высота потолков / Отделка #1550

Merged
lekss361 merged 2 commits from fix/avito-detail-apartment-fields into main 2026-06-16 08:32:15 +00:00
Owner

Проблема (verified на проде + live)

Avito detail-парсер теряет поля квартир: RUS_FIELD_MAP (avito_detail.py) не содержит «Жилая площадь», «Высота потолков», «Отделка». На проде living_area_m2 = 0/15917 заполнено (колонка существует с миграции 019, но в неё никогда не писали).

Live-проверка 3 свежих EKB-объявлений (HTTP 200): подтверждены лейблы «Жилая площадь» (формат "12.6\xa0м²", nbsp), «Высота потолков» ("2.7\xa0м"). «Отделка»/«Стоимость ремонта» на этих 3 не встретились — «Отделка» добавлена (встречается у новостроек), repair_cost НЕ добавлен (лейбл не подтверждён).

⚠️ Это layer-1 (field-map). Доминирующий разрыв — detail-enrichment покрывает лишь ~2-3% avito-объявлений (scrape_pipeline.py:336 обрывает loop после 3 подряд anti-bot блоков на server-IP). Это отдельная проблема (нужны прокси/резидентный IP — вне headless), фиксится не здесь. Данный PR прекращает терять поля там, где detail ВСЁ ЖЕ парсится, и готовит схему к росту покрытия.

Фикс

avito_detail.py — зеркалит существующий паттерн area_m2/kitchen_area_m2 в 4 местах (RUS_FIELD_MAP → DetailEnrichment dataclass → parse elif → save COALESCE + params):

  • «Жилая площадь» → living_area_m2 (_parse_float_m2, колонка уже есть).
  • «Высота потолков» → ceiling_height_m (новый хелпер _parse_height_m: "2.7 м"/"2,7 м"/"3 м"/nbsp → float; junk → None).
  • «Отделка» → finishing (cleaned text, .strip().lower()).

data/sql/111_listings_avito_detail_fields.sqlADD COLUMN IF NOT EXISTS ceiling_height_m numeric(5,2) + finishing text (idempotent; living_area_m2 уже существует → не трогаем).

tests/test_avito_detail_parse.py — happy-path с nbsp-значениями, absent-fields→None, 5 unit-тестов _parse_height_m, strip+lower для finishing.

Проверки

  • uv run pytest (полный suite, CI deselect): 1795 passed, 2 deselected.
  • ruff check + ruff format --check: clean.
  • code-reviewer: APPROVE (4-местная разводка полная, оба minor применены).

Зависимость

Миграция 111 (110 зарезервирована родственным PR #1549). Конфликтов файлов с #1549 нет.

## Проблема (verified на проде + live) Avito detail-парсер **теряет** поля квартир: `RUS_FIELD_MAP` (`avito_detail.py`) не содержит «Жилая площадь», «Высота потолков», «Отделка». На проде `living_area_m2 = 0/15917` заполнено (колонка существует с миграции 019, но в неё никогда не писали). Live-проверка 3 свежих EKB-объявлений (HTTP 200): подтверждены лейблы `«Жилая площадь»` (формат `"12.6\xa0м²"`, nbsp), `«Высота потолков»` (`"2.7\xa0м"`). `«Отделка»`/`«Стоимость ремонта»` на этих 3 не встретились — «Отделка» добавлена (встречается у новостроек), `repair_cost` НЕ добавлен (лейбл не подтверждён). > ⚠️ Это layer-1 (field-map). Доминирующий разрыв — detail-enrichment покрывает лишь **~2-3%** avito-объявлений (`scrape_pipeline.py:336` обрывает loop после 3 подряд anti-bot блоков на server-IP). Это отдельная проблема (нужны прокси/резидентный IP — вне headless), фиксится не здесь. Данный PR прекращает терять поля там, где detail ВСЁ ЖЕ парсится, и готовит схему к росту покрытия. ## Фикс `avito_detail.py` — зеркалит существующий паттерн `area_m2`/`kitchen_area_m2` в 4 местах (RUS_FIELD_MAP → DetailEnrichment dataclass → parse elif → save COALESCE + params): - «Жилая площадь» → `living_area_m2` (`_parse_float_m2`, колонка уже есть). - «Высота потолков» → `ceiling_height_m` (новый хелпер `_parse_height_m`: "2.7 м"/"2,7 м"/"3 м"/nbsp → float; junk → None). - «Отделка» → `finishing` (cleaned text, `.strip().lower()`). `data/sql/111_listings_avito_detail_fields.sql` — `ADD COLUMN IF NOT EXISTS ceiling_height_m numeric(5,2)` + `finishing text` (idempotent; `living_area_m2` уже существует → не трогаем). `tests/test_avito_detail_parse.py` — happy-path с nbsp-значениями, absent-fields→None, 5 unit-тестов `_parse_height_m`, strip+lower для finishing. ## Проверки - `uv run pytest` (полный suite, CI deselect): **1795 passed, 2 deselected**. - `ruff check` + `ruff format --check`: clean. - code-reviewer: ✅ APPROVE (4-местная разводка полная, оба minor применены). ## Зависимость Миграция **111** (110 зарезервирована родственным PR #1549). Конфликтов файлов с #1549 нет.
lekss361 added 2 commits 2026-06-16 07:24:59 +00:00
fix(tradein): avito detail parser — добавить living_area_m2/ceiling_height_m/finishing
All checks were successful
CI / frontend-tests (push) Has been skipped
CI / openapi-codegen-check (push) Has been skipped
CI / changes (push) Successful in 7s
CI / backend-tests (push) Has been skipped
5285a03c98
STEP 0 (живая проверка 2026-06-16): три страницы EKB квартир вернули HTTP 200.
Подтверждены метки: "Жилая площадь" (напр. "12.6 м²"), "Высота потолков"
("2.7 м"), "Отделка" (напр. "чистовая"). "Стоимость ремонта" — не обнаружена.

- RUS_FIELD_MAP: добавлены три записи.
- DetailEnrichment: новые поля living_area_m2/ceiling_height_m/finishing
  (в dataclass attrs и в locals-блоке parse_detail_html).
- parse_detail_html: ветки elif для трёх полей; helper _parse_height_m().
- save_detail_enrichment: COALESCE-строки + params-dict для новых полей.
- migration 111: ADD COLUMN IF NOT EXISTS ceiling_height_m, finishing
  (living_area_m2 уже существовала, migration не нужна).
- Тесты: 5 новых кейсов в test_avito_detail_parse.py + 5 unit-тестов _parse_height_m.
  Полный suite: 1795 passed, 2 deselected.
fix(tradein): review minors — ceiling_height_m numeric(5,2) + strengthen finishing test
All checks were successful
CI / changes (push) Successful in 8s
CI / backend-tests (push) Has been skipped
CI / frontend-tests (push) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI / openapi-codegen-check (push) Has been skipped
CI / changes (pull_request) Successful in 6s
e76a8dcaef
- Migration 111: ceiling_height_m numeric → numeric(5,2) (schema consistency with living_area_m2/kitchen_area_m2)
- test_finishing_stored_lowercase_stripped: fixture now uses padded uppercase '  Чистовая  ' to actually exercise .strip().lower(), asserts finishing == 'чистовая'
lekss361 added the
bug
scope/backend
scrapers
labels 2026-06-16 07:25:18 +00:00
lekss361 merged commit 6747ce941b into main 2026-06-16 08:32:15 +00:00
lekss361 deleted branch fix/avito-detail-apartment-fields 2026-06-16 08:32:15 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
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#1550
No description provided.