[tradein][backend][scrapers] Avito house-link: SERP-карточка JS-only → брать house_url из detail SSR (__preloadedState__) #871

Closed
opened 2026-05-31 08:12:31 +00:00 by lekss361 · 0 comments
Owner

🔍 Verified live 2026-05-31 (Playwright + curl_cffi + admin probe). Корень почему avito house-enrich мёртв (unique_houses=0 всегда, run 22): house-link на SERP рендерится клиентским JS — в SSR-HTML, который тянет наш curl_cffi-скрейпер, его НЕТ.

Доказательство

SERP /ekaterinburg/kvartiry/prodam:

  • curl_cffi (наш скрейпер, SSR, без JS): 0/41 карточек с house-link (run 22 unique_houses=0).
  • Playwright (реальный браузер, post-JS DOM): 21/60 карточек имеют a[href^="/catalog/houses/"], формат /catalog/houses/ekaterinburg/ul_gotvalda_6k1/142792?context=…. Наш селектор их БЫ поймал — но ?context=-токен генерится клиентским JS, в SSR ссылок нет.
  • avito.py:682 (card.css_first('a[href^="/catalog/houses/"]')) структурно не может извлечь house-link из SSR. Селектор не виноват — данных нет в SSR.

Detail-страница (тот же curl_cffi путь, fetch_detail) — house-link ЕСТЬ:

  • raw SSR-HTML /ekaterinburg/kvartiry/4-k._kvartira_127_m_38_et._7683141312 (status 200, не firewall, 846 КБ) содержит: __preloadedState__ ✓ И /catalog/houses/ekaterinburg/ul_gotvalda_6k1/142792 ✓ (+ /reviews), house_id 142792 присутствует.
  • Мы УЖЕ тянем detail для top-N → house_url берётся оттуда без доп. запросов.
  • Парсер house-каталога рабочий: POST /scrape/avito-house на /catalog/houses/ekaterinburg/ul_kuznetsova_7/23035681house_id:372289, HTTP 200 (TEST подтверждён).

Задача

  1. tradein-mvp/backend/app/services/scrapers/avito_detail.py — в fetch_detail/DetailEnrichment извлекать house catalog URL из detail-HTML: regex /catalog/houses/[a-z-]+/[a-z0-9_]+/\d+ (или из __preloadedState__), отдавать house_url + house_ext_id (последний сегмент \d+).
  2. tradein-mvp/backend/app/services/scrape_pipeline.py — Step 3-4: собирать unique_house_paths из detail-enrichment (а не только из lot.house_url SERP-карточек, которых 0). Дальше дедуп + fetch_house_catalog как сейчас.
  3. avito.py:677-690 — SERP house-link extraction оставить как best-effort (на случай возврата в SSR), пометить комментом что основной источник = detail.

Files

  • tradein-mvp/backend/app/services/scrapers/avito_detail.pyfetch_detail, DetailEnrichment (+house_url/house_ext_id).
  • tradein-mvp/backend/app/services/scrape_pipeline.py — Step 3-4 (источник house_paths).

Definition of Done (бинарно)

  • fetch_detail возвращает house_url/house_ext_id, когда /catalog/houses/ есть в detail-HTML; unit-тест на фикстуре detail-страницы (с house-link) → не None.
  • После sweep counters.unique_houses > 0 и houses_enriched > 0 для top-N (было 0). Verify: trigger /scrape/avito-city-sweep (pages=1) → scrape_runs.counters свежего run: unique_houses>0.
  • cd tradein-mvp/backend && uv run ruff check app/services/scrapers/avito_detail.py app/services/scrape_pipeline.py clean; uv run pytest -k "detail or house" pass.

Не делать

  • НЕ полагаться на SERP-карточку для house-link (JS-only, в SSR отсутствует).
  • НЕ трогать rate-limit/rotation detail-пути — это ОТДЕЛЬНАЯ задача (detail без IP-ротации кладёт sweep на первом 429; всплыло в той же пробе run 22).

Refs: run 22, #562 (house batch enrichment — этот фикс его разблокирует), эпик #774.

