4 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ff0a15d443 |
feat(tradein/browser): ходить по Домклику как человек — с Referer и с перезагрузкой зависшего рукопожатия
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 12s
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 1m28s
CI Trade-In / backend-tests (pull_request) Successful in 5m1s
Добор карточек Домклика упирался в отказ на 11-й карточке: прогон 5298 дал attempted=13, enriched=10, blocked=3. Ручные прогоны в живом браузере брали 91 и 40 карточек без единого отказа. Разбор нашёл два отличия, и оба оказались нашими, а не площадки. 1. Referer не отправлялся НИКОГДА. playwright'овский goto() по умолчанию этот заголовок не шлёт, а параметр `referer`, который он принимает, мы не передавали. Площадка видела десяток появлений подряд прямо на URL карточки, без источника перехода — так не ходит ни один человек. Комментарии в коде при этом уверяли про «органическую навигацию с реальным Referer»; они врали, теперь исправлены. Сайдкар принимает `referer` в теле /fetch и ставит его ТОЛЬКО на целевую навигацию; на origin и якорную вкладку не ставит — туда приходят «сами». Добор Домклика передаёт страницу выдачи, чем переход и является по смыслу. 2. Зависшее рукопожатие не перезагружалось. _wait_out_pow_challenge построен на допущении «страница перезагрузит себя сама после решения PoW»; ручная сессия 29.08 через узел 10 это опровергла — выдача осталась на 401, и пропуск qrator_jsid2 выдался только после ДВУХ перезагрузок, сделанных руками: 102.7с GET → 401, 115.1с GET → 401, 118.4с GET → 200, следом кука-пропуск, и карточка за 6 секунд. Пока мы только опрашивали content(), такая страница жила до таймаута, а бэкфилл засчитывал это в блоки. Теперь после BROWSER_CHALLENGE_RELOAD_AFTER_MS (8с) сайдкар перезагружает сам, не больше BROWSER_CHALLENGE_MAX_RELOADS (2) раз за фетч. Оба пути безопасны на откат: без поля `referer` в теле поведение прежнее, BROWSER_CHALLENGE_RELOAD_AFTER_MS=0 возвращает прежний опрос без навигаций, упавшая перезагрузка не роняет фетч — опрос продолжается в том же бюджете. Тесты: 191 passed в сайдкаре (было 182) — 4 на Referer, 5 на перезагрузку, в том числе «страница ожила сама → лишней навигации нет» и «висит вечно → не больше лимита». Точечные backend-тесты домклика и scraper_kit — 144 passed. |
||
|
|
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 удерживаются — значит охранники работают. |
||
| 5c0fd78c1f |
fix(tradein/browser): цикл ожидания челленджа падал ровно на успешном исходе
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
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 53s
Найдено при ревью ветки. `_wait_out_pow_challenge` опрашивал `page.content()`
без защиты, а челлендж перезагружает страницу САМ (`window.location =
location.href`). Вызов content(), попавший в момент этой перезагрузки, кидает
«Execution context was destroyed, most likely because of a navigation» — то
есть цикл ронял фетч ровно тогда, когда проверка успешно пройдена и мы
дождались того, ради чего ждали.
На моках дефект не воспроизводился: поддельная page навигацию не рвёт.
Добавлено:
- `_content_during_navigation()` — content(), возвращающий None вместо
исключения, если текст ошибки указывает на гонку с навигацией. Различаем
по тексту, а не по типу: сервис не импортирует playwright, page приходит
готовым. Всё прочее (закрытая страница, упавший браузер) пробрасывается.
- Цикл трактует None как «ещё не устоялось, опроси снова».
- Финальная догидрация тоже защищена: один короткий добор, затем внятная
ошибка вместо падения на гонке.
- Поддельная page в тестах умеет поднимать исключение из content();
три теста на гонку — прохождение, вечная навигация, посторонняя ошибка.
Фальсификация: без правки два новых теста падают именно с
`Execution context was destroyed`. Полный сьют сервиса — 123 passed.
Refs #3045
|
|||
|
|
0f6523f852 |
fix(tradein/browser): дожидаться QRATOR PoW-челленджа Авито вместо тихой заглушки
Живой замер 2026-08-21 (#3045): фиксированной паузы BROWSER_WAIT_MS (6с) не хватает на цепочку «PoW-расчёт в JS → таймер 3с → self-reload → гидрация» — 4 из 6 карточек с органической навигацией отдавали 7891-байтную challenge- страницу вместо контента (не бан, "проверка безопасности"). caller считал её валидным HTML — парсер либо падал, либо молча ничего не находил. _fetch_once теперь опрашивает page.content() (шаг ~1с, бюджет BROWSER_CHALLENGE_WAIT_MS=30000) пока маркеры челленджа (startPow / "проверка безопасности") не исчезнут, затем догидрируется тем же BROWSER_WAIT_MS. По истечении бюджета — ChallengeTimeoutError вместо заглушки. wait_for_url не годится: страница перезагружает саму себя, URL не меняется. Бан-страница ("проблема с IP") распознаётся отдельно и падает сразу (BanPageDetectedError), без траты бюджета ожидания — это не то же самое, что челлендж, и ждать там нечего. Провайдер-агностично по форме: включается только по факту маркеров в HTML, cian/yandex/generic их никогда не отдают. |