2 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b9025de666 |
fix(tradein/domclick): площадка отдаёт одну карточку на процесс браузера, а мы держали один на весь прогон (#3205)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (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 / browser-tests (pull_request) Successful in 1m5s
Замер на проде 2026-08-29. Три независимых запуска camoufox через прод-прокси, идём по карточкам до первого отказа — каждый раз одно и то же: карточка #1 даёт 200 и ~1 МБ с SSR-стейтом, карточка #2 даёт 401 и страницу отказа на 26 625 байт. A/B на восьми карточках: свой браузер на каждую — 4 из 4 успеха, один браузер на четыре — 1 из 4. Прямой выход с сервера и выход через прокси неразличимы (1 из 8 в обоих условиях), то есть дело не в IP. Свежего КОНТЕКСТА не хватает: в контрольном замере каждая карточка бралась через browser.new_page(), в новом изолированном контексте, — и всё равно отказ со второй. Признак живёт на уровне процесса, camoufox генерирует отпечаток при запуске, а не при создании контекста. Отсюда: reset_context (#3118) эту задачу не решает в принципе. Цена перезапуска — 0.6 с (3.5 с только первый, холодный). - PROVIDERS: добавлен "domclick" (+ host-detect). Раньше он проваливался в generic и делил браузер со счётчиком страниц с прочим трафиком — при пороге перезапуска 1 это было бы неверно. - BROWSER_RECYCLE_PAGES стал поставщик-зависимым (_resolve_recycle_pages + BROWSER_RECYCLE_PAGES_{PROVIDER}), по образцу BROWSER_BLOCK_IMAGES_{PROVIDER} из #3185. Код-дефолт domclick=1, остальным прежние 15 — у Авито и Циана узор другой и своего замера под него нет. Отдельно починены ~24 холостых охранника в тестах. Они делали monkeypatch.setattr(server, "BROWSER_RECYCLE_PAGES", 10_000), чтобы запретить перезапуск браузера; после перехода на словарь этот патч перестал на что-либо влиять, и набор оставался зелёным лишь потому, что ни один тест не делает 15 страниц подряд. Теперь патчится _RECYCLE_PAGES_BY_PROVIDER, а сама глобальная константа убрана, чтобы её не патчили снова. Проверено мутацией: при пороге 1 для всех провайдеров падают ровно три теста, которые этот дефолт и проверяют, остальные 155 удерживаются — значит охранники работают. |
||
|
|
bf3214b9e4 |
fix(tradein/scrapers): диагноз блока брался из текстовых маркеров чужой площадки, а не из HTTP-статуса (#3196)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
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 / browser-tests (pull_request) Successful in 1m7s
CI Trade-In / backend-tests (pull_request) Successful in 4m57s
Сайдкар вообще не читал код ответа page.goto: страница классифицировалась только по маркерам, снятым с Авито. Домклик отдаёт статическую `403 | Домклик` на 26 624 байта, где нет ни одного такого маркера (замер прода 28.08.2026) — она уезжала наверх как валидный HTML, парсер не находил состояние, и прогон получал блок неизвестной природы. За 14 дней все 14 прогонов домклика легли с ban_kind='unknown'; у Яндекса счётчика blocked не было вовсе, поэтому ветка перевода прогона в 'banned' была недостижима по построению — ноль банов. - browser/server.py: статус целевой навигации сохраняется per-provider и доезжает в тело /fetch аддитивным ключом "status" (ключ "html" не тронут); 403/429 с маркерами челленджа больше не ждут PoW — ждать нечего, статическая страница сама себя не перезагрузит. Наверх идёт BanPageDetectedError, а не заглушка: вернув её контентом, воскресили бы #3045. - scraper_kit/browser_fetcher.py: BrowserFetcher.last_response_status + ban_kind_from_status (403/429 → platform, 5xx → infra, прочее → None). Поток управления не менялся: fetch() по-прежнему отдаёт str. - domclick: DomClickBlockedError несёт .status — один тип исключения на маркер-детект и на сбой фетча разводится без размножения типов; прогон передаёт перепись диагнозов в mark_backfill_finished. - yandex: появился счётчик blocked, оживляющий ветку бана. Серии блоков и промахов парсера считаются РАЗДЕЛЬНО: иначе четыре промаха плюс один 403 пятым давали 'banned' с переписью {platform: 1}. - cian: ban_kinds наполняется только диагностируемым статусом. HTTP 200 с пустым разбором — дрейф разметки на нашей стороне, а не отказ площадки; записав его блоком, мы бы штамповали фиктивные баны у здорового источника (13 done против 1 banned за 14 дней). Инвариант: непустой ban_kinds ⟺ виден ответ 403/429/5xx. Значения остаются в пределах CHECK scrape_runs.ban_kind. Известный пробел: шов providers/domclick/detail.py `blocked.status = status` тестами не покрыт — существующие домкликовые тесты подают исключение готовым моком и боевой fetch_detail не исполняют. |