fix(tradein): sweep shared-session (SERP+detail) через mobile proxy (#623) (#805)
All checks were successful
Deploy Trade-In / test (push) Successful in 26s
Deploy Trade-In / build-backend (push) Successful in 42s
Deploy Trade-In / changes (push) Successful in 5s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / deploy (push) Successful in 34s

Co-authored-by: bot-backend <bot-backend@gendsgn.local>
Co-committed-by: bot-backend <bot-backend@gendsgn.local>
This commit is contained in:
bot-backend 2026-05-30 18:07:37 +00:00 committed by bot-reviewer
parent a9e4aecb54
commit 950d0f60de
2 changed files with 35 additions and 0 deletions

View file

@ -34,6 +34,7 @@ from curl_cffi.requests import AsyncSession
from sqlalchemy import text from sqlalchemy import text
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.config import settings
from app.services import scrape_runs from app.services import scrape_runs
from app.services.scrapers.avito import AvitoScraper from app.services.scrapers.avito import AvitoScraper
from app.services.scrapers.avito_detail import fetch_detail, save_detail_enrichment from app.services.scrapers.avito_detail import fetch_detail, save_detail_enrichment
@ -66,6 +67,19 @@ _CHROME_HEADERS = {
} }
def _avito_proxies() -> dict[str, str] | None:
"""#623: mobile-proxy egress для shared-сессии sweep'а.
КРИТИЧНО: sweep создаёт собственную shared AsyncSession и присваивает её
`scraper._cffi`, перезатирая проксированную сессию из `AvitoScraper.__aenter__`.
Через эту же сессию идёт и SERP, и detail-энричмент (fetch_detail). Без прокси
detail-страницы летят с datacenter-IP HTTP 429 весь run mark_banned.
Пусто (env не задан) прямое подключение (dev/no-op).
"""
url = settings.avito_proxy_url
return {"http": url, "https": url} if url else None
# ── Result dataclasses ────────────────────────────────────────── # ── Result dataclasses ──────────────────────────────────────────
@ -133,6 +147,7 @@ async def run_avito_pipeline(
impersonate="chrome120", impersonate="chrome120",
timeout=25, timeout=25,
headers=_CHROME_HEADERS, headers=_CHROME_HEADERS,
proxies=_avito_proxies(),
) )
try: try:
@ -351,6 +366,7 @@ async def run_avito_city_sweep(
impersonate="chrome120", impersonate="chrome120",
timeout=25, timeout=25,
headers=_CHROME_HEADERS, headers=_CHROME_HEADERS,
proxies=_avito_proxies(),
) as session: ) as session:
try: try:
for idx, (lat, lon, name) in enumerate(_anchors, start=1): for idx, (lat, lon, name) in enumerate(_anchors, start=1):

View file

@ -411,3 +411,22 @@ async def test_fetch_around_blocked_when_rotation_exhausted(
await scraper.fetch_around(56.84, 60.60, 1000, pages=1) await scraper.fetch_around(56.84, 60.60, 1000, pages=1)
assert rot.await_count == 1 # одна попытка ротации, потом сдаёмся assert rot.await_count == 1 # одна попытка ротации, потом сдаёмся
# ── #623 sweep-pipeline proxy (SERP+detail через mobile proxy) ───
def test_avito_proxies_helper(monkeypatch: pytest.MonkeyPatch) -> None:
"""_avito_proxies: None без env, dict {http,https} при заданном AVITO_PROXY_URL.
Регрессия: sweep создаёт shared-сессию, через неё идут и SERP, и detail
без прокси detail летит с datacenter-IP 429 run banned (#623 followup).
"""
from app.services import scrape_pipeline as sp
monkeypatch.setattr(sp.settings, "avito_proxy_url", None)
assert sp._avito_proxies() is None
url = "http://u:p@mproxy.site:16928"
monkeypatch.setattr(sp.settings, "avito_proxy_url", url)
assert sp._avito_proxies() == {"http": url, "https": url}