fix(tradein/cian): просмотры перестают теряться на int() по фразе (#2669) #2705

Merged
bot-backend merged 1 commit from fix/2669-cian-views into main 2026-08-06 06:46:19 +00:00
Collaborator

Summary

Cian отдаёт просмотры фразой, а не числом: stats.totalViewsFormattedString = "146 просмотров, 8 за сегодня" (дословно из сохранённого ответа tests/fixtures/cian_flat_330982715.html). _parse_views звала на ней int()ValueError → свой же exceptNone.

Замер на проде (только чтение):

источник detail-обогащено из них с views_total
avito 12 352 12 350
domklik 6 296 6 296
yandex 1 210 1 146 (views_total_yandex)
cian 1 571 0

listings.views_total пуст у всех 21 799 cian-строк. У других площадок той же потери нет — дефект ровно один.

Что сделано

  • Разбор фразы регулярками вместо int(): число до слова «просмотр» → views_total, число перед «за сегодня» → views_today.
  • views_today берётся из хвоста ТОЙ ЖЕ фразы: отдельного ключа todayViewsFormattedString в ответе нет (в фикстуре его нет вовсе — прежний вызов по нему всегда возвращал None). Колонка listings.views_today существует с 019_listings_alter_cian.sql и уже пишется тем же UPDATE — новых полей не заводил.
  • Тест на каждой встреченной форме: фраза из фикстуры дословно, "1 просмотр", "0 просмотров", "1 234 просмотра" с обычным пробелом и с NBSP, narrow-NBSP, голое "1 234", пусто/None/строка без цифр → (None, None). Плюс прогон настоящего сохранённого HTML через настоящий fetch_detail.

Бэкфилл невозможен — доказательство

SELECT count(*) FILTER (WHERE raw_payload::text LIKE '%ViewsFormattedString%'),
       count(*) FILTER (WHERE raw_payload ? 'stats'), count(*)
FROM listings WHERE source='cian';
-- 0 | 0 | 21799

и то же самое по listing_sources.raw_payload — 0 вхождений по всем пяти источникам. Исходной фразы в БД нет, восстанавливать не из чего. Миграция не заводится.

Test plan

  • pytest tests/test_cian_views_parse.py — 13 passed
  • соседние: test_cian_bti_house_persist / test_snapshot_writer / test_cian_city_sweep / test_cian_history_backfill_listings_rollback / scrapers/test_domclick_detail / test_yandex_detail — 144 passed
  • ruff 0.7.4 check + format
  • Прод-эффект непроверяем и заблокирован выше по потоку: cian-detail стоит с 22 июля (max(detail_enriched_at)=2026-07-22, HTTP 403 — протухшие куки, #2700). Новых строк не появится, пока #2700 не разблокируют. Верификация здесь = маркер кода в живом контейнере + тест на сохранённом ответе, НЕ «ноль в колонке».

Refs #2669

## Summary Cian отдаёт просмотры фразой, а не числом: `stats.totalViewsFormattedString` = `"146 просмотров, 8 за сегодня"` (дословно из сохранённого ответа `tests/fixtures/cian_flat_330982715.html`). `_parse_views` звала на ней `int()` → `ValueError` → свой же `except` → `None`. Замер на проде (только чтение): | источник | detail-обогащено | из них с views_total | |---|---:|---:| | avito | 12 352 | 12 350 | | domklik | 6 296 | 6 296 | | yandex | 1 210 | 1 146 (`views_total_yandex`) | | **cian** | **1 571** | **0** | `listings.views_total` пуст у всех 21 799 cian-строк. У других площадок той же потери нет — дефект ровно один. ## Что сделано - Разбор фразы регулярками вместо `int()`: число до слова «просмотр» → `views_total`, число перед «за сегодня» → `views_today`. - `views_today` берётся из хвоста ТОЙ ЖЕ фразы: отдельного ключа `todayViewsFormattedString` в ответе нет (в фикстуре его нет вовсе — прежний вызов по нему всегда возвращал `None`). Колонка `listings.views_today` существует с `019_listings_alter_cian.sql` и уже пишется тем же UPDATE — новых полей не заводил. - Тест на каждой встреченной форме: фраза из фикстуры дословно, `"1 просмотр"`, `"0 просмотров"`, `"1 234 просмотра"` с обычным пробелом и с NBSP, narrow-NBSP, голое `"1 234"`, пусто/None/строка без цифр → `(None, None)`. Плюс прогон настоящего сохранённого HTML через настоящий `fetch_detail`. ## Бэкфилл невозможен — доказательство ```sql SELECT count(*) FILTER (WHERE raw_payload::text LIKE '%ViewsFormattedString%'), count(*) FILTER (WHERE raw_payload ? 'stats'), count(*) FROM listings WHERE source='cian'; -- 0 | 0 | 21799 ``` и то же самое по `listing_sources.raw_payload` — 0 вхождений по всем пяти источникам. Исходной фразы в БД нет, восстанавливать не из чего. Миграция не заводится. ## Test plan - [x] `pytest tests/test_cian_views_parse.py` — 13 passed - [x] соседние: `test_cian_bti_house_persist / test_snapshot_writer / test_cian_city_sweep / test_cian_history_backfill_listings_rollback / scrapers/test_domclick_detail / test_yandex_detail` — 144 passed - [x] ruff 0.7.4 check + format - [ ] Прод-эффект **непроверяем и заблокирован выше по потоку**: cian-detail стоит с 22 июля (`max(detail_enriched_at)=2026-07-22`, HTTP 403 — протухшие куки, #2700). Новых строк не появится, пока #2700 не разблокируют. Верификация здесь = маркер кода в живом контейнере + тест на сохранённом ответе, НЕ «ноль в колонке». Refs #2669
bot-backend added 1 commit 2026-08-06 06:40:20 +00:00
fix(tradein/cian): просмотры перестают теряться на int() по фразе (#2669)
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 / backend-tests (pull_request) Successful in 2m57s
9195f2aef4
Cian отдаёт просмотры не числом, а фразой:
  "stats":{"totalViewsFormattedString":"146 просмотров, 8 за сегодня"}
(дословно из tests/fixtures/cian_flat_330982715.html). `_parse_views` звала
на ней int() → ValueError → свой же except → None. Прод: views_total пуст
у ВСЕХ 21 799 cian-листингов, включая 1 571 detail-обогащённый. У соседей
потери нет (avito 12 350/12 352, domklik 6 296/6 296, yandex 1 146/1 210) —
дефект ровно один и только у Cian.

Разбор теперь достаёт из фразы оба числа: «за сегодня» живёт в её хвосте,
отдельного ключа todayViewsFormattedString в ответе нет (в фикстуре его
нет вовсе — прежний вызов по нему всегда возвращал None). Колонка
listings.views_today существует с 019_listings_alter_cian.sql и уже
пишется тем же UPDATE — новых полей не заводим.

Формы, покрытые тестами: фраза из фикстуры дословно, "1 просмотр",
"0 просмотров", "1 234 просмотра" с обычным пробелом и с NBSP,
narrow-NBSP, голое "1 234", пусто/None/строка без цифр → (None, None).

Бэкфилл невозможен: исходной фразы в БД нет — 0 вхождений
ViewsFormattedString в listings.raw_payload (21 799 строк, ключа stats нет
ни у одной) и 0 в listing_sources.raw_payload по всем пяти источникам.
Миграция не заводится.

Эффект на проде появится только после возобновления cian-detail: парсер
стоит с 22 июля (max(detail_enriched_at)=2026-07-22, HTTP 403 из-за
протухших кук, #2700). До этого проверка правки — только на сохранённом
ответе.

Refs #2669
bot-backend merged commit bc5233de15 into main 2026-08-06 06:46:19 +00:00
bot-backend deleted branch fix/2669-cian-views 2026-08-06 06:46:19 +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#2705
No description provided.