refactor(tradein/tasks): avito_detail_backfill build_warmed_session на kit + config= (#2397 Part D1, #2330)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 7s
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 58s
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 7s
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 58s
This commit is contained in:
parent
eafa305ac8
commit
36c451e155
2 changed files with 61 additions and 16 deletions
|
|
@ -39,6 +39,8 @@ from scraper_kit.browser_fetcher import BrowserFetcher
|
||||||
# зависимость от scrape_pipeline.py перед его удалением.
|
# зависимость от scrape_pipeline.py перед его удалением.
|
||||||
from scraper_kit.providers._base import DOCUMENT_HEADERS, http_proxies
|
from scraper_kit.providers._base import DOCUMENT_HEADERS, http_proxies
|
||||||
from scraper_kit.providers.avito.detail import (
|
from scraper_kit.providers.avito.detail import (
|
||||||
|
_AVITO_WARM_SEARCH_URL,
|
||||||
|
build_warmed_session,
|
||||||
fetch_detail,
|
fetch_detail,
|
||||||
research_in_session,
|
research_in_session,
|
||||||
save_detail_enrichment,
|
save_detail_enrichment,
|
||||||
|
|
@ -52,20 +54,14 @@ from app.core.shutdown import shutdown_requested
|
||||||
from app.services import scrape_runs as runs_mod
|
from app.services import scrape_runs as runs_mod
|
||||||
from app.services.scraper_adapters import RealScraperConfig
|
from app.services.scraper_adapters import RealScraperConfig
|
||||||
|
|
||||||
# _AVITO_WARM_SEARCH_URL/build_warmed_session остаются legacy (#2310): kit's
|
# #2397 Part D1 (#2330 закрыт): _AVITO_WARM_SEARCH_URL/build_warmed_session больше
|
||||||
# scraper_kit.providers.avito.detail.build_warmed_session() строит сессию через
|
# НЕ legacy — kit's scraper_kit.providers.avito.detail.build_warmed_session() теперь
|
||||||
# _build_detail_session() БЕЗ config-параметра (сигнатура вообще не принимает
|
# принимает config: ScraperConfig | None (Strangler-инъекция #2330) и пробрасывает
|
||||||
# ScraperConfig — в отличие от fetch_detail, здесь нет caller-side способа
|
# его в _build_detail_session() для settings.scraper_proxy_url (sticky МГТС-прокси),
|
||||||
# прокинуть прокси). Мигрировав этот импорт, warm-batch curl-путь (use_curl=True,
|
# зеркаля fetch_detail. Оба call site'а ниже передают config=RealScraperConfig() —
|
||||||
# ДЕФОЛТ прод-режима, avito_detail_backfill_use_curl=True) молча перестал бы
|
# без него warm-batch curl-путь (use_curl=True, ДЕФОЛТ прод-режима,
|
||||||
# использовать settings.scraper_proxy_url (sticky МГТС-прокси) — прямое
|
# avito_detail_backfill_use_curl=True) молча терял бы backconnect (прямое
|
||||||
# datacenter-подключение вместо backconnect. Тот же класс бага, что #2322
|
# datacenter-подключение вместо sticky-прокси), тот же класс бага что #2322/#2310.
|
||||||
# (недостающая инъекция конфига в scraper_kit provider-функции), но другая
|
|
||||||
# функция — заведён как отдельный follow-up, НЕ #2322. См. отчёт issue #2310.
|
|
||||||
from app.services.scrapers.avito_detail import (
|
|
||||||
_AVITO_WARM_SEARCH_URL,
|
|
||||||
build_warmed_session,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -174,7 +170,10 @@ async def run_avito_detail_backfill(
|
||||||
# фикс. exit-IP, per-connection ротации нет (rebuild != новый IP); on-block —
|
# фикс. exit-IP, per-connection ротации нет (rebuild != новый IP); on-block —
|
||||||
# cooldown + in-session re-search, не дискард сессии.
|
# cooldown + in-session re-search, не дискард сессии.
|
||||||
own_session = True
|
own_session = True
|
||||||
session = await build_warmed_session()
|
# config= обязателен: #2397 Part D1 — kit build_warmed_session() без него
|
||||||
|
# молча теряет settings.scraper_proxy_url (sticky МГТС-прокси), см. модуль-
|
||||||
|
# ный комментарий у импорта выше.
|
||||||
|
session = await build_warmed_session(config=RealScraperConfig())
|
||||||
elif not use_curl:
|
elif not use_curl:
|
||||||
# curl_cffi legacy path (scraper_fetch_mode="curl_cffi", use_curl=False):
|
# curl_cffi legacy path (scraper_fetch_mode="curl_cffi", use_curl=False):
|
||||||
# строим shared сессию через auv, как делает run_avito_city_sweep (kit).
|
# строим shared сессию через auv, как делает run_avito_city_sweep (kit).
|
||||||
|
|
@ -296,8 +295,12 @@ async def run_avito_detail_backfill(
|
||||||
# #1950: bound сетевой re-warm; строим НОВУЮ сессию до close старой — на
|
# #1950: bound сетевой re-warm; строим НОВУЮ сессию до close старой — на
|
||||||
# timeout/fail сохраняем текущую рабочую сессию (graceful degrade).
|
# timeout/fail сохраняем текущую рабочую сессию (graceful degrade).
|
||||||
try:
|
try:
|
||||||
|
# config= обязателен здесь тоже (тот же footgun, что и в setup-
|
||||||
|
# вызове build_warmed_session выше) -- иначе периодический re-warm
|
||||||
|
# молча пересобрал бы сессию БЕЗ sticky-прокси после первого раза.
|
||||||
new_session = await asyncio.wait_for(
|
new_session = await asyncio.wait_for(
|
||||||
build_warmed_session(), timeout=warm_timeout_s
|
build_warmed_session(config=RealScraperConfig()),
|
||||||
|
timeout=warm_timeout_s,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await session.close()
|
await session.close()
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,48 @@ async def test_backfill_processes_snapshot_to_completion() -> None:
|
||||||
assert isinstance(mock_scraper_cls.call_args.args[0], RealScraperConfig)
|
assert isinstance(mock_scraper_cls.call_args.args[0], RealScraperConfig)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_backfill_build_warmed_session_receives_config() -> None:
|
||||||
|
"""#2397 Part D1 / #2330 regression guard: kit's build_warmed_session() now accepts
|
||||||
|
config=ScraperConfig and silently drops settings.scraper_proxy_url (sticky МГТС-прокси)
|
||||||
|
-- NO crash, just a proxy-less session -- unless config=RealScraperConfig() is passed
|
||||||
|
at the call site. use_curl=True is the prod-default path (avito_detail_backfill_use_curl),
|
||||||
|
so this is the setup-time build_warmed_session() call (~line 177). assert_awaited_once()
|
||||||
|
alone wouldn't catch someone dropping the kwarg later (mirrors the fetch_detail config=
|
||||||
|
guard above, lines 141-148)."""
|
||||||
|
from app.services.scraper_adapters import RealScraperConfig
|
||||||
|
|
||||||
|
snapshot = _make_snapshot(1)
|
||||||
|
db = _mock_db(snapshot)
|
||||||
|
runs = MagicMock()
|
||||||
|
mock_enrichment = MagicMock()
|
||||||
|
mock_fetch = AsyncMock(return_value=mock_enrichment)
|
||||||
|
mock_save = MagicMock(return_value=True)
|
||||||
|
fake_settings = MagicMock(
|
||||||
|
scraper_fetch_mode="cffi",
|
||||||
|
avito_detail_backfill_use_curl=True,
|
||||||
|
)
|
||||||
|
with (
|
||||||
|
patch(_SETTINGS, fake_settings),
|
||||||
|
patch(_SCRAPER),
|
||||||
|
patch(_RUNS, runs),
|
||||||
|
patch(_FETCH, mock_fetch),
|
||||||
|
patch(_SAVE, mock_save),
|
||||||
|
patch(_SLEEP, new_callable=AsyncMock),
|
||||||
|
patch(_BUILD_WARM, AsyncMock(return_value=AsyncMock())) as mock_build,
|
||||||
|
):
|
||||||
|
result = await run_avito_detail_backfill(
|
||||||
|
db, run_id=15, params={"batch_size": 10, "budget_sec": 3600}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.enriched == 1
|
||||||
|
mock_build.assert_awaited_once()
|
||||||
|
_, build_call_kwargs = mock_build.call_args
|
||||||
|
assert isinstance(build_call_kwargs.get("config"), RealScraperConfig)
|
||||||
|
runs.mark_done.assert_called_once()
|
||||||
|
runs.mark_failed.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_backfill_blocked_abort_after_max_consecutive() -> None:
|
async def test_backfill_blocked_abort_after_max_consecutive() -> None:
|
||||||
"""5 consecutive AvitoBlockedError -> abort, mark_done (NOT mark_failed).
|
"""5 consecutive AvitoBlockedError -> abort, mark_done (NOT mark_failed).
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue