fix(tradein/browser): дожидаться QRATOR PoW-челленджа Авито вместо тихой заглушки #3046
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#3046
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/3045-browser-pow-challenge-wait"
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?
Шаг 2 из #3045. Меняет образ
tradein-browser.Проблема
Авито за QRATOR отдаёт proof-of-work челлендж: JS считает PoW, ставит куку
pow_solved, черезsetTimeout(…, 3000)делаетwindow.location = location.href— self-reload на тот же URL, и только после него приходит контент._fetch_onceждал фиксированныеBROWSER_WAIT_MS(6 с) и забиралpage.content(). На цепочку «PoW → таймер 3 с → перезагрузка → гидратация» этого хватало не всегда.Замер на проде 2026-08-21: шесть разных карточек с органической навигацией (
origin= выдача вторички) — 2 успеха, 4 челлендж-страницы по 7891 байт. Важно: ни одного бана, все отказы это «проверка безопасности». Площадка не блокирует — мы уходим раньше.Хуже того, caller считал челлендж-страницу валидным HTML: парсер либо падал, либо молча ничего не находил. Отсюда и вердикты прогонов «причина НЕ установлена».
Что сделано
_is_pow_challenge/_is_ban_pageпо маркерам, снятым живьём (startPow, «проверка безопасности» / «проблема с IP»). Маркеры вынесены в константы с пометкой, что могут протухнуть._wait_out_pow_challenge— опросpage.content()шагом ~1 с в пределах новойBROWSER_CHALLENGE_WAIT_MS(дефолт 30000), затем догидрация тем жеBROWSER_WAIT_MS.wait_for_urlне годится: челлендж перезагружает страницу на тот же адрес.ChallengeTimeoutError, а не тихая отдача заглушки.BanPageDetectedError, без траты бюджета: ждать на заблокированном адресе нечего. Это принципиально разные ответы, и путать их дорого.Дефект, найденный при ревью ветки (второй коммит)
Первая версия опрашивала
page.content()без защиты. Но челлендж перезагружает страницу сам, и вызовcontent(), попавший в момент перезагрузки, кидает:То есть цикл ронял фетч ровно на успешном исходе — в момент той самой перезагрузки, ради ожидания которой он и написан. На моках это не воспроизводилось: поддельная
pageнавигацию не рвёт, поэтому все 12 исходных тестов были зелёными.Добавлено:
_content_during_navigation()— возвращаетNoneвместо исключения, если текст ошибки указывает на гонку с навигацией. Различаем по тексту, а не по типу: сервис не импортирует playwright,pageприходит готовым объектом. Всё прочее (закрытая страница, упавший браузер) пробрасывается как есть.Noneкак «ещё не устоялось, опроси снова».pageв тестах научена поднимать исключение изcontent().Тесты
tradein-mvp/browser/test_server_pow_challenge.py— 15 тестов: детекторы, ожидание челленджа, исчерпание бюджета, бан без траты бюджета, обычная страница без изменений, плюс три на гонку навигации (прохождение, вечная навигация, посторонняя ошибка).Фальсификация: без правки второго коммита два теста на гонку падают именно с
Execution context was destroyed— тем самым, что прилетело бы на проде.pytestпо всему сервису — 123 passedruff check— чистоПосле деплоя
Образ
tradein-browserпересобирается. Нужен замер доли успешных detail-фетчей Авито с органической навигацией — baseline до правки 2 из 6. Ожидание близко к сотне, поскольку блокировки не было.Флаг
avito_detail_backfill_use_curlне тронут — это шаг 3, отдельным PR после замера.Refs #3045
Найдено при ревью ветки. `_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