fix(tradein/browser): снятый PoW-челлендж отдавался с протухшим статусом, и карточка выбрасывалась (#3283) #3306

Merged
bot-backend merged 1 commit from fix/3283e-pow-resolved-page-discarded into main 2026-08-31 14:54:00 +00:00
Owner

Проблема

Сайдкар уже пережидает челлендж QRATOR и уже его снимает. Но _last_response_status[provider] пишется один раз, из ответа первого goto (server.py:2103), а _wait_out_pow_challenge его не обновляет. Наружу уходило {"html": <нормальная карточка>, "status": 439} — и клиент avito/detail.py браковал её по одному коду, не заглянув в HTML.

Замер на проде

Прогон 5606, одни и те же объявления:

13:15:23  сайдкар:  PoW-челлендж снят за ~9000мс, догидрация  ...8242031370
13:15:28  скрапер:  BLOCKED #1  HTTP 439                       ...8242031370

13:22:33  сайдкар:  PoW-челлендж снят за ~3000мс, догидрация  ...8272722582
13:22:37  скрапер:  BLOCKED #11 HTTP 439                       ...8272722582

13:25:23  сайдкар:  PoW-челлендж снят за ~1000мс, догидрация  ...8294967399
13:25:27  скрапер:  BLOCKED #20 HTTP 439                       ...8294967399

Минимум 3 блока из 7 в прогоне — страницы, которые сайдкар уже вытащил, а клиент выбросил.

Почему не «поймать свежий статус»

Рассматривался вариант с page.on("response"). Он не работает: PoW-страница чинит себя собственным window.location = location.href, Response на который мы не наблюдаем (опрашиваем content()), а собственный page.reload() включается только после BROWSER_CHALLENGE_RELOAD_AFTER_MS=8000. На проде челлендж снимался за 1-9 с — то есть в типичном случае наш reload вообще не срабатывает и достоверного ответа на устоявшийся контент нет.

Поэтому на успешном выходе статус сбрасывается в None («неизвестен»), а не тащится протухшим.

Почему клиента править не нужно

providers/avito/detail.py:606if status is not None:; при None работа идёт по html-эвристикам _is_firewall_page / _is_detail_soft_block, которые на настоящем контенте не срабатывают, а на настоящем отказе — срабатывают. Прямой 403/439 от goto (без прохода через _wait_out_pow_challenge) по-прежнему ловится как блок. SERP last_response_status вообще не читает.

backend/tests/test_3044_avito_439_classified_as_block.pyзелёный без единой правки (4 passed).

Побочные эффекты

  • Правка в общей функции, поэтому чинит и ветку DomClick: там статус в классификации блока не участвует (решает тело), только диагностика.
  • Провал ожидания (ChallengeTimeoutError) остаётся отказом — сброс стоит после всех raise, единственный успешный выход.
  • Побочный эффект отражён в докстринге _wait_out_pow_challenge.

Проверка

219 passed — набор сайдкара, запущенный так же, как его гоняет CI (отдельный env pytest + aiohttp, а не backend-венв).

## Проблема Сайдкар **уже** пережидает челлендж QRATOR и **уже** его снимает. Но `_last_response_status[provider]` пишется один раз, из ответа первого `goto` (`server.py:2103`), а `_wait_out_pow_challenge` его не обновляет. Наружу уходило `{"html": <нормальная карточка>, "status": 439}` — и клиент `avito/detail.py` браковал её по одному коду, **не заглянув в HTML**. ## Замер на проде Прогон 5606, одни и те же объявления: ``` 13:15:23 сайдкар: PoW-челлендж снят за ~9000мс, догидрация ...8242031370 13:15:28 скрапер: BLOCKED #1 HTTP 439 ...8242031370 13:22:33 сайдкар: PoW-челлендж снят за ~3000мс, догидрация ...8272722582 13:22:37 скрапер: BLOCKED #11 HTTP 439 ...8272722582 13:25:23 сайдкар: PoW-челлендж снят за ~1000мс, догидрация ...8294967399 13:25:27 скрапер: BLOCKED #20 HTTP 439 ...8294967399 ``` Минимум **3 блока из 7** в прогоне — страницы, которые сайдкар уже вытащил, а клиент выбросил. ## Почему не «поймать свежий статус» Рассматривался вариант с `page.on("response")`. Он не работает: PoW-страница чинит себя собственным `window.location = location.href`, Response на который мы не наблюдаем (опрашиваем `content()`), а собственный `page.reload()` включается только после `BROWSER_CHALLENGE_RELOAD_AFTER_MS=8000`. На проде челлендж снимался за 1-9 с — то есть в типичном случае наш reload вообще не срабатывает и достоверного ответа на устоявшийся контент нет. Поэтому на успешном выходе статус сбрасывается в `None` («неизвестен»), а не тащится протухшим. ## Почему клиента править не нужно `providers/avito/detail.py:606` — `if status is not None:`; при `None` работа идёт по html-эвристикам `_is_firewall_page` / `_is_detail_soft_block`, которые на настоящем контенте не срабатывают, а на настоящем отказе — срабатывают. Прямой `403`/`439` от `goto` (без прохода через `_wait_out_pow_challenge`) по-прежнему ловится как блок. SERP `last_response_status` вообще не читает. `backend/tests/test_3044_avito_439_classified_as_block.py` — **зелёный без единой правки** (4 passed). ## Побочные эффекты - Правка в общей функции, поэтому чинит и ветку **DomClick**: там статус в классификации блока не участвует (решает тело), только диагностика. - Провал ожидания (`ChallengeTimeoutError`) остаётся отказом — сброс стоит после всех `raise`, единственный успешный выход. - Побочный эффект отражён в докстринге `_wait_out_pow_challenge`. ## Проверка `219 passed` — набор сайдкара, запущенный так же, как его гоняет CI (отдельный env `pytest + aiohttp`, а не backend-венв).
lekss361 added 1 commit 2026-08-31 14:47:16 +00:00
fix(tradein/browser): снятый PoW-челлендж отдавался с протухшим статусом, и карточка выбрасывалась (#3283)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 11s
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 1m27s
67a136c95c
Сайдкар уже пережидает челлендж QRATOR и уже его снимает, но
_last_response_status[provider] остаётся статусом challenge-страницы: он
пишется один раз, из ответа первого goto, а _wait_out_pow_challenge его не
обновляет. Наружу уходило {"html": <нормальная карточка>, "status": 439}, и
клиент avito/detail.py браковал её по одному коду, не заглянув в HTML.

Прод, прогон 5606 — одни и те же объявления:
  13:15:23 сайдкар: PoW снят за ~9000мс, догидрация  ...8242031370
  13:15:28 скрапер: BLOCKED #1  HTTP 439              ...8242031370
  13:22:33 сайдкар: PoW снят за ~3000мс, догидрация  ...8272722582
  13:22:37 скрапер: BLOCKED #11 HTTP 439              ...8272722582
Минимум 3 блока из 7 в прогоне — выброшенные страницы, уже вытащенные.

Ловить «свежий» статус нечем: PoW-страница чинит себя своим
window.location-редиректом, Response на который мы не наблюдаем, а
собственный page.reload() включается только после 8с — при снятии за 1-9с он
обычно не срабатывает вовсе. Поэтому на успешном выходе статус сбрасывается в
None («неизвестен»), а не тащится протухшим. Клиент None блоком не считает
(detail.py: `if status is not None`) и падает на html-эвристики, которые на
настоящем контенте не срабатывают.

Правка в общей функции, поэтому чинит и ветку DomClick. Провал ожидания
(ChallengeTimeoutError) остаётся отказом: сброс стоит после всех raise.

Клиента править не потребовалось — test_3044_avito_439_classified_as_block
зелёный без изменений.

Тесты сайдкара: 219 passed.
bot-backend merged commit 9cbdc5dfbe into main 2026-08-31 14:54:00 +00:00
bot-backend deleted branch fix/3283e-pow-resolved-page-discarded 2026-08-31 14:54:01 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#3306
No description provided.