feat(tradein/browser): ходить по Домклику как человек — с Referer и с перезагрузкой зависшего рукопожатия #3250

Merged
lekss361 merged 1 commit from feat/browser-referer-on-card-nav into main 2026-08-29 19:59:13 +00:00
Owner

Откуда это

Добор карточек Домклика упирается в отказ на 11-й карточке. Прогон 5298 (после PR #3244): attempted=13, enriched=10, blocked=3. Ручные прогоны в живом браузере — 91 и 40 карточек без единого отказа. Значит дело не в том, что площадка нас не пускает, а в том, чем машинный заход отличается от человеческого. Отличий нашлось два, и оба наши.

1. Referer не отправлялся никогда

page.goto(url) в playwright по умолчанию этот заголовок не шлёт, а параметр referer, который метод принимает, мы не передавали. Ни в browser/server.py, ни в browser_fetcher.py слова Referer не было вовсе — только в комментариях, которые уверяли про «органическую навигацию с реальными cookies/Referer». Комментарии врали: работал один прогрев кук.

С точки зрения площадки браузер грузил выдачу, а потом десять раз подряд возникал прямо на URL карточки — ниоткуда. Человек так не ходит: он приходит из поиска или кликает из выдачи, и в обоих случаях Referer уходит.

Сайдкар теперь принимает referer в теле /fetch и ставит его только на целевую навигацию; на origin и якорную вкладку — не ставит, туда приходят «сами». Добор Домклика передаёт страницу выдачи, чем переход и является по смыслу.

2. Зависшее рукопожатие не перезагружалось

_wait_out_pow_challenge был построен на допущении из докстринга: «страница перезагружает СЕБЯ САМА (window.location = location.href) после решения PoW». Ручная сессия 29.08 через узел 10 (headful-камуфокс, прод-прокси, путь «яндекс-поиск → выдача → карточка») это опровергла:

102.7с  GET  /pokupka/kvartiry/vtorichka  → 401
107.7с  выдана кука qrator_jsr                (PoW пошёл)
115.1с  GET  /pokupka/kvartiry/vtorichka  → 401   ← перезагрузка руками
118.4с  GET  /pokupka/kvartiry/vtorichka  → 200   ← вторая перезагрузка
119.6с  выдана кука qrator_jsid2               (ПРОПУСК)
124.6с  GET  /card/sale__flat__2078134349 → 200   ← карточка за 6 секунд

Сама страница не ожила. Пропуск выдался только после повторных навигаций. Пока сайдкар лишь опрашивал content() на месте, такая страница гарантированно доживала до BROWSER_CHALLENGE_WAIT_MS, а бэкфилл засчитывал это в блоки — и банил узел на 6 часов (#3246).

page.reload() не вызывался в сайдкаре нигде. Теперь после BROWSER_CHALLENGE_RELOAD_AFTER_MS (8 с) безрезультатного опроса сайдкар перезагружает сам, не больше BROWSER_CHALLENGE_MAX_RELOADS (2) раз за фетч — ровно то действие, которым человек и вышел из этого состояния.

Безопасность отката

  • Нет поля referer в теле — поведение прежнее, байт в байт.
  • BROWSER_CHALLENGE_RELOAD_AFTER_MS=0 — прежний опрос без единой навигации.
  • Перезагрузка упала (гонка с собственным reload'ом, таймаут) — пишем warning и продолжаем опрос в том же бюджете, фетч не роняем.
  • Страница, которая перезагружает себя сама, лишней навигации не получает — проверено тестом.

Тесты

191 passed в сайдкаре против 182 до правки: 4 на Referer (доходит до целевого goto, не доходит до origin/якоря, отсутствие поля = прежнее поведение) и 5 на перезагрузку (зависший челлендж оживает после reload; самостоятельно ожившая страница reload не получает; вечно висящий не превышает лимит; нулевой порог отключает; упавший reload не роняет ожидание).

Точечные backend-тесты домклика и scraper_kit — 144 passed. ruff check чист по всем затронутым файлам.

Что дальше

После деплоя прогнать добор тем же батчем и сравнить с enriched=10, blocked=3. Связано: #3244 (якорная вкладка), #3246 (бан узла там, где хватило бы сброса контекста).

## Откуда это Добор карточек Домклика упирается в отказ на 11-й карточке. Прогон 5298 (после PR #3244): `attempted=13, enriched=10, blocked=3`. Ручные прогоны в живом браузере — 91 и 40 карточек **без единого отказа**. Значит дело не в том, что площадка нас не пускает, а в том, чем машинный заход отличается от человеческого. Отличий нашлось два, и оба наши. ## 1. Referer не отправлялся никогда `page.goto(url)` в playwright по умолчанию этот заголовок не шлёт, а параметр `referer`, который метод принимает, мы не передавали. Ни в `browser/server.py`, ни в `browser_fetcher.py` слова `Referer` не было вовсе — только в комментариях, которые уверяли про «органическую навигацию с реальными cookies/Referer». Комментарии врали: работал один прогрев кук. С точки зрения площадки браузер грузил выдачу, а потом десять раз подряд возникал прямо на URL карточки — ниоткуда. Человек так не ходит: он приходит из поиска или кликает из выдачи, и в обоих случаях Referer уходит. Сайдкар теперь принимает `referer` в теле `/fetch` и ставит его **только на целевую навигацию**; на origin и якорную вкладку — не ставит, туда приходят «сами». Добор Домклика передаёт страницу выдачи, чем переход и является по смыслу. ## 2. Зависшее рукопожатие не перезагружалось `_wait_out_pow_challenge` был построен на допущении из докстринга: «страница перезагружает СЕБЯ САМА (`window.location = location.href`) после решения PoW». Ручная сессия 29.08 через узел 10 (headful-камуфокс, прод-прокси, путь «яндекс-поиск → выдача → карточка») это опровергла: ``` 102.7с GET /pokupka/kvartiry/vtorichka → 401 107.7с выдана кука qrator_jsr (PoW пошёл) 115.1с GET /pokupka/kvartiry/vtorichka → 401 ← перезагрузка руками 118.4с GET /pokupka/kvartiry/vtorichka → 200 ← вторая перезагрузка 119.6с выдана кука qrator_jsid2 (ПРОПУСК) 124.6с GET /card/sale__flat__2078134349 → 200 ← карточка за 6 секунд ``` Сама страница не ожила. Пропуск выдался только после повторных навигаций. Пока сайдкар лишь опрашивал `content()` на месте, такая страница гарантированно доживала до `BROWSER_CHALLENGE_WAIT_MS`, а бэкфилл засчитывал это в блоки — и банил узел на 6 часов (#3246). `page.reload()` не вызывался в сайдкаре нигде. Теперь после `BROWSER_CHALLENGE_RELOAD_AFTER_MS` (8 с) безрезультатного опроса сайдкар перезагружает сам, не больше `BROWSER_CHALLENGE_MAX_RELOADS` (2) раз за фетч — ровно то действие, которым человек и вышел из этого состояния. ## Безопасность отката - Нет поля `referer` в теле — поведение прежнее, байт в байт. - `BROWSER_CHALLENGE_RELOAD_AFTER_MS=0` — прежний опрос без единой навигации. - Перезагрузка упала (гонка с собственным reload'ом, таймаут) — пишем warning и продолжаем опрос в том же бюджете, фетч не роняем. - Страница, которая перезагружает себя сама, лишней навигации не получает — проверено тестом. ## Тесты **191 passed** в сайдкаре против 182 до правки: 4 на Referer (доходит до целевого `goto`, не доходит до origin/якоря, отсутствие поля = прежнее поведение) и 5 на перезагрузку (зависший челлендж оживает после reload; самостоятельно ожившая страница reload не получает; вечно висящий не превышает лимит; нулевой порог отключает; упавший reload не роняет ожидание). Точечные backend-тесты домклика и `scraper_kit` — 144 passed. `ruff check` чист по всем затронутым файлам. ## Что дальше После деплоя прогнать добор тем же батчем и сравнить с `enriched=10, blocked=3`. Связано: #3244 (якорная вкладка), #3246 (бан узла там, где хватило бы сброса контекста).
lekss361 added 1 commit 2026-08-29 19:53:50 +00:00
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
ff0a15d443
Добор карточек Домклика упирался в отказ на 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.
lekss361 merged commit 2b62d279fe into main 2026-08-29 19:59:13 +00:00
lekss361 deleted branch feat/browser-referer-on-card-nav 2026-08-29 19:59:13 +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#3250
No description provided.