From 609744df9166860b1edd98ebbbae1e9c78152bfd Mon Sep 17 00:00:00 2001 From: TradeIn Deploy Date: Thu, 21 May 2026 19:43:12 +0300 Subject: [PATCH] =?UTF-8?q?fix(tradein):=20=D1=87=D0=B0=D0=BD=D0=BA=D0=BE?= =?UTF-8?q?=D0=B2=D1=8B=D0=B9=20=D0=B3=D0=B5=D0=BE=D0=BA=D0=BE=D0=B4=D0=B8?= =?UTF-8?q?=D0=BD=D0=B3=20=E2=80=94=20cron=20=D0=B1=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D1=88=D0=B5=20=D0=BD=D0=B5=20=D1=82=D0=B0=D0=B9=D0=BC=D0=B0?= =?UTF-8?q?=D1=83=D1=82=D0=B8=D1=82=D1=81=D1=8F=20(#1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit geocode_tried_at: после каждой попытки NOW(), повтор только через 7 дней. Эндпоинт чанкуется (limit=100), отдаёт remaining. cron вызывает в цикле. --- tradein-mvp/backend/app/api/v1/admin.py | 48 ++++++++++++++++--- .../backend/data/sql/005_geocode_tracking.sql | 18 +++++++ tradein-mvp/deploy/cron-scrape.sh | 18 ++++--- 3 files changed, 71 insertions(+), 13 deletions(-) create mode 100644 tradein-mvp/backend/data/sql/005_geocode_tracking.sql diff --git a/tradein-mvp/backend/app/api/v1/admin.py b/tradein-mvp/backend/app/api/v1/admin.py index 47184d76..60e8137c 100644 --- a/tradein-mvp/backend/app/api/v1/admin.py +++ b/tradein-mvp/backend/app/api/v1/admin.py @@ -124,12 +124,15 @@ def _clean_address_for_geocode(addr: str) -> str: @router.post("/geocode-missing") async def geocode_missing( db: Annotated[Session, Depends(get_db)], - limit: int = 500, + limit: int = 100, ) -> dict: """Геокодинг listings у которых нет lat/lon (используя address). - Запускать после scrape — Cian/N1 не отдают координаты, только адрес. - Nominatim rate-limited 1 req/sec → большой batch идёт ~limit секунд. + Чанк-обработка: limit=100 → ~150-200с (Nominatim 1 req/sec). cron вызывает + в цикле пока `remaining` > 0. + + geocode_tried_at: после КАЖДОЙ попытки (успех/провал) ставим NOW(). Failed- + адреса не выбираются повторно 7 дней → cron-loop завершается, не зацикливается. geom обновляется автоматически триггером listings_set_geom_trg. """ rows = db.execute( @@ -137,11 +140,13 @@ async def geocode_missing( """ SELECT id, address FROM listings - WHERE (lat IS NULL OR lon IS NULL) + WHERE lat IS NULL AND COALESCE(address, '') != '' AND address NOT LIKE '%(Avito)%' AND address NOT LIKE '%(N1)%' - ORDER BY scraped_at DESC + AND (geocode_tried_at IS NULL + OR geocode_tried_at < NOW() - interval '7 days') + ORDER BY geocode_tried_at NULLS FIRST, scraped_at DESC LIMIT :limit """ ), @@ -154,14 +159,43 @@ async def geocode_missing( clean = _clean_address_for_geocode(row["address"]) result = await geocode(clean, db) if result is None: + # Помечаем что пробовали — иначе ретрай на каждом cron. + db.execute( + text("UPDATE listings SET geocode_tried_at = NOW() WHERE id = :id"), + {"id": row["id"]}, + ) + db.commit() skipped += 1 continue # geom пересчитается триггером из lat/lon. db.execute( - text("UPDATE listings SET lat = :lat, lon = :lon WHERE id = :id"), + text( + "UPDATE listings SET lat = :lat, lon = :lon, " + "geocode_tried_at = NOW() WHERE id = :id" + ), {"lat": result.lat, "lon": result.lon, "id": row["id"]}, ) db.commit() geocoded += 1 - return {"checked": len(rows), "geocoded": geocoded, "skipped": skipped} + # Сколько ещё не-геокоженных адресов ждут (для cron-loop'а). + remaining = db.execute( + text( + """ + SELECT count(*) FROM listings + WHERE lat IS NULL + AND COALESCE(address, '') != '' + AND address NOT LIKE '%(Avito)%' + AND address NOT LIKE '%(N1)%' + AND (geocode_tried_at IS NULL + OR geocode_tried_at < NOW() - interval '7 days') + """ + ) + ).scalar() + + return { + "checked": len(rows), + "geocoded": geocoded, + "skipped": skipped, + "remaining": int(remaining or 0), + } diff --git a/tradein-mvp/backend/data/sql/005_geocode_tracking.sql b/tradein-mvp/backend/data/sql/005_geocode_tracking.sql new file mode 100644 index 00000000..75e68063 --- /dev/null +++ b/tradein-mvp/backend/data/sql/005_geocode_tracking.sql @@ -0,0 +1,18 @@ +-- Trade-In MVP — отслеживание попыток геокодинга. +-- +-- Проблема: geocode-missing раньше выбирал ВСЕ строки с lat IS NULL. Адреса, +-- которые Nominatim не может распознать (~12%), выбирались на каждом прогоне +-- → бесконечный ретрай + cron-таймаут. +-- +-- Решение: колонка geocode_tried_at. После КАЖДОЙ попытки (успех или провал) +-- ставим NOW(). geocode-missing выбирает только не-пробованные ИЛИ +-- пробованные >7 дней назад. Failed-адреса ретраятся раз в неделю, не каждый cron. +-- +-- Идемпотентно (ADD COLUMN IF NOT EXISTS) — безопасно при каждом деплое. + +ALTER TABLE listings ADD COLUMN IF NOT EXISTS geocode_tried_at timestamptz; + +-- Частичный индекс: ускоряет выборку «что ещё геокодить». +CREATE INDEX IF NOT EXISTS listings_geocode_pending_idx + ON listings (geocode_tried_at NULLS FIRST) + WHERE lat IS NULL; diff --git a/tradein-mvp/deploy/cron-scrape.sh b/tradein-mvp/deploy/cron-scrape.sh index 6fae5a6b..04a75a3c 100755 --- a/tradein-mvp/deploy/cron-scrape.sh +++ b/tradein-mvp/deploy/cron-scrape.sh @@ -64,11 +64,17 @@ for anchor in "${ANCHORS[@]}"; do done # Геокодинг свежих лотов без координат (Cian/N1 отдают только адрес). -# Nominatim 1 req/sec → batch ~500 идёт до ~8 мин. -log "→ geocode-missing" -geo=$(docker exec "$CONTAINER" curl -s -X POST \ - "http://localhost:8000/api/v1/admin/geocode-missing?limit=500" \ - -m 700 2>/dev/null || echo '{"error":"geocode_timeout"}') -log "← geocode-missing $geo" +# Чанками по 100 (~200с/чанк, под таймаутом), пока remaining > 0. +# Макс 15 чанков = 1500 адресов. geocode_tried_at не даёт зациклиться. +log "→ geocode-missing (чанками)" +for gi in $(seq 1 15); do + geo=$(docker exec "$CONTAINER" curl -s -X POST \ + "http://localhost:8000/api/v1/admin/geocode-missing?limit=100" \ + -m 320 2>/dev/null || echo '{"error":"geocode_timeout","remaining":0}') + log "← geocode чанк $gi: $geo" + remaining=$(echo "$geo" | grep -oE '"remaining":[0-9]+' | grep -oE '[0-9]+' || echo 0) + [ "${remaining:-0}" -eq 0 ] && break + sleep 2 +done log "=== cron-scrape done ==="