From c995af73c266641a496c19619dd1ab35a8ee3ce5 Mon Sep 17 00:00:00 2001 From: bot-backend Date: Fri, 28 Aug 2026 22:52:47 +0300 Subject: [PATCH] =?UTF-8?q?docs(tradein/scrapers):=20=D0=B2=20=D1=80=D0=B5?= =?UTF-8?q?=D0=BF=D0=BE=D0=B7=D0=B8=D1=82=D0=BE=D1=80=D0=B8=D0=B8=20=D0=BB?= =?UTF-8?q?=D0=B5=D0=B6=D0=B0=D0=BB=D0=BE=20=D0=BE=D0=BF=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B5=D1=80=D0=B3=D0=BD=D1=83=D1=82=D0=BE=D0=B5=20=D0=BE=D0=B1?= =?UTF-8?q?=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D1=85?= =?UTF-8?q?=D1=80=D0=B0=D0=BD=D0=B8=D0=BB=D0=B8=D1=89=D0=B0=20=D1=81=D0=B5?= =?UTF-8?q?=D1=81=D1=81=D0=B8=D0=B9=20=D0=AF=D0=BD=D0=B4=D0=B5=D0=BA=D1=81?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #3195 замержен с утверждением, что авторизованная сессия раскрывает контакты продавца. Утверждение неверно, а лежит оно в двух местах, которые читают в первую очередь: докстринг app/services/yandex_session.py и шапка миграции 274. Повторный замер (#3192, 2026-08-28) сделан на ПРОД-транспорте — curl_cffi + прокси из пула, тот же путь, что у yandex_detail_backfill, — а не на сайдкаре, как первый: - offerCard.card.author у целевой карточки не несёт phones/phoneNumbers ни в одном из 12 случайных объявлений (6 AGENCY, 6 DEVELOPER), одинаково с куками и без; только encryptedPhones (1 токен) и redirectPhones; - phoneNumbers во всём INITIAL_STATE встречается только под offerCard.visitedOffers[*].author — истории просмотров НАШЕЙ учётки; анонимно список пуст, с куками в нём 9-10 записей; - первый замер («0 → 3,4,5,6») считал рост именно этой истории: +1 на каждый фетч; - authorStats.phones (коммутатор застройщика) отдаётся анонимно — тот же номер в обеих ветках. Правка только текстовая: ни схема, ни поведение не меняются. Шапку применённой миграции правлю сознательно — файл повторно не выполняется (учёт по имени в _schema_migrations), а неверное описание пережило бы любой следующий разбор. Таблицу не трогаю: она пуста, но DROP без явного решения владельца делать нельзя. Судьба #3195 — на владельце, #3192 помечен needs-human. Refs #3192, #3195 --- .../backend/app/services/yandex_session.py | 48 ++++++++++++------- .../data/sql/274_yandex_session_cookies.sql | 24 +++++----- 2 files changed, 45 insertions(+), 27 deletions(-) diff --git a/tradein-mvp/backend/app/services/yandex_session.py b/tradein-mvp/backend/app/services/yandex_session.py index b48fa679..dd5e63f0 100644 --- a/tradein-mvp/backend/app/services/yandex_session.py +++ b/tradein-mvp/backend/app/services/yandex_session.py @@ -1,9 +1,25 @@ """Yandex.Nedvizhimost session cookie management — load/save/invalidate encrypted cookies. -Замер на проде (#3192, сайдкар, две карточки, режимы вперемежку, 2026-08-28): -авторизованная сессия Яндекс.Недвижимости отдаёт ключ `mainPhone` и на 11 больше -уникальных телефонов продавца (90→101 / 89→100), чем анонимный запрос той же -карточки — контакты продавца физически недоступны без валидной сессии. +⚠️ ОБОСНОВАНИЕ ЭТОГО МОДУЛЯ ОПРОВЕРГНУТО (#3192, замер 2026-08-28, разбор в тикете). +Модуль строился под гипотезу «авторизованная сессия раскрывает контакты продавца». +Повторный замер на ПРОД-транспорте (curl_cffi + прокси, тот же путь, что у +yandex_detail_backfill, а не сайдкар) её опроверг: + + - offerCard.card.author — целевая карточка — не несёт phones/phoneNumbers ни в + одном из 12 проверенных объявлений (6 AGENCY, 6 DEVELOPER), одинаково с куками + и без; есть только encryptedPhones (1 токен) и redirectPhones; + - phoneNumbers во всём INITIAL_STATE встречается ТОЛЬКО под + offerCard.visitedOffers[*].author — это история просмотров НАШЕЙ учётки; + анонимно она пуста, с куками содержит 9-10 записей; + - первоначальный вывод «0 → 3,4,5,6 телефонов» считал именно рост этой истории: + +1 запись на каждый фетч, а не свойство карточек; + - authorStats.phones (коммутатор застройщика) отдаётся анонимно — тот же номер + в обеих ветках. + +То есть измеримой разницы по данным объявления авторизация не даёт. Хранилище +работает и покрыто тестами, но пока не имеет доказанного применения и НЕ +заполнено. Судьба модуля — за владельцем (#3192, label needs-human); удалять +таблицу самовольно нельзя. Ниже — исходное описание устройства, оно актуально. Cookies хранятся зашифрованно (pgp_sym_encrypt) в yandex_session_cookies — зеркалит app.services.cian_session / app.services.domclick_session, но: @@ -15,8 +31,8 @@ Cookies хранятся зашифрованно (pgp_sym_encrypt) в yandex_se browser context (следующая часть #3192) требует domain/path, не только value; - НЕТ allowlist-фильтра на сохранение (см. filter_analytics_cookies docstring) — Cian/DomClick фильтруют payload по списку известных cookie-имён перед - сохранением, здесь так делать нельзя: замер выше сделан на ПОЛНОМ наборе из - 44 cookies, и какая из них существенна для mainPhone-раскрытия — неизвестно. + сохранением, здесь так делать нельзя: замеры делались на ПОЛНОМ наборе из + 44 cookies, и какая из них за что отвечает — неизвестно. MVP: без verify_session (аналогично domclick_session) — верификация Yandex-сессии требует реального browser-фетча, не простого curl_cffi-запроса; вне рамок этой @@ -48,10 +64,10 @@ COOKIE_EXPIRY_WARN_DAYS = 5 # ВАЖНО: это НЕ allowlist для фильтрации того, что сохранять (в отличие от # CIAN_REQUIRED_COOKIES / DOMCLICK_REQUIRED_COOKIES) — используется ТОЛЬКО в # has_auth_marker() для проверки "дамп вообще похож на авторизованную сессию". -# Замер #3192 сделан на ПОЛНОМ наборе из 44 cookies, какая именно cookie -# существенна для mainPhone-раскрытия — неизвестно, поэтому save_session хранит -# весь дамп целиком (за вычетом аналитики), а не только эти имена. НЕ сужай -# набор, который реально сохраняется, до этого списка. +# Замеры #3192 делались на ПОЛНОМ наборе из 44 cookies, за что отвечает каждая — +# неизвестно, поэтому save_session хранит весь дамп целиком (за вычетом аналитики), +# а не только эти имена. НЕ сужай набор, который реально сохраняется, до этого +# списка. YANDEX_CRITICAL_COOKIES: set[str] = { "Session_id", "sessionid2", @@ -77,12 +93,12 @@ ANALYTICS_COOKIE_PREFIXES: tuple[str, ...] = ("_ym_", "yabs-", "_yasc") def filter_analytics_cookies(cookies: list[dict[str, Any]]) -> list[dict[str, Any]]: """Drop только заведомую аналитику (_ym_*, yabs-*, _yasc*) — остальное как есть. - В отличие от Cian/DomClick здесь нет allowlist на сохранение: замер #3192 - сделан на ПОЛНОМ наборе из 44 cookies, значимость конкретной cookie для - mainPhone-раскрытия неизвестна. Сужать этот фильтр до allowlist известных - "критичных" имён — значит потерять часть замера у следующего инженера, - который решит его "оптимизировать". YANDEX_CRITICAL_COOKIES используется - только в has_auth_marker(), не здесь. + В отличие от Cian/DomClick здесь нет allowlist на сохранение: замеры #3192 + делались на ПОЛНОМ наборе из 44 cookies, значимость конкретной cookie + неизвестна. Сужать этот фильтр до allowlist известных "критичных" имён — + значит потерять часть замера у следующего инженера, который решит его + "оптимизировать". YANDEX_CRITICAL_COOKIES используется только в + has_auth_marker(), не здесь. """ return [c for c in cookies if not str(c.get("name", "")).startswith(ANALYTICS_COOKIE_PREFIXES)] diff --git a/tradein-mvp/backend/data/sql/274_yandex_session_cookies.sql b/tradein-mvp/backend/data/sql/274_yandex_session_cookies.sql index e3b56240..175a38b7 100644 --- a/tradein-mvp/backend/data/sql/274_yandex_session_cookies.sql +++ b/tradein-mvp/backend/data/sql/274_yandex_session_cookies.sql @@ -1,18 +1,20 @@ -- 274_yandex_session_cookies.sql -- Purpose: Encrypted storage for Yandex.Nedvizhimost browser session cookies. --- Замер на проде (сайдкар, две карточки, вперемежку с/без кук, 2026-08-28): --- author.phoneNumbers присутствует ТОЛЬКО в авторизованном режиме --- (0 без кук → 2 с куками, одинаково на обеих карточках), плюс три --- номера в author.phones сверху. Это контакт продавца с тегом канала --- и redirectId. --- NB: mainPhone НЕ собирать — он лежит в паспортном блоке рядом с --- login/passportHost/passportPhones, то есть это номер НАШЕЙ учётки, --- а не продавца. Первая версия этого замера приняла его за данные --- объявления; счётчики по конкретным полям это опровергли. --- encryptedPhones одинаков в обоих режимах (54) — зашифрованные токены --- отдаются всегда, различие не в них. -- Uses pgcrypto pgp_sym_encrypt for AES encryption at rest (зеркалит -- 027_cian_session_cookies.sql / 174_domclick_session_cookies.sql). +-- +-- ⚠️ ОБОСНОВАНИЕ ЭТОЙ ТАБЛИЦЫ ОПРОВЕРГНУТО (#3192, замер 2026-08-28). +-- Шапка этого файла утверждала, что авторизованная сессия раскрывает +-- author.phoneNumbers. Повторный замер на прод-транспорте (curl_cffi + +-- прокси, а не сайдкар) показал: у целевой карточки +-- (offerCard.card.author) телефонов нет никогда — 12 из 12 объявлений, +-- с куками и без, только encryptedPhones + redirectPhones. +-- phoneNumbers встречается ТОЛЬКО под offerCard.visitedOffers[*].author +-- — это история просмотров НАШЕЙ учётки, растущая на +1 с каждым +-- фетчем; именно её рост первый замер принял за данные объявления. +-- Таблица создана и пуста. Оставлена как есть: DROP без явного решения +-- владельца не делаем (#3192, label needs-human). Разбор — в тикете и +-- в докстринге app/services/yandex_session.py. -- Dependencies: -- - pgcrypto extension (installed here via CREATE EXTENSION IF NOT EXISTS) -- Deploy order: Apply after 273.