[tradein] cian-первичка не освежается: 9205 заморожены с 31.05, 93% без house_id_fk, фантомы не гаснут (Савкова 29) #1781

Closed
opened 2026-06-19 11:14:06 +00:00 by bot-backend · 6 comments
Collaborator

Жалоба (live): оценка Trade-In по «ул. Евгения Савкова 29» (ЕКБ, ЖК Суходольский квартал) не подтягивает все cian-объявления того же дома. Проверка вживую (cian, 2026-06-19): на циане по дому 29 (house id 10386120) 5 объявлений, у нас в БД 7 (все last_seen=2026-05-31), пересекаются только 2. БД одновременно устарела, переоценивает дом (фантомы) и не добирает 3 свежих.

Корни (подтверждены кодом + прод-запросами)

1. #938 заморозил cian-первичку с 31.05 [HIGH]. Ночной cian_full_load идёт secondary_only=True (hardcoded scrape_pipeline.py:2063) и выбрасывает listing_segment='novostroyki' на парсе (cian.py:712-715). С момента #938 (коммит 567549c, 2026-05-31 21:35 UTC) регулярный свип новостройки не пере-сохраняет. Итог: 9205 из 9925 активных cian-novostroyki города заморожены на last_seen=2026-05-31.

2. cian_city_sweep — единственный путь загрузки cian-первички — покрывает мало [HIGH]. Жив (last_run 2026-06-19 03:31, next 06-20 04:34, окно 2-5 UTC), но anchor+radius (radius_m=1500, pages_per_anchor=3) освежает ~145 новостроек/ночь на весь город. Дом 29/29А вне досягаемости якорей — освежилась только Савкова 35 (id 331185305), 29/29А нет. Зона Лёхи (cian-парсер/анти-бан/якоря).

3. house_id_fk=NULL у 9235/9925 (93%) [HIGH]. Не промах матчинга — все 9235 сматчены (резолвятся через house_sources по (ext_source, ext_id) 100%: 9235/9235). Просто зеркало FK в realtime появилось только 17.06 (коммит e63b21e, base.py:600-616), а майские строки переписались после одноразового бэкфилла, и рекуррентный бэкфилл их пропускает (NOT EXISTS). NULL FK ломает детерминированный Tier S-canonical «тот же дом» (estimator.py:3375-3423).

4. deactivate_stale_cian чистит только вторичку [MEDIUM]. params {segments:['vtorichka'],ttl_days:30} → протухшие новостройки никогда не гаснут → фантомы (5 из 7 по дому 29 уже ушли с циана) висят is_active=true вечно и кормят оценщик.

5. NULL geom — ложный след [LOW]. У всех активных novostroyki есть координаты (null_geom=0), они на карте и в радиусном пути. geocode_missing нерелевантен.

План фикса (с зонами ответственности)

  • [me-safe → PR] Идемпотентный бэкфилл house_id_fk из house_sources по точному source-id (без адресного fuzzy, 9235/9235 резолвятся) → восстанавливает каноническую группировку «тот же дом» по всему городу. Применяется на деплое.
  • [Лёха · cian-зона] Расширить охват cian_city_sweep (якоря/радиус/pages) или выделенный newbuilding-свип, чтобы дом 29/29А и заморо­женная когорта 9205 переобновлялись. Плюс обновить cian-session-cookies (протухли с 31.05) — без них свип не фетчит.
  • [Лёха · prod-exec] Запустить бэкфилл/прогон на проде (у Клода read-only psql), точечно ре-скрейпить Широкую Речку.
  • [рекомендация, сиквенс ПОСЛЕ охвата] Добавить novostroyki в deactivate_stale_cian (ttl_days≈30) — чтобы фантомы гасли, но только после расширения охвата, иначе массово деактивирует 9205.

Связано: #1774 (PR #1776) — estimator Tier A теперь принимает same-building novostroyki; этот тикет — про то, что скрейпер не держит cian-первичку свежей (бутылочное горло). #1772 (data-block), #1773 (avito de-glue PR #1775).

Диагностика: multi-agent workflow (код-пути + прод-данные + матчинг/гео + live-cian).

