fix(avito): classify novostroyki by item-development-name marker; map переуступка sale_type
All checks were successful
CI / changes (pull_request) Successful in 6s
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped

This commit is contained in:
bot-backend 2026-06-17 22:24:13 +03:00
parent 57800b24e9
commit 22a7a3f086
4 changed files with 61 additions and 15 deletions

View file

@ -74,15 +74,7 @@ _UNIT_DAYS: dict[str, int] = {
# поэтому отдельная ветка с неявным n=1. Юниты в винительном падеже («минуту»,
# «неделю») плюс именительный («час», «день»).
_REL_DATE_SINGULAR_RE = re.compile(
r"(?P<unit>"
r"секунду|"
r"минуту|"
r"час|"
r"день|"
r"неделю|"
r"месяц|"
r"год"
r")\s+назад",
r"(?P<unit>" r"секунду|" r"минуту|" r"час|" r"день|" r"неделю|" r"месяц|" r"год" r")\s+назад",
flags=re.I,
)
@ -751,12 +743,12 @@ class AvitoScraper(BaseScraper):
house_ext_id = parts[-1]
house_url = urljoin("https://www.avito.ru", h_href)
# listing_segment по item URL pattern
listing_segment: str | None = None
if "/kvartiry/" in href:
listing_segment = "vtorichka"
elif "/novostroyki/" in href:
listing_segment = "novostroyki"
# listing_segment по DOM-маркеру карточки: новостройки несут
# data-marker="item-development-name" (название ЖК/застройщика),
# вторичка — нет. URL-паттерн ненадёжен: каждый avito-URL квартиры
# содержит /kvartiry/ (вкл. новостройки) → ветка всегда vtorichka.
dev_name = card.css_first('[data-marker="item-development-name"]')
listing_segment = "novostroyki" if dev_name is not None else "vtorichka"
return ScrapedLot(
source="avito",

View file

@ -152,6 +152,7 @@ _SALE_TYPE_MAP: dict[str, str] = {
"свободная": "free",
"альтернатива": "alternative",
"аукцион": "auction",
"переуступка": "assignment",
}
_HOUSE_TYPE_MAP: dict[str, str] = {

View file

@ -78,6 +78,25 @@ def test_minimal_parse() -> None:
assert result.views_today == 12
def test_sale_type_assignment_pereustupka() -> None:
"""«Способ продажи: переуступка» → sale_type='assignment' (ДДУ переуступка,
сигнал новостройки). До фикса map не содержал ключ None."""
html = """
<html><body>
<div data-marker="item-view/item-id"> 8043003287</div>
<span itemprop="price" content="9500000">9 500 000 </span>
<div data-marker="item-view/item-params">
<ul>
<li>Количество комнат: 2</li>
<li>Способ продажи: переуступка</li>
</ul>
</div>
</body></html>
"""
result = parse_detail_html(html, "https://www.avito.ru/test_8043003287")
assert result.sale_type == "assignment"
def test_metro_extraction() -> None:
result = parse_detail_html(MINIMAL_HTML, SOURCE_URL)
# "Чкаловская 11-15 мин пешком" → metro_stations

View file

@ -60,3 +60,37 @@ def test_lazy_card_date_comes_from_json_not_dom() -> None:
lots = AvitoScraper()._parse_html(html, "https://www.avito.ru/x")
lazy = next(lot for lot in lots if "8043936560" in (lot.source_url or ""))
assert lazy.listing_date == _expected(_TS_LAZY)
# ── listing_segment по DOM-маркеру item-development-name ───────────────────────
# Каждый avito-URL квартиры содержит /kvartiry/ (вкл. новостройки), поэтому
# сегмент определяется не по URL, а по наличию data-marker="item-development-name"
# (название ЖК/застройщика рендерится только у карточек новостроек).
def _serp_card(item_id: str, *, with_dev_name: bool) -> str:
dev = '<div data-marker="item-development-name">ЖК «Федерация»</div>' if with_dev_name else ""
return f"""
<div data-marker="item" data-item-id="{item_id}">
<a data-marker="item-title" href="/ekaterinburg/kvartiry/2-k_kvartira_{item_id}">
2-к. квартира, 50 м², 5/20 эт.</a>
<meta itemprop="price" content="9500000"/>
{dev}
</div>
"""
def test_segment_novostroyki_when_development_name_present() -> None:
"""Карточка с data-marker='item-development-name' → listing_segment='novostroyki'."""
html = f"<html><body>{_serp_card('8043003287', with_dev_name=True)}</body></html>"
lots = AvitoScraper()._parse_html(html, "https://www.avito.ru/ekaterinburg/kvartiry")
assert len(lots) == 1
assert lots[0].listing_segment == "novostroyki"
def test_segment_vtorichka_when_no_development_name() -> None:
"""Карточка без маркера ЖК → listing_segment='vtorichka' (даже с /kvartiry/ в URL)."""
html = f"<html><body>{_serp_card('8043936560', with_dev_name=False)}</body></html>"
lots = AvitoScraper()._parse_html(html, "https://www.avito.ru/ekaterinburg/kvartiry")
assert len(lots) == 1
assert lots[0].listing_segment == "vtorichka"