> 🔍 Verified live 2026-05-31 (Playwright + curl_cffi + admin probe). Корень почему avito house-enrich мёртв (`unique_houses=0` всегда, run 22): house-link на SERP рендерится клиентским JS — в SSR-HTML, который тянет наш `curl_cffi`-скрейпер, его НЕТ. ## Доказательство **SERP `/ekaterinburg/kvartiry/prodam`:** - curl_cffi (наш скрейпер, SSR, без JS): **0/41** карточек с house-link (run 22 `unique_houses=0`). - Playwright (реальный браузер, post-JS DOM): **21/60** карточек имеют `a[href^="/catalog/houses/"]`, формат `/catalog/houses/ekaterinburg/ul_gotvalda_6k1/142792?context=…`. Наш селектор их БЫ поймал — но `?context=`-токен генерится клиентским JS, в SSR ссылок нет. - ⇒ `avito.py:682` (`card.css_first('a[href^="/catalog/houses/"]')`) структурно не может извлечь house-link из SSR. **Селектор не виноват — данных нет в SSR.** **Detail-страница (тот же curl_cffi путь, `fetch_detail`) — house-link ЕСТЬ:** - raw SSR-HTML `/ekaterinburg/kvartiry/4-k._kvartira_127_m_38_et._7683141312` (status 200, не firewall, 846 КБ) содержит: `__preloadedState__` ✓ И `/catalog/houses/ekaterinburg/ul_gotvalda_6k1/142792` ✓ (+ `/reviews`), house_id `142792` присутствует. - Мы УЖЕ тянем detail для top-N → house_url берётся оттуда без доп. запросов. - Парсер house-каталога рабочий: `POST /scrape/avito-house` на `/catalog/houses/ekaterinburg/ul_kuznetsova_7/23035681` → `house_id:372289`, HTTP 200 (TEST подтверждён). ## Задача 1. `tradein-mvp/backend/app/services/scrapers/avito_detail.py` — в `fetch_detail`/`DetailEnrichment` извлекать house catalog URL из detail-HTML: regex `/catalog/houses/[a-z-]+/[a-z0-9_]+/\d+` (или из `__preloadedState__`), отдавать `house_url` + `house_ext_id` (последний сегмент `\d+`). 2. `tradein-mvp/backend/app/services/scrape_pipeline.py` — Step 3-4: собирать `unique_house_paths` из detail-enrichment (а не только из `lot.house_url` SERP-карточек, которых 0). Дальше дедуп + `fetch_house_catalog` как сейчас. 3. `avito.py:677-690` — SERP house-link extraction оставить как best-effort (на случай возврата в SSR), пометить комментом что основной источник = detail. ## Files - `tradein-mvp/backend/app/services/scrapers/avito_detail.py` — `fetch_detail`, `DetailEnrichment` (+house_url/house_ext_id). - `tradein-mvp/backend/app/services/scrape_pipeline.py` — Step 3-4 (источник house_paths). ## Definition of Done (бинарно) - [ ] `fetch_detail` возвращает `house_url`/`house_ext_id`, когда `/catalog/houses/` есть в detail-HTML; unit-тест на фикстуре detail-страницы (с house-link) → не None. - [ ] После sweep `counters.unique_houses > 0` и `houses_enriched > 0` для top-N (было 0). Verify: trigger `/scrape/avito-city-sweep` (pages=1) → `scrape_runs.counters` свежего run: `unique_houses>0`. - [ ] `cd tradein-mvp/backend && uv run ruff check app/services/scrapers/avito_detail.py app/services/scrape_pipeline.py` clean; `uv run pytest -k "detail or house"` pass. ## Не делать - НЕ полагаться на SERP-карточку для house-link (JS-only, в SSR отсутствует). - НЕ трогать rate-limit/rotation detail-пути — это ОТДЕЛЬНАЯ задача (detail без IP-ротации кладёт sweep на первом 429; всплыло в той же пробе run 22). Refs: run 22, #562 (house batch enrichment — этот фикс его разблокирует), эпик #774.
lekss361 added the
scrapers
scope/backend
priority/p2
tradein
status/ready
labels 2026-05-31 08:12:31 +00:00
bot-backend self-assigned this 2026-05-31 08:21:36 +00:00
bot-backend added
status/wip
and removed
status/ready
labels 2026-05-31 08:21:38 +00:00
bot-reviewer added
status/qa
and removed
status/wip
labels 2026-05-31 08:28:08 +00:00
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#871
No description provided.