[P1][tradein][backend][bug] Avito listing_date 100% NULL на проде — диагностировать + починить (temporal linkage killer #2) #728

Closed
opened 2026-05-30 13:42:03 +00:00 by bot-analyst · 1 comment
Collaborator

Worker: это исполняемый спек. STEP 1 ОБЯЗАТЕЛЕН — сначала воспроизведи на реальном HTML, потом чини. Гипотеза JSON-LD ниже НЕ подтверждена живым DOM — проверь прежде чем кодить. Неясность → коммент, НЕ угадывай.

Задача

Диагностировать, почему listings.listing_date у source=avito = NULL в ~91.5% (1 180 из 13 899 заполнено), и починить парсинг даты в avito.py так, чтобы свежие avito-карточки получали listing_date.

Контекст (факты)

  • live-БД 2026-05-30: avito 13 899 listings, has listing_date = 1 180 (8.5%). cian — 4 528/4 529 (100%, через addedTimestamp).
  • Это killer #2 временного разрыва (см. #724): NULL listing_dateCOALESCE(listing_date, scraped_at) подставляет дату скрейпа (май 2026) → карточка ещё дальше от замороженных сделок (max 2026-01-01) → не попадает в matching-окно street_sales_vs_listings → вклад в linkage_rate=0.
  • ВАЖНО — конфликт с кодом: прошлый PR (M-followup, коммент avito.py:74-77) уже расширял _parse_relative_date под keyword/absolute форматы именно ради «100% NULL». Раз снова ~91% NULL — либо тот фикс не сработал в prod, либо Avito опять сменил разметку. Поэтому гипотеза research-note про «JSON-LD» — кандидат, НЕ факт. Сначала reproduce.

Files (точные — проверено чтением)

  • tradein-mvp/backend/app/services/scrapers/avito.py:
    • def _parse_relative_date(s: str | None) -> date | None — строки 68-99 (relative numeric / keyword / absolute day+month).
    • Парсинг даты карточки — строки 315-327: card.css_first('[data-marker="item-date"]') → fallback item-date-info → fallback _parse_relative_date(card_text). NB: на стр. 326 есть подозрительно мёртвая повторная строка date_el = card.css_first('[data-marker="item-date"]') сразу после fallback — проверь, не баг ли (присваивание не используется).
  • tradein-mvp/backend/tests/test_avito_listing_date.py — существующий тест-сьют дат (добавляй кейсы сюда).
  • Референс рабочего паттерна: tradein-mvp/backend/app/services/scrapers/cian.py:390-394 (datetime.fromtimestamp(addedTimestamp)).

Шаги (обязательный порядок)

  1. Reproduce: возьми реальный HTML свежей Avito SERP-выдачи (EKB квартиры) — через существующий fetch-механизм скрейпера или сохранённый ответ. Определи, ГДЕ сейчас лежит дата: data-marker="item-date"? в свободном тексте карточки? в <script type="application/ld+json"> (datePublished)? в window.__initialData__/JSON? Зафиксируй вывод в комментарии к PR.
  2. Fix: допиши извлечение из фактического источника даты (тот, что нашёл в шаге 1). Если это JSON-LD — парси datePublished; если изменился текст — расширь _parse_relative_date. Убери мёртвую строку 326 если подтвердишь, что баг.
  3. Test: добавь в test_avito_listing_date.py кейс с реальным HTML-фрагментом из шага 1 (как фикстура), проверяющий, что дата извлекается.

Definition of Done (бинарно проверяемо)

  • В PR-описании указано, где именно Avito сейчас отдаёт дату (вывод шага 1, со ссылкой на фрагмент HTML).
  • cd tradein-mvp/backend && uv run pytest tests/test_avito_listing_date.py -v → pass, включая НОВЫЙ кейс с реальным фрагментом из шага 1 (фикстура добавлена в тест-файл/tests/fixtures).
  • cd tradein-mvp/backend && uv run ruff check app/services/scrapers/avito.py tests/test_avito_listing_date.py → clean.
  • Мёртвая строка avito.py:326 (повторный date_el = ... без использования) либо удалена, либо в PR объяснено, почему она нужна.

Не делать (out of scope)

  • НЕ трогать cian/yandex date-парсинг (cian работает).
  • НЕ менять street_sales_vs_listings SQL и COALESCE логику.
  • НЕ заниматься разморозкой rosreestr (это #724) и backfill старых avito-строк (отдельный follow-up; этот issue — про парсер свежих).
  • НЕ прогонять реальный сетевой скрейп Avito в CI/тестах — только фикстура.

Risk

  • Avito anti-bot: live-fetch для reproduce может потребовать актуальных cookies/headers (см. avito.py fetch-секцию). Если fetch недоступен — используй сохранённый HTML из admin-скрейпа; зафиксируй в комментарии.

Связано

  • #724 (temporal blocker, needs-human) — этот issue = killer #2 того же разрыва, fix независим и не требует human.
  • Research: inbox/2026-05-30-1654-main-session-temporal-linkage-root-cause-and-levers.
  • Оба рычага (этот + размороженный rosreestr) поднимают linkage только в паре, но avito-фикс самостоятельно ценен (свежие listings получат корректную дату на будущее).
> Worker: это исполняемый спек. STEP 1 ОБЯЗАТЕЛЕН — сначала воспроизведи на реальном HTML, потом чини. Гипотеза JSON-LD ниже НЕ подтверждена живым DOM — проверь прежде чем кодить. Неясность → коммент, НЕ угадывай. ## Задача Диагностировать, почему `listings.listing_date` у source=avito = NULL в ~91.5% (1 180 из 13 899 заполнено), и починить парсинг даты в `avito.py` так, чтобы свежие avito-карточки получали `listing_date`. ## Контекст (факты) - live-БД 2026-05-30: avito 13 899 listings, has `listing_date` = 1 180 (8.5%). cian — 4 528/4 529 (100%, через `addedTimestamp`). - Это **killer #2** временного разрыва (см. #724): NULL `listing_date` → `COALESCE(listing_date, scraped_at)` подставляет дату скрейпа (май 2026) → карточка ещё дальше от замороженных сделок (max 2026-01-01) → не попадает в matching-окно `street_sales_vs_listings` → вклад в `linkage_rate=0`. - **ВАЖНО — конфликт с кодом:** прошлый PR (M-followup, коммент `avito.py:74-77`) уже расширял `_parse_relative_date` под keyword/absolute форматы именно ради «100% NULL». Раз снова ~91% NULL — либо тот фикс не сработал в prod, либо Avito опять сменил разметку. Поэтому гипотеза research-note про «JSON-LD» — кандидат, НЕ факт. Сначала reproduce. ## Files (точные — проверено чтением) - `tradein-mvp/backend/app/services/scrapers/avito.py`: - `def _parse_relative_date(s: str | None) -> date | None` — строки **68-99** (relative numeric / keyword / absolute day+month). - Парсинг даты карточки — строки **315-327**: `card.css_first('[data-marker="item-date"]')` → fallback `item-date-info` → fallback `_parse_relative_date(card_text)`. NB: на стр. **326** есть подозрительно мёртвая повторная строка `date_el = card.css_first('[data-marker="item-date"]')` сразу после fallback — проверь, не баг ли (присваивание не используется). - `tradein-mvp/backend/tests/test_avito_listing_date.py` — существующий тест-сьют дат (добавляй кейсы сюда). - Референс рабочего паттерна: `tradein-mvp/backend/app/services/scrapers/cian.py:390-394` (`datetime.fromtimestamp(addedTimestamp)`). ## Шаги (обязательный порядок) 1. **Reproduce:** возьми реальный HTML свежей Avito SERP-выдачи (EKB квартиры) — через существующий fetch-механизм скрейпера или сохранённый ответ. Определи, ГДЕ сейчас лежит дата: `data-marker="item-date"`? в свободном тексте карточки? в `<script type="application/ld+json">` (datePublished)? в `window.__initialData__`/JSON? Зафиксируй вывод в комментарии к PR. 2. **Fix:** допиши извлечение из фактического источника даты (тот, что нашёл в шаге 1). Если это JSON-LD — парси `datePublished`; если изменился текст — расширь `_parse_relative_date`. Убери мёртвую строку 326 если подтвердишь, что баг. 3. **Test:** добавь в `test_avito_listing_date.py` кейс с реальным HTML-фрагментом из шага 1 (как фикстура), проверяющий, что дата извлекается. ## Definition of Done (бинарно проверяемо) - [ ] В PR-описании указано, где именно Avito сейчас отдаёт дату (вывод шага 1, со ссылкой на фрагмент HTML). - [ ] `cd tradein-mvp/backend && uv run pytest tests/test_avito_listing_date.py -v` → pass, включая НОВЫЙ кейс с реальным фрагментом из шага 1 (фикстура добавлена в тест-файл/`tests/fixtures`). - [ ] `cd tradein-mvp/backend && uv run ruff check app/services/scrapers/avito.py tests/test_avito_listing_date.py` → clean. - [ ] Мёртвая строка `avito.py:326` (повторный `date_el = ...` без использования) либо удалена, либо в PR объяснено, почему она нужна. ## Не делать (out of scope) - НЕ трогать cian/yandex date-парсинг (cian работает). - НЕ менять `street_sales_vs_listings` SQL и `COALESCE` логику. - НЕ заниматься разморозкой rosreestr (это #724) и backfill старых avito-строк (отдельный follow-up; этот issue — про парсер свежих). - НЕ прогонять реальный сетевой скрейп Avito в CI/тестах — только фикстура. ## Risk - Avito anti-bot: live-fetch для reproduce может потребовать актуальных cookies/headers (см. `avito.py` fetch-секцию). Если fetch недоступен — используй сохранённый HTML из admin-скрейпа; зафиксируй в комментарии. ## Связано - #724 (temporal blocker, needs-human) — этот issue = killer #2 того же разрыва, fix независим и не требует human. - Research: `inbox/2026-05-30-1654-main-session-temporal-linkage-root-cause-and-levers`. - Оба рычага (этот + размороженный rosreestr) поднимают linkage только в паре, но avito-фикс самостоятельно ценен (свежие listings получат корректную дату на будущее).
Author
Collaborator

Дубликат #726 (создан параллельным bot-analyst раньше, уже с status/ready + labels, тот же scope — avito listing_date 91.5% NULL). Закрываю в пользу #726. Race двух analyst-окон.

Дубликат #726 (создан параллельным bot-analyst раньше, уже с `status/ready` + labels, тот же scope — avito listing_date 91.5% NULL). Закрываю в пользу #726. Race двух analyst-окон.
Sign in to join this conversation.
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#728
No description provided.