Авито: добор карточек больше не сжигает 1600 попыток на упавшем сайдкаре и везде ходит браузером #3560

Merged
bot-backend merged 4 commits from fix/avito-detail-backfill into main 2026-09-17 09:23:22 +00:00
4 changed files with 53 additions and 13 deletions
Showing only changes of commit 19b6081242 - Show all commits

View file

@ -1194,14 +1194,6 @@ class Settings(BaseSettings):
# False = старое поведение (без фильтра). ENV: AVITO_SERP_EKB_ONLY.
avito_serp_ekb_only: bool = Field(default=True, validation_alias="AVITO_SERP_EKB_ONLY")
# ── Yandex SERP cookies (#801/T4) ───────────────────────────────────────
# Путь к JSON-файлу с cookies браузера (формат: [{name, value, ...}, ...]).
# Если задан и файл существует — cookies передаются в curl_cffi-сессию при
# Yandex SERP-запросах; снижает вероятность captcha на datacenter IP.
# Пусто / файл не найден = запросы без cookies (не падаем, только warning).
# ENV: YANDEX_COOKIES_FILE.
yandex_cookies_file: str | None = None
# ── #639: Cian browser auto-login (Variant B) ────────────────────────────
# Провалидировано вживую 2026-05-31: email+пароль, без SMS/капчи. Флоу 2-шаговый
# (после 1-го сабмита экран «Введите пароль» → повтор). Селекторы env-overridable.

View file

@ -0,0 +1,45 @@
"""#3182: режим detail-добора Авито не зависит от того, какой контейнер взял задачу.
Добор исполняют два процесса образа backend: планировщик (tradein-scraper) и ручка
админки scrape_avito_detail_backfill (tradein-backend). Хостовый backend/.env.runtime
задаёт AVITO_DETAIL_BACKFILL_USE_CURL=true, а environment сервиса перекрывает env_file
до правки "false" стояло только у scraper, и printenv на проде давал scraper=false,
backend=true. Проверка по значению: разобранный compose, каждый сервис образа backend.
"""
import os
from pathlib import Path
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")
import pytest
import yaml
from app.core.config import Settings
_COMPOSE = Path(__file__).resolve().parents[2] / "docker-compose.prod.yml"
def test_every_backend_image_service_pins_browser_transport() -> None:
services = yaml.safe_load(_COMPOSE.read_text())["services"]
backend_image = {
name: svc
for name, svc in services.items()
if "gendesign-tradein-backend" in svc.get("image", "")
}
# Контроль разбора: без него пустой словарь дал бы зелёный тест ни о чём.
assert {"backend", "scraper"} <= set(backend_image), sorted(backend_image)
values = {
name: (svc.get("environment") or {}).get("AVITO_DETAIL_BACKFILL_USE_CURL")
for name, svc in backend_image.items()
}
assert values == dict.fromkeys(backend_image, "false"), values
def test_leftover_yandex_cookies_env_is_ignored(monkeypatch: pytest.MonkeyPatch) -> None:
"""YANDEX_COOKIES_FILE удалён из Settings (никто не читал); хостовый .env.runtime
его ещё несёт старт не должен падать, а поле не должно всплыть обратно."""
monkeypatch.setenv("YANDEX_COOKIES_FILE", "/app/yandex_cookies.json")
assert not hasattr(Settings(_env_file=None), "yandex_cookies_file")

View file

@ -40,10 +40,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
import pytest
def _mock_settings(
scraper_proxy_url: str | None,
yandex_cookies_file: str | None = None,
) -> SimpleNamespace:
def _mock_settings(scraper_proxy_url: str | None) -> SimpleNamespace:
"""Minimal settings stand-in with fields read by proxy helpers and scraper __aenter__."""
return SimpleNamespace(
scraper_proxy_url=scraper_proxy_url,
@ -52,7 +49,6 @@ def _mock_settings(
# removed — dead mobileproxy accounts). Mirrored here for test compat.
cian_proxy_url=scraper_proxy_url,
yandex_proxy_url=scraper_proxy_url,
yandex_cookies_file=yandex_cookies_file,
)

View file

@ -340,6 +340,11 @@ services:
# деградация. У scraper те же значения с #2126/#2160.
USE_PROXY_POOL_CURL: "true"
USE_PROXY_POOL_BROWSER: "true"
# #3182: ручка админки scrape_avito_detail_backfill исполняет добор В ЭТОМ
# процессе. Хостовый backend/.env.runtime задаёт true (curl не проходит QRATOR
# PoW, см. scraper ниже) — environment перекрывает env_file. Значение одно на
# все сервисы образа backend, чтобы режим не зависел от того, кто взял задачу.
AVITO_DETAIL_BACKFILL_USE_CURL: "false"
depends_on:
browser:
condition: service_started
@ -453,6 +458,8 @@ services:
# Settings()-объект образа где-то читает флаг при импорте (defense-in-depth,
# аналогично backend). tgbot_main.py не должен зависеть от этого значения.
SCHEDULER_ENABLE: "false"
# #3182: то же значение, что у backend/scraper (см. комментарий у backend).
AVITO_DETAIL_BACKFILL_USE_CURL: "false"
depends_on:
postgres:
condition: service_healthy