fix(tradein/avito): парсер карточки читал только первый блок параметров — параметры дома терялись молча #3048
No reviewers
Labels
No labels
Fable 5 ревью
GG-форсайт
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
feedback/max
generative
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
ИРД
вторичка
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#3048
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/avito-detail-fields"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Закрывает #3047. Подготовка к массовому обогащению: гонять 11 021 карточку, зная, что парсер недобирает поля, — значит гонять их дважды.
Правка сделана по двум живым эталонам, снятым с прода 2026-08-21 через браузерный sidecar, а не по догадкам. Обе карточки лежат в фикстурах и специально различаются: одна от частника, другая от агентства — иначе ветку
is_homeownerнечем проверить.Корневой дефект
Авито отдаёт «О квартире» и «О доме» как два отдельных
<div data-marker='item-view/item-params'>, каждый со своим<ul>. Код бралtree.css_first(...)— только первый.sale_typeлежит в первом блоке, поэтому читался. Аhouse_type,total_floors_houseи лифты — во втором, и терялись молча на каждой карточке. Ветка, которая должна была это ловить, проверялаlen(ul_els) >= 2внутри первого div — не срабатывала никогда, потому что там всегда ровно один<ul>. Комментарий рядом («некоторые страницы совмещают всё в одном ul») описывал прежнюю вёрстку.Теперь собираем
<ul>из всех блоков с этим маркером — устойчиво и к старой форме, и к порядку блоков.Побочно это объясняет
houses_enriched: 0в прогоне 17:33: #3040 пишет параметры дома из карточки вhouses, а параметры всегда приходили пустыми. Отдельной правки там, судя по всему, не требуется — данные просто начнут появляться.Остальные поля
metro_stations— было 28 записей из 54 855 против 21 112 у ЦИАН. Причина: тянулось регуляркой из текста описания, шаблон ловил суффиксы-ская/-инская, а «Уралмаш» и «Машиностроителей» мимо. Переведено на разметку:div#item-view-address, станция — по иконке «Пешком до метро», имя читается как «текст обёртки минус текст времени», что устойчиво к смене хэшированных классов. Описание осталось фолбэком.is_homeowner— не парсился вовсе. Взят структурный признак[data-marker='seller-info/label']: «Частное лицо» →True, «Агентство» →False. Именно структурный, а не поиск слова в тексте — иначе получился бы второй_extract_metro.days_on_market— на странице в явном виде нет, выводится из даты публикации. Попутно нашлось, чтоpublish_dateтоже никогда не парсился: дата лежит в соседнем[data-marker='item-view/item-date'], а код искал её внутри блокаitem-view/item-id. Починено, старый вложенный путь оставлен фолбэком, регресс-тесты на переход года проходят. Семантикаmax((today - publish_date).days, 0)— та же, что у Яндекса.cadastral_number— подтверждено отсутствие, ничего не парсим. Ни текста «кадастр», ни значения на обеих карточках; единственный след — пустоеdomotekaReportTeaser.cadastralNumberв JSON-блоке, схема зарезервирована Авито и не заполняется. Колонка пуста у всех источников. Зафиксировано в докстринге, чтобы не искали снова.Докстринг файла приведён в соответствие реальности. Прежний перечислял поля, которых код не извлекает — на это купился я сам, когда назвал «обещанными» три поля, которых парсер никогда не обещал.
Что это НЕ меняет
Ни одно из полей не влияет на цену. По
estimator.py:sale_type,is_homeowner,metro_stationsне упоминаются;cadastral_numberтам относится к другой колонке.estimator.pyне тронут.Единственное следствие для расчётов:
days_on_marketпитает прогноз срока продажиest_days_on_market. У Авито он был практически всегдаNULL(в коде на это есть явный комментарий, #2894), теперь появятся реальные значения — после деплоя стоит посмотреть, как это скажется на прогнозе срока для avito-объектов. По цене регрессий быть не может.Честная оговорка
Продакшн-регрессию
sale_type(июнь 59% → август 1,6%) две эталонные карточки не воспроизводят — обе парсятся верно и до, и после правки. Значит падение покрытия вызвано скорее блокировками на части фетчей, чем структурой этих страниц. Найденный дефект чтения при этом реален и исправлен, но приписывать ему всю регрессию было бы натяжкой.Тесты
test_avito_detail_fields_3047.py— 11 тестов на обеих эталонных карточках. Проверка идёт по двум файлам намеренно: на одном селектор легко подогнать.ruff checkчисто, pre-commit прошёлПравка ссылок
Воркер сослался на
#3086, которого не существует, и назвал по нему тест-файл. Заведён настоящий #3047 с замерами; все ссылки и имя файла приведены к нему отдельным коммитом.Refs #3047, #3040, #3045
Разобрались по эталонной разметке (2 живые detail-карточки, 2026-08-21), что реально парсится и что нет: - sale_type: item-view/item-params — Avito отдаёт "О квартире" и "О доме" как ДВА ОТДЕЛЬНЫХ <div> с ОДНИМ И ТЕМ ЖЕ маркером, а не один div с двумя <ul>, как предполагал старый код. css_first брал только ПЕРВЫЙ div — sale_type это не задевало (он в первом блоке), но house_type/total_floors_house/лифты из второго блока терялись молча через мёртвую ветку `len(ul_els) >= 2`. Теперь читаем <ul> из ВСЕХ блоков с этим маркером — устойчиво к порядку блоков. - metro_stations: раньше только эвристика по тексту описания (28 строк из 54 855). Основной источник теперь — структурная разметка (#item-view-address, иконка "Пешком до метро"), покрывает станции без ограничения на суффикс имени (METRO_RE ловил только "-ская"/"-инская"). Описание — фолбэк. - is_homeowner: не парсился вовсе. [data-marker='seller-info/label'] — "Частное лицо" -> True, "Агентство" -> False. Подтверждено разными значениями на двух эталонах. - days_on_market: на странице явно нет, но есть publish_date, из которого честно выводится. Заодно чинит сам publish_date — искали дату ВНУТРИ item-id-блока, а она в СОСЕДНЕМ [data-marker='item-view/item-date'] ("сегодня в HH:MM"). - cadastral_number: подтверждено отсутствие на странице (не парсим, не выдумываем). Тесты на реальной разметке (backend/tests/test_avito_detail_fields_3086.py) на обеих эталонных фикстурах (большие embedded JS-блобы вырезаны из фикстур — не используются DOM-based парсером, экономят место). estimator.py не тронут — ни одно поле не влияет на цену.