fix(tradein/avito): рейтинг дома перестаёт уезжать в адрес объявления (#2814)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 3m50s
CI Trade-In / frontend-checks (pull_request) Has been skipped

Авито с 27.07 рендерит рейтинг и число отзывов внутри того же <p> в
data-marker="item-location", откуда serp.py берёт адрес: «ул. Ткачей,17·5,0 · 4
отзыва». Прод 2026-08-10: 1 123 активных объявления с таким адресом, и у 1 123 из
1 123 нет координат — доля 100%, 711 из них геокодер уже пробовал. Контроль в тех
же данных: у чистых адресов координаты есть у 4 766 из 5 715 (83%).

Строка без geom молча выпадает из comp-пула: Tier W отбирает через ST_DWithin, а
NULL не проходит предикат и нигде не считается. 2 446 из 3 270 безкоординатных
попадают в свежий пул аналогов — 12.7% аналогов невидимы радиусному поиску.

Режем по «·», за которой идёт ЦИФРА (рейтинг «·4,9», счётчик «·2 отзыва»). По
любой «·» нельзя: разделитель района пишется «, 59 · р-н Академический» — за
точкой буква, и этот хвост _deglue_house_marker намеренно сохраняет (#1773).
Тест проверяет обе стороны плюс три прежних хвоста (CSS, метро, «от N мин.»).

Чинит только новые вставки: base.py пишет address = COALESCE(listings.address,
EXCLUDED.address), адрес при конфликте не перезаписывается осознанно (#2777).
Бэкфилл 1 123 существующих строк вынесен в #2814 для database-expert.
This commit is contained in:
bot-backend 2026-08-10 14:32:25 +05:00
parent 272abac4d2
commit ab5c4b86cd
2 changed files with 68 additions and 1 deletions

View file

@ -0,0 +1,59 @@
"""Адрес Авито не должен утаскивать за собой рейтинг дома и число отзывов.
Прод 2026-08-10: 1 123 активных объявления Авито с адресом вида
«ул. Ткачей,17·5,0 · 4 отзыва», и у 1 123 из 1 123 нет координат доля 100%.
У объявлений с чистым адресом координаты есть у 4 766. Хвост появился 27.07.2026,
когда Авито начал рендерить рейтинг внутри того же <p> в 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, р-н Академический"
)

View file

@ -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 рендерит
# ВНУТРИ того же <p> в 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,
)