**Жалоба (live):** оценка Trade-In по «ул. Евгения Савкова 29» (ЕКБ, ЖК Суходольский квартал) не подтягивает все cian-объявления того же дома. Проверка вживую (cian, 2026-06-19): на циане по дому 29 (house id 10386120) **5 объявлений**, у нас в БД **7** (все `last_seen=2026-05-31`), пересекаются **только 2**. БД одновременно устарела, переоценивает дом (фантомы) и не добирает 3 свежих. ## Корни (подтверждены кодом + прод-запросами) **1. #938 заморозил cian-первичку с 31.05 [HIGH].** Ночной `cian_full_load` идёт `secondary_only=True` (hardcoded `scrape_pipeline.py:2063`) и выбрасывает `listing_segment='novostroyki'` на парсе (`cian.py:712-715`). С момента #938 (коммит 567549c, 2026-05-31 21:35 UTC) регулярный свип новостройки **не пере-сохраняет**. Итог: **9205 из 9925** активных cian-novostroyki города заморожены на `last_seen=2026-05-31`. **2. `cian_city_sweep` — единственный путь загрузки cian-первички — покрывает мало [HIGH].** Жив (last_run 2026-06-19 03:31, next 06-20 04:34, окно 2-5 UTC), но anchor+radius (radius_m=1500, pages_per_anchor=3) освежает ~145 новостроек/ночь на весь город. Дом 29/29А вне досягаемости якорей — освежилась только Савкова 35 (id 331185305), 29/29А нет. **Зона Лёхи (cian-парсер/анти-бан/якоря).** **3. `house_id_fk=NULL` у 9235/9925 (93%) [HIGH].** Не промах матчинга — все 9235 сматчены (резолвятся через `house_sources` по `(ext_source, ext_id)` 100%: 9235/9235). Просто **зеркало FK** в realtime появилось только 17.06 (коммит e63b21e, `base.py:600-616`), а майские строки переписались после одноразового бэкфилла, и рекуррентный бэкфилл их пропускает (NOT EXISTS). NULL FK ломает детерминированный Tier S-canonical «тот же дом» (`estimator.py:3375-3423`). **4. `deactivate_stale_cian` чистит только вторичку [MEDIUM].** params `{segments:['vtorichka'],ttl_days:30}` → протухшие новостройки никогда не гаснут → фантомы (5 из 7 по дому 29 уже ушли с циана) висят `is_active=true` вечно и кормят оценщик. **5. NULL geom — ложный след [LOW].** У всех активных novostroyki есть координаты (`null_geom=0`), они на карте и в радиусном пути. `geocode_missing` нерелевантен. ## План фикса (с зонами ответственности) - **[me-safe → PR]** Идемпотентный бэкфилл `house_id_fk` из `house_sources` по точному source-id (без адресного fuzzy, 9235/9235 резолвятся) → восстанавливает каноническую группировку «тот же дом» по всему городу. Применяется на деплое. - **[Лёха · cian-зона]** Расширить охват `cian_city_sweep` (якоря/радиус/pages) или выделенный newbuilding-свип, чтобы дом 29/29А и заморо­женная когорта 9205 переобновлялись. Плюс обновить cian-session-cookies (протухли с 31.05) — без них свип не фетчит. - **[Лёха · prod-exec]** Запустить бэкфилл/прогон на проде (у Клода read-only psql), точечно ре-скрейпить Широкую Речку. - **[рекомендация, сиквенс ПОСЛЕ охвата]** Добавить `novostroyki` в `deactivate_stale_cian` (ttl_days≈30) — чтобы фантомы гасли, но только после расширения охвата, иначе массово деактивирует 9205. **Связано:** #1774 (PR #1776) — estimator Tier A теперь принимает same-building novostroyki; этот тикет — про то, что скрейпер не держит cian-первичку свежей (бутылочное горло). #1772 (data-block), #1773 (avito de-glue PR #1775). _Диагностика: multi-agent workflow (код-пути + прод-данные + матчинг/гео + live-cian)._
Author
Collaborator

