fix(tradein/domclick): свип брал BFF навигацией браузера — теперь подзапросом (#3264) #3266

Merged
lekss361 merged 1 commit from fix/3264-sweep-subresource-fetch into main 2026-08-30 08:32:52 +00:00
Owner

Закрывает #3264 — на этот раз с подтверждённой причиной. Первая попытка (PR #3265, куки в свип) была основана на неверной посылке и свип не починила; куки при этом остались, они нужны.

Причина

BFF-ручка Домклика — не страница, а JSON-эндпоинт SPA. Сайдкар умел ровно одно: page.goto(url). То есть навигацией браузера на API-хост мы делали то, чего настоящий клиент не делает никогда.

Перехват сети на живой выдаче 30.08 это показал прямо: офферы приезжают в SSR-документе, а к BFF ходят XHR'ы за гео, районами, метро и избранным. Через мобильные узлы пула наша навигация упиралась в ChallengeTimeout — прогоны 5330 и 5351: 9 из 9 и 6 из 6 запросов зависли, 0 лотов. Карточки через те же узлы в те же минуты шли.

Замер

Три режима, один узел, один и тот же ресурс:

режим BFF карта офферов
navigate (как было) ChallengeTimeout, 55 с BanPageDetected
subresource HTTP 200, 52 Б, 6 с 279 Б (загрузчик QRATOR)
page_fetch ошибка ошибка

Подзапрос проверен вширь:

узел 1, все корзины:  st 644 · 1к 1632 · 2к 2202 · 3к 1564 · 4к 286 · 5+ 31
                      = 6359  (с резидентного IP напрямую было 6367)
список офферов:       узлы 1, 9, 10, 11 — HTTP 200, по 20 офферов, 120 824 Б

Расхождение 6359 против 6367 — дрейф фонда за пару часов между замерами, не потери.

Что сделано

Сайдкар получил fetch_mode: navigate (дефолт, поведение байт-в-байт прежнее), subresource (context.request.get из прогретого контекста) и page_fetch (fetch изнутри страницы).

page_fetch оставлен намеренно, хотя в замере отказал: теоретически он ближе всего к настоящему XHR (несёт Origin/Sec-Fetch-*), и выбор между ним и subresource сделан измерением, а не рассуждением — стоит сохранить возможность перемерить на другой площадке.

Прогрев origin обязателен — рукопожатие QRATOR попадает в куки контекста именно при заходе на страницу. Свип теперь передаёт origin и referer, которых раньше не передавал вовсе.

_decode_body распаковывает gzip по магическим байтам: карта офферов приезжает Content-Type: application/gzip (это gzip-файл, а не Content-Encoding), и без этого вызывающий получил бы бинарь в поле html.

fetch_mode кладётся в payload только когда не дефолтный — сайдкар прежней версии не должен получать незнакомый ключ (тот же приём, что с referer в #3247).

Про карту сайта

sitemap-offers-1.xml.gz подзапросом всё равно не берётся (279 байт — загрузчик). Не страшно: BFF полнее. В карте 3 999 квартир на продажу против 6 367 по BFF — 62% фонда; как единственный источник она и не годилась.

Тесты

  • сайдкар: 212 passed, новый test_server_fetch_mode.py (6 проверок: дефолт остаётся навигацией, subresource не навигирует на целевой url, распаковка gzip, неизвестный режим — внятная ошибка);
  • backend: 316 passed, новый test_3264_sweep_subresource_mode.py (5: оба вызова свипа идут с subresource И непустым origin; fetch_mode попадает в payload только когда осмыслен);
  • три тестовых дублёра _do_fetch/_post_fetch знали старую сигнатуру — обновлены.

Что НЕ проверено

Полный прогон свипа на этой правке. Замер подтверждает обе ручки BFF (count и offers) на всех узлах, но сквозной свип с сохранением лотов в БД запускается после деплоя.

Закрывает #3264 — на этот раз с подтверждённой причиной. Первая попытка (PR #3265, куки в свип) была основана на неверной посылке и свип не починила; куки при этом остались, они нужны. ## Причина BFF-ручка Домклика — не страница, а JSON-эндпоинт SPA. Сайдкар умел ровно одно: `page.goto(url)`. То есть навигацией браузера на API-хост мы делали то, чего настоящий клиент не делает никогда. Перехват сети на живой выдаче 30.08 это показал прямо: **офферы приезжают в SSR-документе**, а к BFF ходят XHR'ы за гео, районами, метро и избранным. Через мобильные узлы пула наша навигация упиралась в `ChallengeTimeout` — прогоны 5330 и 5351: 9 из 9 и 6 из 6 запросов зависли, 0 лотов. Карточки через те же узлы в те же минуты шли. ## Замер Три режима, один узел, один и тот же ресурс: | режим | BFF | карта офферов | |---|---|---| | `navigate` (как было) | `ChallengeTimeout`, 55 с | `BanPageDetected` | | **`subresource`** | **HTTP 200, 52 Б, 6 с** | 279 Б (загрузчик QRATOR) | | `page_fetch` | ошибка | ошибка | Подзапрос проверен вширь: ``` узел 1, все корзины: st 644 · 1к 1632 · 2к 2202 · 3к 1564 · 4к 286 · 5+ 31 = 6359 (с резидентного IP напрямую было 6367) список офферов: узлы 1, 9, 10, 11 — HTTP 200, по 20 офферов, 120 824 Б ``` Расхождение 6359 против 6367 — дрейф фонда за пару часов между замерами, не потери. ## Что сделано **Сайдкар** получил `fetch_mode`: `navigate` (дефолт, поведение байт-в-байт прежнее), `subresource` (`context.request.get` из прогретого контекста) и `page_fetch` (`fetch` изнутри страницы). `page_fetch` оставлен намеренно, хотя в замере отказал: теоретически он ближе всего к настоящему XHR (несёт `Origin`/`Sec-Fetch-*`), и выбор между ним и `subresource` сделан измерением, а не рассуждением — стоит сохранить возможность перемерить на другой площадке. **Прогрев origin обязателен** — рукопожатие QRATOR попадает в куки контекста именно при заходе на страницу. Свип теперь передаёт `origin` и `referer`, которых раньше не передавал вовсе. `_decode_body` распаковывает gzip по магическим байтам: карта офферов приезжает `Content-Type: application/gzip` (это gzip-файл, а не `Content-Encoding`), и без этого вызывающий получил бы бинарь в поле `html`. `fetch_mode` кладётся в payload только когда не дефолтный — сайдкар прежней версии не должен получать незнакомый ключ (тот же приём, что с `referer` в #3247). ## Про карту сайта `sitemap-offers-1.xml.gz` подзапросом всё равно не берётся (279 байт — загрузчик). Не страшно: BFF полнее. В карте 3 999 квартир на продажу против 6 367 по BFF — **62% фонда**; как единственный источник она и не годилась. ## Тесты - сайдкар: **212 passed**, новый `test_server_fetch_mode.py` (6 проверок: дефолт остаётся навигацией, `subresource` не навигирует на целевой url, распаковка gzip, неизвестный режим — внятная ошибка); - backend: **316 passed**, новый `test_3264_sweep_subresource_mode.py` (5: оба вызова свипа идут с `subresource` И непустым `origin`; `fetch_mode` попадает в payload только когда осмыслен); - три тестовых дублёра `_do_fetch`/`_post_fetch` знали старую сигнатуру — обновлены. ## Что НЕ проверено Полный прогон свипа на этой правке. Замер подтверждает обе ручки BFF (`count` и `offers`) на всех узлах, но сквозной свип с сохранением лотов в БД запускается после деплоя.
lekss361 added 1 commit 2026-08-30 08:24:57 +00:00
fix(tradein/domclick): свип брал BFF навигацией браузера — теперь подзапросом (#3264)
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 1m28s
CI Trade-In / backend-tests (pull_request) Successful in 4m53s
5baf07d6e6
BFF-ручка Домклика не страница, а JSON-эндпоинт SPA. Сайдкар умел ровно
одно — page.goto(url), — и навигацией браузера на API-хост мы делали то,
чего настоящий клиент не делает никогда. Перехват сети на живой выдаче
30.08 это и показал: офферы приезжают в SSR-документе, а к BFF ходят
XHR'ы за гео, районами и метро.

Через мобильные узлы пула такая навигация упиралась в ChallengeTimeout:
прогоны 5330 и 5351 — 9 из 9 и 6 из 6 запросов зависли на челлендже, 0
лотов. Карточки через те же узлы в те же минуты шли.

Замер трёх режимов на одном узле, один и тот же ресурс:

  BFF navigate     ChallengeTimeout, 55с
  BFF subresource  HTTP 200, 52 байта, 6с
  BFF page_fetch   ошибка

Подзапрос проверен вширь: все шесть комнатных корзин HTTP 200, суммарно
6359 офферов против 6367, снятых напрямую с резидентного IP (расхождение
— дрейф фонда за пару часов); список офферов отдаётся на всех четырёх
узлах пула, по 20 штук, 120 КБ.

Сайдкар получил fetch_mode: navigate (дефолт, прежнее поведение),
subresource (context.request.get из прогретого контекста) и page_fetch
(fetch из страницы). Третий оставлен, потому что теоретически он ближе
всего к настоящему XHR, но в замере отказал — выбор сделан измерением, а
не рассуждением. Прогрев origin обязателен: рукопожатие QRATOR попадает
в куки контекста именно при заходе на страницу, поэтому свип теперь
передаёт origin и referer, которых раньше не передавал вовсе.

_decode_body распаковывает gzip по магическим байтам — карта офферов
приезжает Content-Type: application/gzip, и без этого вызывающий получил
бы бинарь в поле "html". Сама карта, к слову, подзапросом всё равно не
берётся (279 байт — загрузчик QRATOR), но BFF полнее: 100% фонда против
62% у карты.

fetch_mode кладётся в payload только когда он не дефолтный — сайдкар
прежней версии не должен получать незнакомый ключ (тот же приём, что с
referer в #3247).

Тесты: сайдкар 212 passed (новый test_server_fetch_mode.py — 6
проверок), backend 316 passed (новый test_3264_sweep_subresource_mode.py
— 5). Три тестовых дублёра _do_fetch/_post_fetch знали старую сигнатуру
— обновлены.
lekss361 merged commit 10b90cb85b into main 2026-08-30 08:32:52 +00:00
lekss361 deleted branch fix/3264-sweep-subresource-fetch 2026-08-30 08:32:52 +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#3266
No description provided.