Скрапер: newbuilding-провайдеры без config больше не уходят мимо пула прокси, IMV Авито строит сессию общей фабрикой #3562
No reviewers
Labels
No labels
Fable 5 ревью
GG-форсайт
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
feedback/max
generative
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
ИРД
вторичка
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#3562
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/kit-providers-config"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #2385
Closes #2386
Два хвоста миграции scraper_kit на общие фабрики (#2352, эпик F4). На проде поведение не меняется: все боевые вызовы уже передают config, параметры curl-сессии IMV те же. Правки убирают две ловушки, на которых уже теряли дни.
#2385: config в newbuilding-провайдерах Циана и Яндекса теперь обязателен
Что было.
fetch_newbuilding,resolve_cian_zhk_url_via_search,YandexNewbuildingScraperиresolve_yandex_jk_slugпринималиconfig: ScraperConfig | None = None. Без config браузерный фетчер строился в обход фабрики какBrowserFetcher(endpoint=None), а резолвер Циана получал сессию без прокси и без пула. На проде это до сих пор так: вtradein-scraperв/app/packages/scraper-kit/.../cian/newbuilding.pyна строках 217 и 886 стоит| None = None.Почему это важно. Сейчас config передают все боевые вызовы: admin.py (2 места), cian_history_backfill, newbuilding_enrich_backfill, yandex_newbuilding_sweep и pipeline.run_cian_city_sweep. Но любой новый вызов без config молча ушёл бы на env-узел сайдкара мимо пула. Именно так потеряли 8 суток в #2767 и сломались в #3197.
Что сделано. config стал обязательным keyword-only аргументом. Запасные ветки
else: BrowserFetcher(source=..., endpoint=None)удалены вместе с# type: ignore[arg-type]и импортомBrowserFetcher. Фетчер теперь строит толькоbuild_browser_fetcher. Устаревший комментарий «admin.py зовёт без config» тоже удалён. Боевой код не трогали: config передают все его вызовы.Тесты. Четыре кейса
*_without_configждалиendpoint=None/proxy_url=None, то есть закрепляли ту самую ловушку. Вместо них один параметризованный тестtest_newbuilding_entrypoints_refuse_to_run_without_config: вызов без config падаетTypeErrorещё до сети, а шпионы на_base.BrowserFetcherиbuild_curl_cffi_sessionне вызваны ни разу. Остальные тестовые вызовы получили config. Четыре теста #2860 и два в test_scraper_proxy.py теперь подменяютBrowserFetcherв фабрике, а не в модуле провайдера.#2386: IMV Авито строит curl-сессию общей фабрикой
Что было.
avito/imv.pyсам собиралcurl_cffi.requests.AsyncSessionс третьей копией document-заголовков (_DOC_HEADERS). Комментарий NB #2361 F4a объяснял причину: parity-тесты патчилиcurl_cffi.requests.AsyncSession, а_baseимпортирует класс на уровне модуля, так что патч до фабрики не долетал.Почему вариант B, а не A. В issue рекомендован вариант A: живой lookup
curl_cffi.requests.AsyncSessionв_base. Но в кодовой базе уже прижился патчscraper_kit.providers._base.AsyncSession: test_scraper_proxy.py:293,321, test_pipeline_browser_routing.py:246, test_kit_serp_proxy_pool.py:198-201. Вариант A убрал бы атрибут_base.AsyncSessionи сломал бы эти тесты. Вариант B — это следование уже принятому соглашению, а не новое.Что сделано. Сессия строится через
build_document_session(proxy_url=_proxy_url, timeout=_HTTP_TIMEOUT_SEC)._DOC_HEADERSудалён, warm-up берётDOCUMENT_HEADERS. До правки проверил:_DOC_HEADERS == DOCUMENT_HEADERS, совпадает и порядок ключей. Мёртвыйtry/except ImportErrorубран: curl_cffi и так импортируется на уровне модуля через_base. Удалён и NB-комментарий.Тесты. test_avito_imv_kit_parity.py (2 места) и test_3386_estimator_imv_proxy_pool.py (2 места) теперь патчат
scraper_kit.providers._base.AsyncSession. Ассерты не ослаблены. Тест с config теперь проверяет ещёimpersonate == DEFAULT_IMPERSONATE,timeout == 25иheaders == DOCUMENT_HEADERS, то есть что параметры сессии остались прежними. Без смены цели патча оба теста в test_3386 остались бы зелёными вхолостую: патч перестал бы перехватывать конструктор.Прогоны
DATABASE_URL=… uv run python -m pytest tests/ -q -p no:cacheprovider): 6225 passed, 42 skipped, rc=0 (126 с). Пакет scraper-kit своих тестов не содержит:uv run python -m pytest -qвpackages/scraper-kit→ «no tests ran», rc=5; его тесты живут вbackend/tests.uv run ruff check app testsи ruff по изменённым файлам кита: All checks passed.ruff format --checkпо изменённым файлам: чисто.Фальсификация
#2385. Исходники
cian/newbuilding.pyиyandex/newbuilding.pyподменены версиями из origin/main (копия фикса лежала в scratchpad, после прогона восстановлена,diff -qчистый). Прогонtests/test_scraper_kit_newbuilding_endpoint.pyдал4 failed, 8 passed:#2386, мутация 1. В
build_document_session(...)вместо_proxy_urlподставленproxy_url=None. Результат1 failed, 1 passed:#2386, мутация 2.
imv.pyвозвращён к версии из origin/main со своей сессией. Результат2 failed: патч_base.AsyncSessionне перехватывает старую конструкцию, запрос уходит в реальную сеть (Could not resolve proxy: test-proxy.local/IMVTransientError). Значит, тесты действительно закрепляют переезд на фабрику. После обеих мутаций исходник восстановлен,diff -qчистый.Приёмка на проде (после деплоя; проверить до 24.09.2026)
docker exec tradein-scraper grep -c "config: ScraperConfig | None" /app/packages/scraper-kit/src/scraper_kit/providers/cian/newbuilding.py /app/packages/scraper-kit/src/scraper_kit/providers/yandex/newbuilding.py. Ожидаю0и0; на текущем проде2и2, так что маркер различает версии.SELECT id, status, error FROM scrape_runs WHERE source IN ('cian_history_backfill','yandex_newbuilding_sweep','newbuilding_enrich_backfill') AND started_at > <время деплоя> AND coalesce(error,'') ILIKE '%required keyword-only argument%'. Ожидаю 0 строк. Если строка есть, значит пропущен вызывающий, и это ровно то, что правка должна ловить громко.docker exec tradein-backend grep -c "CffiAsyncSession\|_DOC_HEADERS" /app/packages/scraper-kit/src/scraper_kit/providers/avito/imv.py. Ожидаю0, сейчас больше нуля.tradein-backendпослеimv: cache MISSидётIMV warm-up GET /evaluation/realty → HTTP <код>без TypeError/AttributeError. Сессия построена фабрикой и сходила в сеть. Оговорка: за 7 суток до правки вavito_imv_evaluations0 записей, а в логах с 06:09 17.09 нет ни одногоimv: cache MISS. Путь почти не исполняется, так что по записям эффект не измерить. Отсутствие новых ошибок — единственный честный критерий.#3191 в этот PR не вошёл
Группа задач включала #3191 («вечно недогруженные карточки Яндекса обрывают прогон»). Не сделан, причины ниже.
offer/7338505269989347072иoffer/67398737598609920, забранная отсюда, весит 381 002 и 380 759 байт, как на проде. В ней нетINITIAL_STATE, нет<title>и ни разу не встречается запрошенный id. Живая карточка для сравнения: 3,3 МБ, id встречается 29 раз. Это не «недогруженная карточка», а оболочка без объявления (похоже на снятое объявление;last_seen_atвсех пяти застыл на 12.09 15:02).is_active=FALSE, как avito 404. Второе — новая запись в данные, её должен одобрить владелец.🤖 Generated with Claude Code