feat(tradein/scraper-kit): shared base/utils для providers/* (#2358, Foundation) #2360

Merged
lekss361 merged 2 commits from feat/tradein-scraper-kit-base-2358 into main 2026-07-04 00:56:14 +00:00
Owner

Summary

Group F4-Foundation (эпик #2277, Group F #2309/#2352). Чисто аддитивный первый шаг структурного рефактора scraper_kit/providers/* (13 201 строк, 4 провайдера) — новый shared-модуль, существующие provider-файлы НЕ изменены (0 diff).

Найдено (реальная идентичность кода, не придуманная абстракция):

  • BrowserFetcher(...) независимо конструировался в 6+ местах (avito/serp.py:389, cian/serp.py:142, yandex/serp.py:510, cian/newbuilding.py:147, yandex/newbuilding.py:189,354, domclick/serp.py:319) — источник footgun-класса #2322/#2330.
  • curl_cffi AsyncSession construction + document-headers dict дублировались почти дословно в 5+ местах (avito/serp.py, avito/detail.py, avito/imv.py, cian/detail.py, cian/newbuilding.py, cian/session.py).

Новый модуль scraper_kit/providers/_base.py:

  • build_browser_fetcher(config: ScraperConfig, source, *, proxy_provider=None, fetch_timeout_s=None)config mandatory (без default), структурно закрывает footgun-класс на будущее.
  • build_curl_cffi_session/build_document_sessionproxy_url принимается уже резолвленным (не ScraperConfig), т.к. providers резолвят прокси по-разному — обоснование в docstring.
  • http_proxies(), DOCUMENT_HEADERS, DEFAULT_IMPERSONATE.

Retry/backoff-циклы НЕ обобщены (осознанно) — yandex/serp.py tarpit-retry, avito/serp.py page-retry и BaseScraper._http_get tenacity-декоратор семантически разные (разные исключения, разное anti-block действие); обобщение дало бы leaky abstraction. Задокументировано в модуле.

Плюс: scraper_adapters.py устаревший docstring исправлен (реально wired в _run_kit_scheduler с #2192, не "никто не использует").

Review

code-reviewer APPROVE (byte-сравнил извлечённые паттерны с оригиналами, подтвердил 0 diff в существующих provider-файлах, проверил retry-loop non-abstraction решение как обоснованное). 2 minor находки исправлены в этом PR (http_proxies() docstring — не httpx-совместим начиная с httpx 0.28) + задокументирован follow-up caveat в issue #2358 для будущих F4a/F4d (use_pool default при миграции avito/domclick).

Test plan

  • 13 новых изолированных тестов (включая inspect.signature-проверку mandatory config) — все зелёные
  • Full backend suite: 3272 passed, 1 pre-existing unrelated fail (test_search_cache_hit, #2208), 6 skipped
  • ruff check/ruff format — clean
  • 0 diff в существующих providers/{avito,cian,yandex,domclick}/*.py

Closes #2358

## Summary Group F4-Foundation (эпик #2277, Group F #2309/#2352). Чисто аддитивный первый шаг структурного рефактора `scraper_kit/providers/*` (13 201 строк, 4 провайдера) — новый shared-модуль, существующие provider-файлы НЕ изменены (0 diff). Найдено (реальная идентичность кода, не придуманная абстракция): - `BrowserFetcher(...)` независимо конструировался в 6+ местах (`avito/serp.py:389`, `cian/serp.py:142`, `yandex/serp.py:510`, `cian/newbuilding.py:147`, `yandex/newbuilding.py:189,354`, `domclick/serp.py:319`) — источник footgun-класса #2322/#2330. - curl_cffi `AsyncSession` construction + document-headers dict дублировались почти дословно в 5+ местах (`avito/serp.py`, `avito/detail.py`, `avito/imv.py`, `cian/detail.py`, `cian/newbuilding.py`, `cian/session.py`). Новый модуль `scraper_kit/providers/_base.py`: - `build_browser_fetcher(config: ScraperConfig, source, *, proxy_provider=None, fetch_timeout_s=None)` — **`config` mandatory** (без default), структурно закрывает footgun-класс на будущее. - `build_curl_cffi_session`/`build_document_session` — `proxy_url` принимается уже резолвленным (не `ScraperConfig`), т.к. providers резолвят прокси по-разному — обоснование в docstring. - `http_proxies()`, `DOCUMENT_HEADERS`, `DEFAULT_IMPERSONATE`. Retry/backoff-циклы НЕ обобщены (осознанно) — `yandex/serp.py` tarpit-retry, `avito/serp.py` page-retry и `BaseScraper._http_get` tenacity-декоратор семантически разные (разные исключения, разное anti-block действие); обобщение дало бы leaky abstraction. Задокументировано в модуле. Плюс: `scraper_adapters.py` устаревший docstring исправлен (реально wired в `_run_kit_scheduler` с #2192, не "никто не использует"). ## Review `code-reviewer` — ✅ APPROVE (byte-сравнил извлечённые паттерны с оригиналами, подтвердил 0 diff в существующих provider-файлах, проверил retry-loop non-abstraction решение как обоснованное). 2 minor находки исправлены в этом PR (`http_proxies()` docstring — не httpx-совместим начиная с httpx 0.28) + задокументирован follow-up caveat в issue #2358 для будущих F4a/F4d (use_pool default при миграции avito/domclick). ## Test plan - [x] 13 новых изолированных тестов (включая `inspect.signature`-проверку mandatory `config`) — все зелёные - [x] Full backend suite: 3272 passed, 1 pre-existing unrelated fail (test_search_cache_hit, #2208), 6 skipped - [x] `ruff check`/`ruff format` — clean - [x] 0 diff в существующих `providers/{avito,cian,yandex,domclick}/*.py` Closes #2358
lekss361 added 2 commits 2026-07-04 00:51:22 +00:00
Аддитивный Foundation-модуль (эпик #2277 Group F4): scraper_kit/providers/_base.py
извлекает 3 реально повторяющихся паттерна, найденных чтением avito/cian/yandex/
domclick providers (~13k строк):

- build_browser_fetcher(config: ScraperConfig, source, ...) — config MANDATORY,
  устраняет footgun-класс #2322/#2330 (BrowserFetcher конструировался вручную в
  6+ местах, endpoint/proxy-pool wiring легко забыть/потерять).
- build_curl_cffi_session / build_document_session + http_proxies() — общий
  curl_cffi AsyncSession(impersonate=chrome120, proxies=..., headers=...)
  boilerplate, дублированный в avito/serp.py, avito/detail.py, avito/imv.py,
  cian/detail.py, cian/newbuilding.py (x2), cian/session.py.
- DOCUMENT_HEADERS — идентичный "document" headers dict, дублированный трижды.

Retry/backoff-циклы (yandex tarpit-retry, avito page-retry, tenacity в
BaseScraper) сознательно НЕ обобщены — разная семантика ошибок/действий между
попытками, обобщение дало бы leaky abstraction (см. docstring модуля).

Existing providers/{avito,cian,yandex,domclick}/*.py НЕ тронуты — миграция
call-site'ов на новый модуль это отдельные future issues (F4a-d).

Заодно: актуализирован устаревший докстринг app/services/scraper_adapters.py
("Пока НИКТО их не использует") — адаптеры wired в прод с #2192 через
app.scheduler_main._run_kit_scheduler.
fix(tradein/scraper-kit): точность докстринга http_proxies() про httpx-совместимость
All checks were successful
CI / changes (pull_request) Successful in 7s
CI Trade-In / 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 1m35s
51e2e545b7
code-reviewer отметил: httpx>=0.28 больше не принимает dict в proxies= (только
одиночный proxy= строкой). Docstring ошибочно называл эту функцию
"httpx-совместимой" — уточнено что это только для curl_cffi, чтобы будущий
мигратор F4a-d не словил TypeError.
lekss361 merged commit d31fabd102 into main 2026-07-04 00:56:14 +00:00
lekss361 deleted branch feat/tradein-scraper-kit-base-2358 2026-07-04 00:56:14 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2360
No description provided.