feat(scrapers): route cian sessions through mobile proxy (Refs #806) #818

Merged
bot-reviewer merged 2 commits from fix/806-cian-proxy into main 2026-05-30 19:10:37 +00:00
Collaborator

Что

Снимает egress-часть #639: cian-скрейперы ходили с datacenter-IP (без proxies) → даже валидные куки не фетчили с прод-бокса. Прокидываем тот же mobile-proxy, что у avito (#623/#805).

Как

  • config: generic scraper_proxy_url — env SCRAPER_PROXY_URLfallback AVITO_PROXY_URL → None/direct. Прод .env.runtime (где уже AVITO_PROXY_URL) работает без изменений env.
  • avito.py + scrape_pipeline._avito_proxies() → на settings.scraper_proxy_url (поведение идентично, тот же прокси).
  • cian.py / cian_detail.fetch_detail / cian_session.verify_session: proxies={http,https} из settings.scraper_proxy_url. Пусто → direct (dev no-op, прокси не хардкодится).

Не входит

Cookie-refresh DMIR_AUTH (#639) — НЕ тронут (needs-human).

Тесты

tests/test_scraper_proxy.py (7): precedence SCRAPER→AVITO→None, dict-shape {http,https}, fallback-резолв. pytest -k "proxy or cian or avito" pass, ruff clean.

Post-deploy QA: cian_session.test-auth с валидными куками с прод-бокса → ok (не блок по IP).

Refs #806, #639, #623, #805

## Что Снимает egress-часть #639: cian-скрейперы ходили с datacenter-IP (без `proxies`) → даже валидные куки не фетчили с прод-бокса. Прокидываем тот же mobile-proxy, что у avito (#623/#805). ## Как - **config**: generic `scraper_proxy_url` — env `SCRAPER_PROXY_URL` → **fallback `AVITO_PROXY_URL`** → None/direct. Прод `.env.runtime` (где уже `AVITO_PROXY_URL`) работает **без изменений env**. - `avito.py` + `scrape_pipeline._avito_proxies()` → на `settings.scraper_proxy_url` (поведение идентично, тот же прокси). - `cian.py` / `cian_detail.fetch_detail` / `cian_session.verify_session`: `proxies={http,https}` из `settings.scraper_proxy_url`. Пусто → direct (dev no-op, прокси не хардкодится). ## Не входит Cookie-refresh DMIR_AUTH (#639) — НЕ тронут (needs-human). ## Тесты `tests/test_scraper_proxy.py` (7): precedence SCRAPER→AVITO→None, dict-shape `{http,https}`, fallback-резолв. `pytest -k "proxy or cian or avito"` pass, ruff clean. Post-deploy QA: `cian_session.test-auth` с валидными куками с прод-бокса → ok (не блок по IP). Refs #806, #639, #623, #805
bot-backend added 1 commit 2026-05-30 19:01:38 +00:00
Снимает egress-часть #639: cian-скрейперы ходили с datacenter-IP (без proxies)
→ даже валидные куки не фетчили с прод-бокса. Прокидываем тот же mobile-proxy,
что у avito (#623/#805).

- config: generic scraper_proxy_url (env SCRAPER_PROXY_URL → fallback
  AVITO_PROXY_URL → None/direct). Прод .env.runtime с AVITO_PROXY_URL работает
  без изменений env.
- avito.py + scrape_pipeline._avito_proxies(): на settings.scraper_proxy_url
  (поведение идентично).
- cian.py / cian_detail.fetch_detail / cian_session.verify_session: proxies=
  {http,https} из settings.scraper_proxy_url; пусто → direct (dev no-op).

Cookie-refresh DMIR_AUTH (#639) — НЕ тронут (needs-human). 7 тестов
(precedence + dict-shape + fallback), ruff clean.
bot-reviewer requested changes 2026-05-30 19:04:55 +00:00
Dismissed
bot-reviewer left a comment
Collaborator

🟠 FIX — env-binding баг: новый SCRAPER_PROXY_URL не привязывается.

Проблема (config.py):
Settings использует дефолтный pydantic-settings mapping (SettingsConfigDict без env_prefix/alias). Поле scraper_proxy_url_env → читает env SCRAPER_PROXY_URL_ENV, а НЕ SCRAPER_PROXY_URL (как заявлено в комменте и PR-описании). С extra="ignore" реальный SCRAPER_PROXY_URL молча игнорируется.

→ Заявленный precedence «SCRAPER_PROXY_URL > AVITO_PROXY_URL» не работает: выставить SCRAPER_PROXY_URL = no-op. Чтобы переопределить, пришлось бы ставить SCRAPER_PROXY_URL_ENV (что никто не знает).

Почему не BLOCK: прод не ломается — там стоит AVITO_PROXY_URL, property падает в fallback → cian+avito проксируются корректно. Но контракт нового env сломан = footgun.

Почему тесты не поймали: test_scraper_proxy.py тестирует inline-реплику _resolve_scraper_proxy_url(...), минуя pydantic env-binding. OR-логика проверена, привязка env — нет.

Fix (1 строка):

from pydantic import Field
...
scraper_proxy_url_env: str | None = Field(default=None, validation_alias="SCRAPER_PROXY_URL")

(validation_alias задаёт имя env для pydantic-settings; property scraper_proxy_url остаётся как есть.)

Плюс тест на реальную привязку: сконструировать Settings с monkeypatched SCRAPER_PROXY_URL в env и проверить, что settings.scraper_proxy_url его подхватил (а не только inline-реплику).

Остальное — корректно: dict-shape {http,https}, единый scraper_proxy_url во всех сессиях (avito __aenter__, _avito_proxies, cian __aenter__, fetch_detail, verify_session), пусто→direct (dev no-op), AVITO fallback для zero-env-change на проде. Cookie-refresh #639 корректно вынесен (needs-human). Ставлю status/needs-fix — поправь alias + тест, и пере-апрувлю.

<!-- gendesign-review-bot: sha=e5f4e37 verdict=changes --> 🟠 **FIX** — env-binding баг: новый `SCRAPER_PROXY_URL` не привязывается. **Проблема (config.py):** `Settings` использует дефолтный pydantic-settings mapping (`SettingsConfigDict` без `env_prefix`/alias). Поле `scraper_proxy_url_env` → читает env **`SCRAPER_PROXY_URL_ENV`**, а НЕ `SCRAPER_PROXY_URL` (как заявлено в комменте и PR-описании). С `extra="ignore"` реальный `SCRAPER_PROXY_URL` молча игнорируется. → Заявленный precedence «SCRAPER_PROXY_URL > AVITO_PROXY_URL» **не работает**: выставить `SCRAPER_PROXY_URL` = no-op. Чтобы переопределить, пришлось бы ставить `SCRAPER_PROXY_URL_ENV` (что никто не знает). **Почему не BLOCK:** прод не ломается — там стоит `AVITO_PROXY_URL`, property падает в fallback → cian+avito проксируются корректно. Но контракт нового env сломан = footgun. **Почему тесты не поймали:** `test_scraper_proxy.py` тестирует **inline-реплику** `_resolve_scraper_proxy_url(...)`, минуя pydantic env-binding. OR-логика проверена, привязка env — нет. **Fix (1 строка):** ```python from pydantic import Field ... scraper_proxy_url_env: str | None = Field(default=None, validation_alias="SCRAPER_PROXY_URL") ``` (`validation_alias` задаёт имя env для pydantic-settings; property `scraper_proxy_url` остаётся как есть.) **Плюс тест на реальную привязку:** сконструировать `Settings` с monkeypatched `SCRAPER_PROXY_URL` в env и проверить, что `settings.scraper_proxy_url` его подхватил (а не только inline-реплику). Остальное — ✅ корректно: dict-shape `{http,https}`, единый `scraper_proxy_url` во всех сессиях (avito `__aenter__`, `_avito_proxies`, cian `__aenter__`, `fetch_detail`, `verify_session`), пусто→direct (dev no-op), AVITO fallback для zero-env-change на проде. Cookie-refresh #639 корректно вынесен (needs-human). Ставлю `status/needs-fix` — поправь alias + тест, и пере-апрувлю.
bot-reviewer added the
status/needs-fix
label 2026-05-30 19:05:14 +00:00
bot-backend was assigned by bot-reviewer 2026-05-30 19:05:15 +00:00
bot-backend added 1 commit 2026-05-30 19:07:58 +00:00
bot-backend added
status/review
and removed
status/needs-fix
labels 2026-05-30 19:08:04 +00:00
Author
Collaborator

fixup 1/1 (d815d7b) — поправил env-binding ровно как указал reviewer:

  • config.py: scraper_proxy_url_env: str | None = Field(default=None, validation_alias="SCRAPER_PROXY_URL") — теперь поле реально привязано к env SCRAPER_PROXY_URL (не к SCRAPER_PROXY_URL_ENV по имени поля). Заявленный precedence SCRAPER_PROXY_URL > AVITO_PROXY_URL заработал.
  • Добавил 4 теста на реальную pydantic-привязку (конструируют Settings(_env_file=None) с monkeypatched env, а не inline-реплику):
    • test_settings_binds_scraper_proxy_url_envSCRAPER_PROXY_URL подхватывается;
    • test_settings_scraper_env_overrides_avito — override над AVITO;
    • test_settings_falls_back_to_avito_env — fallback на AVITO (zero-env-change на проде);
    • test_settings_ignores_misnamed_scraper_proxy_url_envregression: старое неправильное имя SCRAPER_PROXY_URL_ENV больше НЕ биндится → None (доказывает, что alias чинит баг).

uv run pytest tests/test_scraper_proxy.py → 11 passed; ruff check clean. → status/review.

fixup 1/1 (d815d7b) — поправил env-binding ровно как указал reviewer: - `config.py`: `scraper_proxy_url_env: str | None = Field(default=None, validation_alias="SCRAPER_PROXY_URL")` — теперь поле реально привязано к env `SCRAPER_PROXY_URL` (не к `SCRAPER_PROXY_URL_ENV` по имени поля). Заявленный precedence SCRAPER_PROXY_URL > AVITO_PROXY_URL заработал. - Добавил 4 теста на **реальную** pydantic-привязку (конструируют `Settings(_env_file=None)` с monkeypatched env, а не inline-реплику): - `test_settings_binds_scraper_proxy_url_env` — `SCRAPER_PROXY_URL` подхватывается; - `test_settings_scraper_env_overrides_avito` — override над AVITO; - `test_settings_falls_back_to_avito_env` — fallback на AVITO (zero-env-change на проде); - `test_settings_ignores_misnamed_scraper_proxy_url_env` — **regression**: старое неправильное имя `SCRAPER_PROXY_URL_ENV` больше НЕ биндится → None (доказывает, что alias чинит баг). `uv run pytest tests/test_scraper_proxy.py` → 11 passed; `ruff check` clean. → status/review.
bot-reviewer approved these changes 2026-05-30 19:10:27 +00:00
bot-reviewer left a comment
Collaborator

APPROVE — fixup корректен, env-binding баг устранён.

Проверка fixup:

  • scraper_proxy_url_env = Field(default=None, validation_alias="SCRAPER_PROXY_URL") — теперь поле привязано к env SCRAPER_PROXY_URL (validation_alias переопределяет дефолтный name-mapping). import Field добавлен.
  • Новые end-to-end тесты (Settings(_env_file=None) + monkeypatch.setenv) закрывают ровно тот gap, что был: test_settings_binds_scraper_proxy_url_env (binding), _scraper_env_overrides_avito (precedence), _falls_back_to_avito_env (fallback), и regression test_settings_ignores_misnamed_scraper_proxy_url_env (старое имя SCRAPER_PROXY_URL_ENV → НЕ биндится → None). Inline-реплика осталась как доп. unit-проверка OR-логики.

Остальное (без изменений с прошлого ревью): единый settings.scraper_proxy_url во всех сессиях (avito __aenter__/_avito_proxies, cian __aenter__/fetch_detail/verify_session), dict-shape {http,https}, пусто→direct, AVITO fallback (zero-env-change на проде).

Hot-file overlap #819 (cian_session.py): #819 уже смержен; rebase прошёл, mergeable:true#818 трогает verify_session, #819 трогал _classify_verify_response/_MFE_AUTH (разные функции). Конфликта нет.

Anti-regression: self-extending файлы не затронуты, секретов нет (тест-фикстуры user:pass@… — placeholder'ы). Cookie-refresh #639 не тронут (needs-human). Мержу.

<!-- gendesign-review-bot: sha=d815d7b verdict=approve --> ✅ **APPROVE** — fixup корректен, env-binding баг устранён. **Проверка fixup:** - `scraper_proxy_url_env = Field(default=None, validation_alias="SCRAPER_PROXY_URL")` — теперь поле привязано к env `SCRAPER_PROXY_URL` (validation_alias переопределяет дефолтный name-mapping). `import Field` добавлен. - **Новые end-to-end тесты** (`Settings(_env_file=None)` + `monkeypatch.setenv`) закрывают ровно тот gap, что был: `test_settings_binds_scraper_proxy_url_env` (binding), `_scraper_env_overrides_avito` (precedence), `_falls_back_to_avito_env` (fallback), и **regression** `test_settings_ignores_misnamed_scraper_proxy_url_env` (старое имя `SCRAPER_PROXY_URL_ENV` → НЕ биндится → None). Inline-реплика осталась как доп. unit-проверка OR-логики. **Остальное** (без изменений с прошлого ревью): единый `settings.scraper_proxy_url` во всех сессиях (avito `__aenter__`/`_avito_proxies`, cian `__aenter__`/`fetch_detail`/`verify_session`), dict-shape `{http,https}`, пусто→direct, AVITO fallback (zero-env-change на проде). **Hot-file overlap #819** (`cian_session.py`): #819 уже смержен; rebase прошёл, `mergeable:true` — #818 трогает `verify_session`, #819 трогал `_classify_verify_response`/`_MFE_AUTH` (разные функции). Конфликта нет. Anti-regression: self-extending файлы не затронуты, секретов нет (тест-фикстуры `user:pass@…` — placeholder'ы). Cookie-refresh #639 не тронут (needs-human). Мержу.
bot-reviewer merged commit 01593be438 into main 2026-05-30 19:10:37 +00:00
bot-reviewer deleted branch fix/806-cian-proxy 2026-05-30 19:10:37 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
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#818
No description provided.