fix(tradein): avito house-link из detail SSR — SERP-карточка JS-only (#871) (#873)
All checks were successful
Deploy Trade-In / changes (push) Successful in 5s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / test (push) Successful in 30s
Deploy Trade-In / build-backend (push) Successful in 41s
Deploy Trade-In / deploy (push) Successful in 36s
All checks were successful
Deploy Trade-In / changes (push) Successful in 5s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / test (push) Successful in 30s
Deploy Trade-In / build-backend (push) Successful in 41s
Deploy Trade-In / deploy (push) Successful in 36s
Co-authored-by: lekss361 <lekss361@gendsgn.local> Co-committed-by: lekss361 <lekss361@gendsgn.local>
This commit is contained in:
parent
1684c6ed3e
commit
70838c7dfe
3 changed files with 65 additions and 2 deletions
|
|
@ -268,6 +268,7 @@ async def run_avito_pipeline(
|
||||||
)
|
)
|
||||||
|
|
||||||
consecutive_blocks = 0
|
consecutive_blocks = 0
|
||||||
|
detail_house_paths: set[str] = set()
|
||||||
for idx, row in enumerate(priority_rows):
|
for idx, row in enumerate(priority_rows):
|
||||||
source_url: str = row["source_url"]
|
source_url: str = row["source_url"]
|
||||||
counters.detail_attempted += 1
|
counters.detail_attempted += 1
|
||||||
|
|
@ -278,6 +279,12 @@ async def run_avito_pipeline(
|
||||||
enrichment_detail = await fetch_detail(item_url, cffi_session=session)
|
enrichment_detail = await fetch_detail(item_url, cffi_session=session)
|
||||||
if save_detail_enrichment(db, enrichment_detail):
|
if save_detail_enrichment(db, enrichment_detail):
|
||||||
counters.detail_enriched += 1
|
counters.detail_enriched += 1
|
||||||
|
# #871: house-link есть в detail SSR (в SERP-карточке — JS-only).
|
||||||
|
# Копим catalog-пути для house-enrich ниже (Step 5b).
|
||||||
|
if enrichment_detail.house_catalog_url:
|
||||||
|
hp = urlparse(enrichment_detail.house_catalog_url).path
|
||||||
|
if hp:
|
||||||
|
detail_house_paths.add(hp)
|
||||||
consecutive_blocks = 0
|
consecutive_blocks = 0
|
||||||
except (AvitoBlockedError, AvitoRateLimitedError) as e:
|
except (AvitoBlockedError, AvitoRateLimitedError) as e:
|
||||||
consecutive_blocks += 1
|
consecutive_blocks += 1
|
||||||
|
|
@ -310,6 +317,35 @@ async def run_avito_pipeline(
|
||||||
jitter = random.uniform(0.8, 1.2)
|
jitter = random.uniform(0.8, 1.2)
|
||||||
await asyncio.sleep(detail_delay * jitter)
|
await asyncio.sleep(detail_delay * jitter)
|
||||||
|
|
||||||
|
# ── Step 5b: enrich houses, найденные через detail-страницы (#871) ──
|
||||||
|
# SERP-карточки больше не несут house-link (JS-render) → дома берём из
|
||||||
|
# detail SSR. Дедуп против обработанных в Step 4 (unique_house_paths).
|
||||||
|
new_house_paths = detail_house_paths - unique_house_paths
|
||||||
|
if enrich_houses and new_house_paths:
|
||||||
|
counters.unique_houses += len(new_house_paths)
|
||||||
|
nh_list = list(new_house_paths)
|
||||||
|
for h_idx, house_path in enumerate(nh_list):
|
||||||
|
try:
|
||||||
|
enrichment = await fetch_house_catalog(house_path, cffi_session=session)
|
||||||
|
house_counters = save_house_catalog_enrichment(db, enrichment)
|
||||||
|
counters.houses_enriched += 1
|
||||||
|
hid = house_counters.get("house_id")
|
||||||
|
if hid:
|
||||||
|
touched_house_ids.add(int(hid))
|
||||||
|
except (AvitoBlockedError, AvitoRateLimitedError):
|
||||||
|
logger.error(
|
||||||
|
"pipeline:house(detail) BLOCKED at %s — propagating", house_path
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(
|
||||||
|
"pipeline:house(detail) enrich failed for %s: %s", house_path, e
|
||||||
|
)
|
||||||
|
counters.houses_failed += 1
|
||||||
|
counters.errors.append(f"house(detail) {house_path}: {e}")
|
||||||
|
if h_idx < len(nh_list) - 1:
|
||||||
|
await asyncio.sleep(detail_delay)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"pipeline:done anchor=(%.4f,%.4f) lots=%d (ins=%d/upd=%d) "
|
"pipeline:done anchor=(%.4f,%.4f) lots=%d (ins=%d/upd=%d) "
|
||||||
"houses=%d/%d detail=%d/%d touched_house_ids=%d errors=%d",
|
"houses=%d/%d detail=%d/%d touched_house_ids=%d errors=%d",
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import re
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin, urlparse
|
||||||
|
|
||||||
from curl_cffi.requests import AsyncSession
|
from curl_cffi.requests import AsyncSession
|
||||||
from selectolax.parser import HTMLParser, Node
|
from selectolax.parser import HTMLParser, Node
|
||||||
|
|
@ -319,12 +319,19 @@ def parse_detail_html(html: str, source_url: str) -> DetailEnrichment:
|
||||||
metro_stations = _extract_metro(description)
|
metro_stations = _extract_metro(description)
|
||||||
|
|
||||||
# ── House catalog link ───────────────────────────────────────────────────
|
# ── House catalog link ───────────────────────────────────────────────────
|
||||||
|
# Новостройки отдают кнопку ЖК (nd-jk-details-button); вторичка — ссылку
|
||||||
|
# «узнать больше о доме» под другим маркером. Fallback на любой /catalog/houses/
|
||||||
|
# якорь: verified 2026-05-31 (#871) — эта ссылка есть в SSR detail-страницы,
|
||||||
|
# даже когда отсутствует в JS-рендеренной SERP-карточке (откуда мы брали 0).
|
||||||
house_catalog_url: str | None = None
|
house_catalog_url: str | None = None
|
||||||
house_link = tree.css_first("[data-marker='nd-jk-details-button']")
|
house_link = tree.css_first("[data-marker='nd-jk-details-button']")
|
||||||
|
if house_link is None:
|
||||||
|
house_link = tree.css_first("a[href*='/catalog/houses/']")
|
||||||
if house_link is not None:
|
if house_link is not None:
|
||||||
href = house_link.attributes.get("href", "")
|
href = house_link.attributes.get("href", "")
|
||||||
if href:
|
if href:
|
||||||
house_catalog_url = urljoin(AVITO_BASE, href)
|
# отбрасываем ?context-трекинг → чистый catalog-путь
|
||||||
|
house_catalog_url = urljoin(AVITO_BASE, urlparse(href).path)
|
||||||
|
|
||||||
# ── Photo gallery ────────────────────────────────────────────────────────
|
# ── Photo gallery ────────────────────────────────────────────────────────
|
||||||
photo_urls: list[str] = []
|
photo_urls: list[str] = []
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
без сетевых запросов.
|
без сетевых запросов.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# ruff: noqa: E501 # HTML-фикстуры содержат длинные строки разметки
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from app.services.scrapers.avito_detail import DetailEnrichment, parse_detail_html
|
from app.services.scrapers.avito_detail import DetailEnrichment, parse_detail_html
|
||||||
|
|
@ -136,6 +138,24 @@ def test_house_catalog_url_full() -> None:
|
||||||
assert result.house_catalog_url.startswith("https://www.avito.ru")
|
assert result.house_catalog_url.startswith("https://www.avito.ru")
|
||||||
|
|
||||||
|
|
||||||
|
def test_house_catalog_url_secondary_marker_no_nd_jk() -> None:
|
||||||
|
"""#871: вторичка без nd-jk-details-button → fallback на /catalog/houses/ якорь;
|
||||||
|
?context-трекинг отброшен (verified live: house-link есть в detail SSR)."""
|
||||||
|
html = """
|
||||||
|
<html><body>
|
||||||
|
<div data-marker="item-view/item-id">№ 7683141312</div>
|
||||||
|
<span itemprop="price" content="4000000">4 000 000 ₽</span>
|
||||||
|
<a href="/catalog/houses/ekaterinburg/ul_gotvalda_6k1/142792?context=H4sIabc">
|
||||||
|
Узнать больше о доме</a>
|
||||||
|
</body></html>
|
||||||
|
"""
|
||||||
|
result = parse_detail_html(html, "https://www.avito.ru/test_7683141312")
|
||||||
|
assert (
|
||||||
|
result.house_catalog_url
|
||||||
|
== "https://www.avito.ru/catalog/houses/ekaterinburg/ul_gotvalda_6k1/142792"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_bathroom_type_combined() -> None:
|
def test_bathroom_type_combined() -> None:
|
||||||
html = """
|
html = """
|
||||||
<html><body>
|
<html><body>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue