fix(tradein/proxy): доводить сигнал бана площадки до пула (#2600 п.1) #2653
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#2653
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/tradein-ban-signal-to-pool"
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?
Summary
#2600 п.1 (предусловие п.2). Корень:
mark_health(ok)выставляется в__exit__контекст-менеджера и даётok=Falseтолько при исключении из блока. Мягкий бан приходит валидным HTTP 200 с заглушкой (Авито «Доступ ограничен: проблема с IP», капча Циана, gate-заглушка Яндекса, QRATOR Домклика) и распознаётся ПОЗЖЕ — при разборе HTML, уже вне блока. Итог: забаненный прокси получалok=True,consecutive_failsобнулялся, узел выдавали снова.Решение:
BrowserFetcher.report_ban()помечает текущий sticky-lease (#2640) →proxy_pool.mark_banned()выключает узел сdisabled_reason='banned:<source>'(колонка из #2610 — ipify-проба такой узел не воскрешает, иначе флаппинг).Тонкость инструментации: Avito размечен на raise-сайтах, а не в
__aexit__—pipeline.pyприсваиваетscraper._browserнапрямую и в контекст-менеджер не заходит, централизованный хук пропустил бы весь этот путь (тот же класс ловушки, что чинили в #2634). Cian/Yandex проверяют свои счётчики в__aexit__до релиза lease. Curl-путь получил маркерProxyBanError(подмешан в Blocked-исключения;AvitoRateLimitedErrorнамеренно НЕ включён — может быть нашим таймаутом).Две защиты, добавленные по deep-review (🟠 HIGH ×2)
Ложный бан. У Яндекса
_http_getглотал исключенияfetch()и отдавалstatus_code=0— этот случай считался в тот же счётчик, что настоящая капча. Живой путьPOST /admin/scrape {"sources":["yandex"]}делает ровно одну попытку → 1/1 = 100% → здоровый узел выключался бы как забаненный. Добавлен признакtransport_error(счётчик трогают только контентные провалы — Циан так делал изначально, зеркалим) + порогattempts >= 3на обоих триггерах. Проверено ревью: боевой sweep делает ≥4 попытки на инстанс (cian: 4 room-бакета × страницы, yandex: room×price-комбо) — детект настоящего бана не страдает, а одиночный админский прогон структурно защищён. Mixed-случай (транспорт → контент) покрыт отдельным флагом и тестом.Выкос пула.
mark_bannedне выключает узел, если он последний достижимый для источника — EXISTS зеркалит логикуacquire()с учётом affinity (domclick-узел не считается запасным для avito/cian/yandex). Плюсpg_advisory_xact_lock: однострочный UPDATE не атомарен поперёк строк, два параллельных бана разных узлов проходили мимо защиты и могли обнулить пул — без самолечения (disabled_reasonзапрещает ipify-воскрешение). Ключ проверен на коллизии со всеми advisory-локами репо; дедлока сFOR UPDATE SKIP LOCKEDвacquire()нет.Три вида отказа остались различимы: бан площадки →
mark_banned; нет прокси →NoProxyAvailableError(#2616); сетевой сбой → обычныйmark_health(ok=False).Test plan
disabled_reason='banned:*', пул не обнуляетсяReview
deep-code-reviewer: 🟠 HIGH ×2 → оба закрыты в этом же PR; повторный code-review дельты: ✅ APPROVE (проверил, что floor не ослепляет детект, коллизии ключей, отсутствие дедлока, red/green дельты).
Не входит (осознанно): п.2 #2600 (per-source
banned_until), wiringproxy_providerв domclick SERP (#2160 P4),IMVAuthErrorв ban-сигнал.Refs #2600