fix(tradein): чанковый геокодинг — cron не таймаутится (#1) #382
3 changed files with 71 additions and 13 deletions
|
|
@ -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),
|
||||
}
|
||||
|
|
|
|||
18
tradein-mvp/backend/data/sql/005_geocode_tracking.sql
Normal file
18
tradein-mvp/backend/data/sql/005_geocode_tracking.sql
Normal file
|
|
@ -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;
|
||||
|
|
@ -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 ==="
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue