diff --git a/tradein-mvp/backend/tests/scrapers/test_avito_clean_address_rating_tail.py b/tradein-mvp/backend/tests/scrapers/test_avito_clean_address_rating_tail.py new file mode 100644 index 00000000..2e9da4bf --- /dev/null +++ b/tradein-mvp/backend/tests/scrapers/test_avito_clean_address_rating_tail.py @@ -0,0 +1,59 @@ +"""Адрес Авито не должен утаскивать за собой рейтинг дома и число отзывов. + +Прод 2026-08-10: 1 123 активных объявления Авито с адресом вида +«ул. Ткачей,17·5,0 · 4 отзыва», и у 1 123 из 1 123 нет координат — доля 100%. +У объявлений с чистым адресом координаты есть у 4 766. Хвост появился 27.07.2026, +когда Авито начал рендерить рейтинг внутри того же
в item-location. +""" + +from __future__ import annotations + +import pytest +from scraper_kit.providers.avito.serp import _clean_address + + +@pytest.mark.parametrize( + ("raw", "expected"), + [ + # Живые строки с прода — рейтинг + счётчик отзывов. + ("Авиационная ул.,10·4,9 · 11 отзывов", "Авиационная ул.,10"), + ("ул. 8 Марта,204Г/2·4,3 · 3 отзыва", "ул. 8 Марта,204Г/2"), + ("ул. Академика Шварца,18к2·1 отзыв", "ул. Академика Шварца,18к2"), + # Только счётчик, без рейтинга. + ("ул. Фурманова,59·2 отзыва", "ул. Фурманова,59"), + # Пробелы вокруг разделителя. + ("ул. Ткачей, 17 · 5,0 · 4 отзыва", "ул. Ткачей, 17"), + ], +) +def test_rating_tail_stripped(raw: str, expected: str) -> None: + assert _clean_address(raw) == expected + + +def test_district_marker_after_dot_survives() -> None: + """Режем «·» только перед цифрой — район за точкой остаётся на месте. + + Иначе фикс рейтинга съел бы разделитель района, который _deglue_house_marker + намеренно восстанавливает (#1773). + """ + assert ( + _clean_address("улица Вильгельма де Геннина, 59 · р-н Академический") + == "улица Вильгельма де Геннина, 59 · р-н Академический" + ) + + +def test_clean_address_unchanged() -> None: + assert _clean_address("с. Новоалексеевское, ул. 8 Марта,35") == ( + "с. Новоалексеевское, ул. 8 Марта,35" + ) + + +def test_existing_noise_still_stripped() -> None: + """Регрессия: старые хвосты (CSS/метро/«от N мин.») режутся как раньше.""" + assert _clean_address("ул. Токарей, 56к1Площадь 1905 года.css-39hgr0{fill:red}") == ( + "ул. Токарей, 56к1" + ) + assert _clean_address("ул. Малышева, 1.css-xxx{color:blue}") == "ул. Малышева, 1" + # #1773: слипшийся маркер района по-прежнему расклеивается. + assert _clean_address("ул. Евгения Савкова, 29р-н Академический") == ( + "ул. Евгения Савкова, 29, р-н Академический" + ) diff --git a/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/serp.py b/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/serp.py index 484403c3..3c3d717a 100644 --- a/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/serp.py +++ b/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/serp.py @@ -2097,8 +2097,16 @@ def _avito_room_label(name: str) -> str: _CSS_NOISE_RE = re.compile(r"\.?css-[a-z0-9_-]+\s*\{[^}]*\}", flags=re.I) +# Хвост «·<цифра>» — рейтинг дома и число отзывов, которые Авито с 27.07.2026 рендерит +# ВНУТРИ того же
в data-marker="item-location": «ул. Ткачей,17·5,0 · 4 отзыва». +# Прод 2026-08-10: 1 123 активных объявления с таким адресом, и у 1 123 из 1 123 нет +# координат (доля 100%) — ни один геокодер такую строку не берёт. Для сравнения, у +# объявлений с чистым адресом координаты есть у 4 766. +# Режем по «·», ЗА КОТОРОЙ ИДЁТ ЦИФРА (рейтинг «·4,9» либо счётчик «·2 отзыва»), а не +# по любой «·»: разделитель района у площадок пишется как «, 59 · р-н Академический» — +# за точкой буква, и такой хвост _deglue_house_marker намеренно сохраняет. _NOT_ADDRESS_TAIL_RE = re.compile( - r"\s*(Площадь \d|от \d+\s?мин\.|css-[a-z0-9_-]+)", + r"\s*(Площадь \d|от \d+\s?мин\.|css-[a-z0-9_-]+|·\s*\d)", flags=re.I, )