fix(tradein/avito): browser-путь добора не смотрел на HTTP-статус — отказ площадки уходил в ошибку парсинга (#3297) #3300
No reviewers
Labels
No labels
Fable 5 ревью
GG-форсайт
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
feedback/max
generative
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
ИРД
вторичка
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#3300
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/3297-avito-browser-status-check"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Закрывает #3297.
Что происходило
Замер прода 31.08 (24 попытки через 3 узла) поймал три отказа, доезжавших до
parse_detail_htmlи падавшихValueError("Cannot extract item_id"): страница 8172 байта, статус 439, title «Доска объявлений от частных лиц и компаний на Авито». Отказ площадки записывался generic-ошибкой разбора — узел не ротировался, не банился, брейкер по доле его не видел.Две независимые дыры, обе в browser-ветке
fetch_detail:last_response_statusне читался вовсе. Curl-ветка того же файла статус проверяет (if sc in (403, 439) or is_firewall), browser-ветка смотрела только на HTML. Прод ходит именно browser-путём.Правка
_AVITO_DETAIL_BROWSER_BLOCK_STATUSES = {403, 439}и_AVITO_DETAIL_BROWSER_RATELIMIT_STATUS = 429, источник каждого статуса назван комментарием._is_detail_not_found(404 остаётсяAvitoListingGoneError) и доparse_detail_html.None-статус отказом не считается — поведение прежнее, фолбэк на html-эвристики.Почему 429 отдельно от 403/439
Разница не косметическая. На
AvitoBlockedErrorоркестратор один раз за прогон зовётrequest_context_reset()(#3251) и выбрасывает пройденный QRATOR-PoW. При rate-limit контекст цел, площадка лишь просит сбавить темп — сбросив контекст, мы заставим проходить проверку заново с того же IP. Curl-ветка разводит эти случаи так же: 429 → короткий retry, при исчерпанииAvitoRateLimitedError.Замечание пришло от ревьюера; первая версия поднимала
AvitoBlockedErrorна все три статуса.Тесты — 9
Каждый статус по отдельности;
Noneне ломает разбор и не подавляет html-эвристики; 404 побеждает блокирующий статус (доказывает порядок проверок); оба title-маркера опознаются; 429 не являетсяAvitoBlockedError.Фальсификация: без правки
detail.py5 из 9 новых тестов падают — тесты проверяют новое поведение, а не переписаны под реализацию.Прогон:
397 passed, 1 skipped(-k "avito or cadence or scheduler"), ruff чист.Оговорка
Замер намеренно жёстче прода — без прогрева сессии и без органического перехода из выдачи. Поэтому доля таких отказов в проде из него не следует; её покажет счётчик
blockedпосле выката.Что этот PR НЕ чинит
Бан-страницы с маркерами (8×403, 2×429 в том же замере) сюда не доходят — сайдкар поднимает
SidecarBanPageErrorещё вfetch, и она уезжает вinfraчерез общийexcept. Это #3288, отдельный заход.browser/server.py(_REFUSAL_STATUSES, где нет 439) намеренно не тронут.Замер прода 31.08.2026 (24 попытки через 3 узла) поймал три отказа, которые доезжали до parse_detail_html и падали ValueError("Cannot extract item_id"): страница 8172 байта, статус 439, title «Доска объявлений от частных лиц и компаний на Авито». Отказ ПЛОЩАДКИ записывался generic-ошибкой разбора, узел не ротировался и не банился, брейкер по доле его не видел. Две независимые дыры, обе в browser-ветке fetch_detail: 1. last_response_status не читался вовсе. Curl-ветка того же файла статус проверяет (`if sc in (403, 439) or is_firewall`), browser-ветка смотрела только на HTML. Прод ходит именно browser-путём. 2. Маркер витрины-заглушки протух: искали «объявления на сайте авито», а фактический title — «доска объявлений от частных лиц и компаний на авито», подстрока в нём не встречается. Правка: - новые константы _AVITO_DETAIL_BROWSER_BLOCK_STATUSES = {403, 439} и _AVITO_DETAIL_BROWSER_RATELIMIT_STATUS = 429, источник каждого статуса назван комментарием; - проверка стоит ПОСЛЕ _is_detail_not_found (404 остаётся AvitoListingGoneError) и ДО parse_detail_html; - статус None (сайдкар старой версии, goto без статуса) отказом НЕ считается — поведение прежнее, фолбэк на html-эвристики; - 429 разведён с блокирующими статусами и поднимает AvitoRateLimitedError. Разница не косметическая: на AvitoBlockedError оркестратор один раз за прогон зовёт request_context_reset (#3251) и выбрасывает пройденный QRATOR-PoW. При rate-limit контекст цел, сбрасывать его — значит проходить проверку заново с того же IP. Зеркалит curl-ветку, где 429 тоже не блок; - старый title-маркер не удалён, а дополнен снятым вживую: площадка может отдавать обе формы. Тесты (9): каждый статус по отдельности, None-статус не ломает разбор и не подавляет html-эвристики, 404 побеждает блокирующий статус (порядок проверок), оба title-маркера опознаются, 429 не является AvitoBlockedError. Фальсификация: без правки detail.py 5 из 9 новых тестов падают. Прогон: 397 passed, 1 skipped (-k "avito or cadence or scheduler"), ruff чист. Замер намеренно жёстче прода (без прогрева сессии и органического перехода из выдачи), поэтому доля таких отказов в проде из него НЕ следует — её покажет счётчик после правки.