Verify (2026-06-23, из audit #1871): разморозка cian-первички подтверждена — newbuilding_listings MAX(source_last_scraped_at) = 2026-06-22 20:54, 491 строк освежено за 7 дней (total 9653). Корень #1/#2 (freeze + sweep) — снят, свип живой.

⚠️ НЕ закрываю автоматически: остальные корни тикета вне этой проверки и частично в зоне Лёхи (расширение охвата cian_city_sweep / cookies; phantom-deactivation novostroyki). Плюс схема таблицы изменилась с момента заведения (house_id_fk теперь не в newbuilding_listings) — стоит ревизовать актуальность пунктов #3/#4 перед закрытием. Оставляю решение о close за владельцем.

**Verify (2026-06-23, из audit #1871):** разморозка cian-первички подтверждена — `newbuilding_listings` MAX(`source_last_scraped_at`) = **2026-06-22 20:54**, 491 строк освежено за 7 дней (total 9653). Корень #1/#2 (freeze + sweep) — снят, свип живой. ⚠️ НЕ закрываю автоматически: остальные корни тикета вне этой проверки и частично в зоне Лёхи (расширение охвата `cian_city_sweep` / cookies; phantom-deactivation novostroyki). Плюс схема таблицы изменилась с момента заведения (`house_id_fk` теперь не в `newbuilding_listings`) — стоит ревизовать актуальность пунктов #3/#4 перед закрытием. Оставляю решение о close за владельцем.
Author
Collaborator

Статус 2026-06-26 (свежие прод-цифры — сильно сдвинулось с 06-19)

Root #3 (house_id_fk) — РЕШЕНО (bot-safe часть). Миграция 130_backfill_listings_house_id_fk.sql задеплоена (в _schema_migrations) + realtime-зеркало base.py:636-645 (#e63b21e). NULL-fk по cian: 9235 → 485 (3% от 15951 активных). Из этих 485 — 0 имеют house_ext_id и 0 резолвятся детерминированным джойном (house_source+house_ext_id → house_sources). Т.е. бэкфилл выбрал всё что можно; остаток 485 — строки без house-ключа, их добьёт только ре-скрейп (зона Лёхи, см. ниже). Новой миграции не нужно. NULL-fk теперь мал по всем источникам (avito 4%, cian 3%, yandex 0.7%).

🟡 Root #1 (новостройки заморожены) — ЖИВ, зона Лёхи. cian novostroyki: 9598 / 10003 активных заморожены на last_seen ≤ 31.05 (vtorichka свежая: 482/5734 frozen). Причина та же — cian_full_load идёт secondary_only=True (#938, scrape_pipeline.py:2063) + cian.py:712-715 дропает novostroyki на парсе. Это намеренное отключение (#938) — просто снять флаг рискованно (анти-бан/нагрузка). Нужно: anti-ban-aware охват novostroyki (выделенный newbuilding-listing-свип или расширение cian_city_sweep) + обновить cian-session-cookies (протухли с 31.05). Зона Лёхи (cian-парсер/анти-бан/якоря).

🟡 Root #2 (cian_city_sweep охват) — Лёха. ~145 новостроек/ночь на весь город, дом 29/29А вне якорей.

🔴 Root #4 (фантомы не гаснут) — BLOCKED на охвате. deactivate_stale_cian чистит только вторичку. Добавлять novostroyki в деактивацию СЕЙЧАС НЕЛЬЗЯ — мгновенно деактивирует 9598 замороженных (они active+stale, но многие реальны, просто не ре-скрейпятся). Делать ТОЛЬКО после расширения охвата.

Итог

Bot-safe часть закрыта (house_id_fk). Остаток — целиком cian-зона Лёхи (охват novostroyki + cookies), после чего фантом-деактивация. Помечаю needs-human/p1.

## Статус 2026-06-26 (свежие прод-цифры — сильно сдвинулось с 06-19) **✅ Root #3 (house_id_fk) — РЕШЕНО (bot-safe часть).** Миграция `130_backfill_listings_house_id_fk.sql` задеплоена (в `_schema_migrations`) + realtime-зеркало `base.py:636-645` (#e63b21e). NULL-fk по cian: **9235 → 485** (3% от 15951 активных). Из этих 485 — **0 имеют `house_ext_id`** и **0 резолвятся** детерминированным джойном (`house_source+house_ext_id → house_sources`). Т.е. бэкфилл выбрал всё что можно; остаток 485 — строки без house-ключа, их добьёт только ре-скрейп (зона Лёхи, см. ниже). Новой миграции не нужно. NULL-fk теперь мал по всем источникам (avito 4%, cian 3%, yandex 0.7%). **🟡 Root #1 (новостройки заморожены) — ЖИВ, зона Лёхи.** cian novostroyki: **9598 / 10003 активных заморожены на last_seen ≤ 31.05** (vtorichka свежая: 482/5734 frozen). Причина та же — `cian_full_load` идёт `secondary_only=True` (#938, scrape_pipeline.py:2063) + cian.py:712-715 дропает novostroyki на парсе. Это **намеренное** отключение (#938) — просто снять флаг рискованно (анти-бан/нагрузка). Нужно: anti-ban-aware охват novostroyki (выделенный newbuilding-listing-свип или расширение cian_city_sweep) + обновить cian-session-cookies (протухли с 31.05). **Зона Лёхи (cian-парсер/анти-бан/якоря).** **🟡 Root #2 (cian_city_sweep охват) — Лёха.** ~145 новостроек/ночь на весь город, дом 29/29А вне якорей. **🔴 Root #4 (фантомы не гаснут) — BLOCKED на охвате.** `deactivate_stale_cian` чистит только вторичку. Добавлять novostroyki в деактивацию СЕЙЧАС НЕЛЬЗЯ — мгновенно деактивирует 9598 замороженных (они active+stale, но многие реальны, просто не ре-скрейпятся). Делать ТОЛЬКО после расширения охвата. ## Итог Bot-safe часть закрыта (house_id_fk). Остаток — целиком cian-зона Лёхи (охват novostroyki + cookies), после чего фантом-деактивация. Помечаю needs-human/p1.
bot-backend added the
bug
needs-human
priority/p1
scope/backend
scrapers
tradein
labels 2026-06-26 15:53:59 +00:00
Author
Collaborator

[me-safe] FK-бэкфилл задеплоен (migration 136) + актуализация цифр (2026-06-27)

PR #1936 смержен + задеплоен. Важно: исходная диагностика (06-19) устарела — пересчёт на проде:

Что было на самом деле сейчас (не 9235)

  • Активных NULL-FK осталось 1884 (не 9205 — когорта рассосалась: re-scrape после 17.06 ловил mirror, deactivate_stale почистил часть).
  • Из них 0 несут house_source/house_ext_id → re-run migration 130 был бы strict no-op (130 покрывала только (house_source, house_ext_id)).
  • Реальный путь резолва для этих строк — per-listing-identity: mirror (base.py:704-750) при отсутствии house-catalog id зовёт match_or_create_house(ext_source=source, ext_id=source_id) → Tier-1 house_sources(ext_source,ext_id) conf=1.0. Migration 130 этот ключ НЕ трогала.

Что сделала migration 136 (faithful replay Tier-1, не fuzzy)

UPDATE listings SET house_id_fk = hs.house_id FROM house_sources hs
WHERE house_id_fk IS NULL AND source_id IS NOT NULL
  AND hs.ext_source=source AND hs.ext_id=source_id AND hs.house_id IS NOT NULL;

Результат на проде (verified):

  • Активных NULL-FK 1884 → 1140 (залито 744 active).
  • Всего (с inactive) залито 4608 строк; linked house_id_fk = 65406.
  • Residual resolvable via (source,source_id) = 0 — всё, что резолвилось каноническим source-id, залито.
  • listings_search_mv пере-refreshнут (CONCURRENTLY) → estimator/search MV видит новые линки сразу.
  • code-reviewer + database-expert: APPROVE (идемпотентно, collision-safe, faithful mirror-replay, короткие row-locks).

Остаётся (зона Лёхи — НЕ SQL-бэкфилл)

  1. ~1140 active NULL-FK без canonical source-id матча → нужен re-scrape/re-match (mirror проставит FK при следующем скрейпе). SQL-путь для них невозможен (нет существующего house_sources-маппинга — нужен прогон матчера: address/geo/fingerprint).
  2. Корень тикета — freshness заморо­женной cian-первички (9205 на last_seen=2026-05-31): cian_full_load secondary_only=True + охват cian_city_sweep ~145 новостроек/ночь + протухшие cian-cookies. Расширить охват/якоря + обновить cookies. После расширения охвата — добавить novostroyki в deactivate_stale_cian (фантомы), но ТОЛЬКО после, иначе массово деактивирует.

Группировочный слой (house_id_fk) починен везде, где это возможно детерминированно. Остаток — чисто скрейперный (свежесть + охват). Оставляю open под scraper-часть.

## [me-safe] FK-бэкфилл задеплоен (migration 136) + актуализация цифр (2026-06-27) PR #1936 смержен + задеплоен. **Важно: исходная диагностика (06-19) устарела** — пересчёт на проде: ### Что было на самом деле сейчас (не 9235) - Активных NULL-FK осталось **1884** (не 9205 — когорта рассосалась: re-scrape после 17.06 ловил mirror, deactivate_stale почистил часть). - Из них **0** несут `house_source/house_ext_id` → re-run migration 130 был бы **strict no-op** (130 покрывала только `(house_source, house_ext_id)`). - Реальный путь резолва для этих строк — **per-listing-identity**: mirror (`base.py:704-750`) при отсутствии house-catalog id зовёт `match_or_create_house(ext_source=source, ext_id=source_id)` → Tier-1 `house_sources(ext_source,ext_id)` conf=1.0. Migration 130 этот ключ НЕ трогала. ### Что сделала migration 136 (faithful replay Tier-1, не fuzzy) ```sql UPDATE listings SET house_id_fk = hs.house_id FROM house_sources hs WHERE house_id_fk IS NULL AND source_id IS NOT NULL AND hs.ext_source=source AND hs.ext_id=source_id AND hs.house_id IS NOT NULL; ``` **Результат на проде (verified):** - Активных NULL-FK **1884 → 1140** (залито 744 active). - Всего (с inactive) залито **4608** строк; linked house_id_fk = **65406**. - **Residual resolvable via (source,source_id) = 0** — всё, что резолвилось каноническим source-id, залито. - `listings_search_mv` пере-refreshнут (CONCURRENTLY) → estimator/search MV видит новые линки сразу. - code-reviewer + database-expert: **✅ APPROVE** (идемпотентно, collision-safe, faithful mirror-replay, короткие row-locks). ### Остаётся (зона Лёхи — НЕ SQL-бэкфилл) 1. **~1140 active NULL-FK без canonical source-id матча** → нужен re-scrape/re-match (mirror проставит FK при следующем скрейпе). SQL-путь для них невозможен (нет существующего house_sources-маппинга — нужен прогон матчера: address/geo/fingerprint). 2. **Корень тикета — freshness заморо­женной cian-первички** (9205 на `last_seen=2026-05-31`): `cian_full_load secondary_only=True` + охват `cian_city_sweep` ~145 новостроек/ночь + протухшие cian-cookies. Расширить охват/якоря + обновить cookies. После расширения охвата — добавить `novostroyki` в `deactivate_stale_cian` (фантомы), но ТОЛЬКО после, иначе массово деактивирует. Группировочный слой (house_id_fk) починен везде, где это возможно детерминированно. Остаток — чисто скрейперный (свежесть + охват). Оставляю open под scraper-часть.
lekss361 added the
data
label 2026-08-16 10:24:54 +00:00
Author
Collaborator

Замер 21.08.2026: корень №1 закрыт, корень №2 стоит на месте — и вот его цена в числах

Пришёл сюда с другой стороны — из #2994, где 26 203 «фантома» оказались не отказом деактиватора, а тенью несобираемого инвентаря. Разрез сошёлся ровно на этом issue.

Корень №1 (secondary_only=True) — исчез

Флага secondary_only в исходниках tradein-backend больше нет (остались только упоминания в тестах), cian_full_load в расписании идёт без него, interval_days: 3, последний прогон 17.08. То есть парсер новостройки больше не выбрасывает.

Корень №2 (cian_city_sweep покрывает мало) — стоит

Полный обход до новостроек не доходит. Видно по датам последнего подтверждения:

cian/vtorichka (покрыт)          cian/novostroyki (не покрыт)
  18.08  2336                      18.08   255
  15.08  1922                      20.08   123
  17.08   968                      13.08    60
  14.08   188                      12.08    62
  ...                              ...  ровный ручеёк 20-120/сутки

У vtorichka — залпы полного обхода. У novostroyki залпов нет ни одного: только тот ручеёк, что даёт anchor+radius cian_city_sweep. За 20 суток так подтвердилось ≈1 162 строки из 11 993 активных.

Цена в охвате

Доля активного инвентаря, которую свип подтверждает своим приходом:

источник / сегмент за неделю за месяц
cian / vtorichka 75.4 % 100.0 %
avito / novostroyki 57.0 % 100.0 %
yandex / vtorichka 61.0 % 90.0 %
cian / novostroyki 5.1 % 11.7 %

Медианный возраст активной cian-новостройки — 81 сутки, самая старая последний раз видена 23.05. Из 11 993 активных строк 10 585 старше 30 суток.

Для сравнения: avito обходит свои новостройки полностью за месяц, и просроченных строк там ноль.

Почему это не чинится со стороны деактивации

В #2994 предлагалось закрыть расхождение is_active реконсилятором и расширением скоупа деактивации на novostroyki. Замерил — нельзя: при охвате 11.7 % за месяц TTL=30 снял бы ≈88 % инвентаря просто потому, что свип туда не приходил. Это ровно механизм #2659. Докстрока deactivate_stale_avito.py:10-20 этот пробел уже описывает и ставит предусловие «сперва выяснить, поддерживает ли cian full-coverage sweep для novostroyki СЕЙЧАС» — ответ по замеру: не поддерживает.

То есть до тех пор, пока охват не вырастет, выбор стоит между «показывать мёртвое» и «убивать живое». Настоящая работа — здесь, в сборе, а не во флаге живости.

Ориентир приёмки

Месячный охват cian/novostroyki на уровне покрытых сегментов — 90 %+ (как у yandex/vtorichka). При нём расширение деактивации на этот сегмент станет безопасным, и предусловие из докстроки будет выполнено фактом, а не на глаз.

## Замер 21.08.2026: корень №1 закрыт, корень №2 стоит на месте — и вот его цена в числах Пришёл сюда с другой стороны — из #2994, где 26 203 «фантома» оказались не отказом деактиватора, а тенью несобираемого инвентаря. Разрез сошёлся ровно на этом issue. ### Корень №1 (`secondary_only=True`) — исчез Флага `secondary_only` в исходниках tradein-backend больше нет (остались только упоминания в тестах), `cian_full_load` в расписании идёт без него, `interval_days: 3`, последний прогон 17.08. То есть парсер новостройки больше не выбрасывает. ### Корень №2 (`cian_city_sweep` покрывает мало) — стоит Полный обход до новостроек **не доходит**. Видно по датам последнего подтверждения: ``` cian/vtorichka (покрыт) cian/novostroyki (не покрыт) 18.08 2336 18.08 255 15.08 1922 20.08 123 17.08 968 13.08 60 14.08 188 12.08 62 ... ... ровный ручеёк 20-120/сутки ``` У vtorichka — **залпы** полного обхода. У novostroyki залпов нет ни одного: только тот ручеёк, что даёт anchor+radius `cian_city_sweep`. За 20 суток так подтвердилось ≈1 162 строки из 11 993 активных. ### Цена в охвате Доля активного инвентаря, которую свип подтверждает своим приходом: | источник / сегмент | за неделю | **за месяц** | |---|---|---| | cian / vtorichka | 75.4 % | **100.0 %** | | avito / novostroyki | 57.0 % | **100.0 %** | | yandex / vtorichka | 61.0 % | **90.0 %** | | **cian / novostroyki** | **5.1 %** | **11.7 %** | Медианный возраст активной cian-новостройки — **81 сутки**, самая старая последний раз видена 23.05. Из 11 993 активных строк 10 585 старше 30 суток. Для сравнения: avito обходит свои новостройки полностью за месяц, и просроченных строк там **ноль**. ### Почему это не чинится со стороны деактивации В #2994 предлагалось закрыть расхождение `is_active` реконсилятором и расширением скоупа деактивации на novostroyki. Замерил — нельзя: при охвате 11.7 % за месяц TTL=30 снял бы ≈88 % инвентаря просто потому, что свип туда не приходил. Это ровно механизм #2659. Докстрока `deactivate_stale_avito.py:10-20` этот пробел уже описывает и ставит предусловие «сперва выяснить, поддерживает ли cian full-coverage sweep для novostroyki СЕЙЧАС» — ответ по замеру: **не поддерживает**. То есть до тех пор, пока охват не вырастет, выбор стоит между «показывать мёртвое» и «убивать живое». Настоящая работа — здесь, в сборе, а не во флаге живости. ### Ориентир приёмки Месячный охват cian/novostroyki на уровне покрытых сегментов — **90 %+** (как у yandex/vtorichka). При нём расширение деактивации на этот сегмент станет безопасным, и предусловие из докстроки будет выполнено фактом, а не на глаз.
Author
Collaborator

Поправка к моему же комментарию выше: корень №1 НЕ закрыт

В предыдущем сообщении я написал, что флага secondary_only в исходниках больше нет. Это неверно. Мой grep смотрел только в tradein-mvp/backend/, а флаг переехал в пакет tradein-mvp/packages/scraper-kit/. Поведение не менялось вовсе — изменилось только место.

packages/scraper-kit/src/scraper_kit/orchestration/pipeline.py:3328
    secondary_only=True,          ← хардкод в run_cian_full_load, не параметр расписания

packages/scraper-kit/src/scraper_kit/providers/cian/serp.py:367
    secondary_only: bool = True,  ← дефолт

Прошу прощения за шум: вывод про охват от этого не меняется (11.7 % за месяц — замер по данным), но причина названа была неправильно.

Что при этом выяснилось — и это важнее

Новостройки не пропускаются при запросе. Они скачиваются, разбираются и выбрасываются последним шагом.

# cian/serp.py:667-673
collected_this_bucket = len(bucket_lots)
dropped_nb = 0
if secondary_only:
    filtered = [lot for lot in bucket_lots if lot.listing_segment != "novostroyki"]
    dropped_nb = collected_this_bucket - len(filtered)
    bucket_lots = filtered

Почему так, объяснено в докстроке: object_type=1 в SERP Cian ненадёжен (~5 % выдачи), поэтому фильтруют не запросом, а по authoritative listing_segment из offer.newbuilding.id — уже после парсинга.

Следствие: включение новостроек в полный обход стоит ноль дополнительных HTTP-запросов. Проверил, что это не догадка: проба бакета берёт totalOffers из Redux-состояния SERP, и дробление считается как pages_needed = ceil(totalOffers / offers_per_page). totalOffers включает обе категории — то есть страницы с новостройками уже скачиваются и уже оплачены и лимитом страниц, и антибан-бюджетом. Выбрасывается только результат разбора.

То есть это не проблема ёмкости сбора и не «Cian не отдаёт». Это однострочная политика.

Чего я НЕ смог измерить

Сколько именно строк выбрасывается за прогон. Счётчик dropped_nb логируется (cian: %s [...] dropped_nb=%d ...), но в scrape_runs.counters не сохраняется, а логи контейнеров за 17.08 (последний cian_full_load) уже не доступны — docker logs --since 120h не находит ни одной строки «cian:» ни в одном контейнере. Без прогона или без сохранения счётчика число назвать нельзя, и я его не называю.

Побочно: то, что dropped_nb считается и печатается, но не персистится, — сам по себе пробел наблюдаемости. Он бы дал ответ на этот вопрос из истории, без всяких проб.

Предлагаю минимальный обратимый шаг

Сделать secondary_only параметром расписания (scrape_schedules.default_params), сохранив нынешний дефолт True, и заодно сохранять dropped_nb в счётчики прогона. Тогда:

  • поведение прода не меняется ни на строку, пока никто не тронет параметр;
  • включение и откат становятся правкой одной ячейки в БД, а не деплоем;
  • первый же прогон с false даст точное число новых строк, а dropped_nb в счётчиках — цену вопроса задним числом.

Готовлю PR в таком виде, если возражений нет. Решение «включать ли» остаётся за владельцем — при охвате 11.7 % это ещё и предусловие для расширения деактивации на novostroyki (#2994).

## Поправка к моему же комментарию выше: корень №1 НЕ закрыт В предыдущем сообщении я написал, что флага `secondary_only` в исходниках больше нет. **Это неверно.** Мой grep смотрел только в `tradein-mvp/backend/`, а флаг переехал в пакет `tradein-mvp/packages/scraper-kit/`. Поведение не менялось вовсе — изменилось только место. ``` packages/scraper-kit/src/scraper_kit/orchestration/pipeline.py:3328 secondary_only=True, ← хардкод в run_cian_full_load, не параметр расписания packages/scraper-kit/src/scraper_kit/providers/cian/serp.py:367 secondary_only: bool = True, ← дефолт ``` Прошу прощения за шум: вывод про охват от этого не меняется (11.7 % за месяц — замер по данным), но причина названа была неправильно. ## Что при этом выяснилось — и это важнее Новостройки **не пропускаются при запросе. Они скачиваются, разбираются и выбрасываются последним шагом.** ```python # cian/serp.py:667-673 collected_this_bucket = len(bucket_lots) dropped_nb = 0 if secondary_only: filtered = [lot for lot in bucket_lots if lot.listing_segment != "novostroyki"] dropped_nb = collected_this_bucket - len(filtered) bucket_lots = filtered ``` Почему так, объяснено в докстроке: `object_type=1` в SERP Cian ненадёжен (~5 % выдачи), поэтому фильтруют не запросом, а по authoritative `listing_segment` из `offer.newbuilding.id` — уже после парсинга. **Следствие: включение новостроек в полный обход стоит ноль дополнительных HTTP-запросов.** Проверил, что это не догадка: проба бакета берёт `totalOffers` из Redux-состояния SERP, и дробление считается как `pages_needed = ceil(totalOffers / offers_per_page)`. `totalOffers` включает обе категории — то есть страницы с новостройками **уже скачиваются и уже оплачены** и лимитом страниц, и антибан-бюджетом. Выбрасывается только результат разбора. То есть это не проблема ёмкости сбора и не «Cian не отдаёт». Это однострочная политика. ## Чего я НЕ смог измерить Сколько именно строк выбрасывается за прогон. Счётчик `dropped_nb` **логируется** (`cian: %s [...] dropped_nb=%d ...`), но в `scrape_runs.counters` не сохраняется, а логи контейнеров за 17.08 (последний `cian_full_load`) уже не доступны — `docker logs --since 120h` не находит ни одной строки «cian:» ни в одном контейнере. Без прогона или без сохранения счётчика число назвать нельзя, и я его не называю. Побочно: то, что `dropped_nb` считается и печатается, но не персистится, — сам по себе пробел наблюдаемости. Он бы дал ответ на этот вопрос из истории, без всяких проб. ## Предлагаю минимальный обратимый шаг Сделать `secondary_only` параметром расписания (`scrape_schedules.default_params`), сохранив нынешний дефолт `True`, и заодно сохранять `dropped_nb` в счётчики прогона. Тогда: - поведение прода **не меняется** ни на строку, пока никто не тронет параметр; - включение и откат становятся правкой одной ячейки в БД, а не деплоем; - первый же прогон с `false` даст точное число новых строк, а `dropped_nb` в счётчиках — цену вопроса задним числом. Готовлю PR в таком виде, если возражений нет. Решение «включать ли» остаётся за владельцем — при охвате 11.7 % это ещё и предусловие для расширения деактивации на novostroyki (#2994).
Owner

Ревизия открытых задач 2026-08-30. Проверено в коде на forgejo/main — сделано, закрываю.

secondary_only стал параметром расписания вместо хардкода в pipeline.py. tests/test_1781_secondary_only_param.py.

Ревизия открытых задач 2026-08-30. Проверено в коде на forgejo/main — сделано, закрываю. `secondary_only` стал параметром расписания вместо хардкода в pipeline.py. tests/test_1781_secondary_only_param.py.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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#1781
No description provided.