feat(tradein/scrapers): хранилище авторизованной сессии Яндекс.Недвижимости #3195

Merged
bot-backend merged 3 commits from feat/3192-yandex-session-store into main 2026-08-28 19:02:06 +00:00

3 commits

Author SHA1 Message Date
bot-backend
4c3a767f45 fix(tradein/sql): миграция 274 без lock_timeout роняла гейт #2752
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m55s
CI / changes (pull_request) Successful in 11s
CI / backend-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
CREATE INDEX без lock_timeout встаёт в очередь за чужой долгой сессией
и уводит за собой запросы приложения. Таблица новая и пустая, но очередь
блокировок этого не знает, а гейт check-migration-lock-timeout.py держит
правило на каждом PR — и правильно делает.

Заодно поправлена шапка миграции: там осталась моя опровергнутая версия,
будто ценность в mainPhone и одиннадцати номерах. mainPhone лежит в
паспортном блоке рядом с login/passportHost/passportPhones — это номер
НАШЕЙ учётки, а не продавца, и собирать его нельзя. Настоящее различие
между режимами — author.phoneNumbers (0 без кук, 2 с куками, одинаково
на обеих карточках); encryptedPhones в обоих режимах одинаков.

Refs #3192
2026-08-28 21:56:24 +03:00
bot-backend
bb59caa90c feat(tradein/scrapers): хранилище авторизованной сессии Яндекс.Недвижимости
Some checks failed
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Failing after 8s
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 1m6s
CI Trade-In / backend-tests (pull_request) Successful in 4m48s
Замер через прод-сайдкар, две карточки, один тракт с куками и без:
поле author.phoneNumbers присутствует только в авторизованном режиме
(0 без кук, 2 с куками, одинаково на обеих), плюс три номера автора
сверху. Это контакты продавца с тегом канала и redirectId.
encryptedPhones при этом одинаков в обоих режимах (54) — зашифрованные
токены отдаются всегда, различие не в них.

listings.phones (jsonb) существует в схеме и пуст у всех 51 251
активного объявления по всем четырём источникам. Яндекс станет первым
источником с контактами.

Форма таблицы и сервиса повторяет работающие в проде cian_session_cookies
и domclick_session_cookies: pgp_sym_encrypt, expires_at_estimate,
last_used_at/last_invalid_at, предупреждение о протухании.

Два отличия от образцов, оба намеренные:

Хранится ПОЛНЫЙ CDP-объект куки (name/value/domain/path/httpOnly/
secure/expires), а не плоский dict name→value. У Яндекса куки живут на
трёх доменах (.yandex.ru, .passport.yandex.ru, .realty.yandex.ru) и
дублируют имена (pi ×4, yashr ×4) — плоский словарь их схлопывает.
Сайдкар сегодня принимает только плоский формат; когда его контракт
починят, хранилище уже готово.

Набор сохраняется целиком, минус очевидная аналитика (_ym_*, yabs-*,
_yasc*). Список критичных кук используется ТОЛЬКО для проверки, что
дамп похож на авторизованную сессию, и не является фильтром сохранения:
замер сделан на полном наборе 44 кук, и какая из них существенна —
неизвестно. Сужение до allowlist сломало бы измеренное; это защищено
комментариями и тестом.

Роуты закрыты на двух уровнях: Caddy basic_auth снаружи и rbac_guard
внутри (role=admin для /api/v1/admin/*).

Refs #3192
2026-08-28 21:53:02 +03:00
bot-backend
e2863f6be9 fix(tradein/domclick): каждый запрос получал чистый браузерный контекст, куки реплеились протухшими
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 9s
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 1m7s
CI Trade-In / backend-tests (pull_request) Successful in 4m49s
Сайдкар на каждый /fetch делал browser.new_page() поверх AsyncCamoufox — это новый
изолированный контекст, cookie-jar умирал сразу после ответа. Туда вливался
замороженный снимок кук из domclick_session_cookies, где qrator_jsid2 живёт ~2.5
часа, а хранится 30 дней. Первый запрос проходил с ещё живым токеном, все следующие
показывали QRATOR один и тот же протухший — он отдавал челлендж-страницу без
__SSR_STATE__. Отсюда двухнедельное attempted=4, enriched=1, blocked=3.

Замер на проде 28.08: через сайдкар 26 запросов подряд — 100% блоков, включая
свежеротированный exit-IP; те же карточки в тёплом контексте — 5 из 5 успешно,
~2 сек каждая.

Сайдкар получил переиспользуемый per-provider контекст: куки вливаются один раз при
создании, дальше jar живёт сам; страница закрывается после ответа, контекст остаётся.
Закрытие контекста подшито к _close_browser, так что relaunch и shutdown его не
теряют. Флаг opt-in: при reuse_context=False payload /fetch не получает новых ключей
вовсе, поведение остальных поставщиков не меняется.

Сброс сожжённого контекста — ровно один на обнаруженный блок, через отложенный флаг
BrowserFetcher.request_context_reset(): fetch_detail() в kit не прокидывает
reset_context, и менять этот промежуточный слой ради одного поставщика не хотелось.

NB для деплоя: правка работает только если tradein-browser пересобран вместе с
бэкендом. Старый сайдкар новые поля молча проигнорирует — не упадёт, но и не починит.

Refs #3190, #3118
2026-08-28 21:43:24 +03:00