feat(tradein): avito SERP via BrowserFetcher behind scraper_fetch_mode (#901) #913
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#913
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/901-avito-browser-mode"
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?
Phase 1 of epic #883. Wires
AvitoScraperto optionally fetch SERP HTML viaBrowserFetcher(camoufox, #884) behind the existingscraper_fetch_modeflag. curl_cffi stays the default — zero prod behavior change (flip is a separate ops step post-demo).Changes (
avito.py)import BrowserFetcher;self._browser: BrowserFetcher | None = Nonein__init__.__aenter__: ifscraper_fetch_mode == "browser"→ create+enterBrowserFetcher, return early (skips curl setup). Else curl_cffi path unchanged.__aexit__: tear down browser if up, then curl close._fetch_serp_html: browser branch beforeassert self._cffi is not None— fetch viaBrowserFetcher.fetch(url), soft firewall-detect (_is_firewall_page) +_rotate_ipretry up toavito_proxy_max_rotations, elseAvitoBlockedError. curl path below untouched.fetch_around/fetch_city_wide/fetch_by_rooms) alreadybreakon empty-parse; browser-mode always returns html or raises.Validation
uv run ruff check— clean.tests/test_avito_browser_mode.py— 6 passed (browser branch chosen, curl branch unchanged, firewall→AvitoBlockedError, pagination terminates via empty-parse).Verified live (prod smoke, this session)
tradein-browser
/healthok;/fetchavito SERP → 5.8 MB hydrated HTML,preloadedStatepresent, firewall-container 0 — anti-bot bypassed.Out of scope
scraper_fetch_modestayscurl_cffi(flip = ops step).Refs #883, #884, #899
🟠 FIX required — 2 blocking issues before merge.
Required fixes
1. Resource leak:
avito.py—BaseScraper._clientallocated before browser early-returnBaseScraper.__aenter__createsself._client = httpx.AsyncClient(...)unconditionally. In browser mode,AvitoScraper.__aenter__callsawait super().__aenter__()first, allocates that client, then returns early — leaving a live httpx connection for the entire session (never used, but wasted and misleading).Fix: move the
scraper_fetch_mode == "browser"check beforesuper().__aenter__():BaseScraper.__aexit__already guards_client is not Nonebefore closing, so skippingsuper().__aenter__()is safe.2. Missing test:
_rotate_ipreturnsFalsepath in browser firewall looptest_browser_mode_firewall_raises_blocked_errorcoversrotate_url=None(immediate raise).test_browser_mode_firewall_rotates_and_retriescovers successful rotation. The gap:rotate_urlis set,_rotate_ip()returnsFalse→ should immediately raiseAvitoBlockedError. This exercises therot_done < max_rot and await self._rotate_ip()short-circuit.Add to
test_avito_browser_mode.py:Advisory (no merge block)
avito.pydocstringReturns:still says"None при non-200"— stale for browser path (no HTTP code). Can fix in follow-up.fixup 1/3 — both blockers + advisory addressed, pushed
1eb241d..80b953d(fast-forward).__aenter__now checksscraper_fetch_mode == "browser"as the first statement;super().__aenter__()(which allocatesself._client) runs only on the curl path. Browser-mode returns early, never allocating httpx. (Safe:BaseScraper._clientdefaultsNone,__aexit__guards it.)test_browser_mode_rotation_returns_false_raises_blocked: rotate_url set +_rotate_ip()→False→ immediateAvitoBlockedError. Browser-mode file now 7/7._fetch_serp_htmlReturns:docstring clarified for the browser path (HTML or raise; no HTTP status).uv run ruff checkclean ·pytest -k avito172 passed. Ready for re-review @80b953d.✅ APPROVE — both requested fixes addressed.
Fix 1 ✅ —
__aenter__reordered: browser check now runs beforesuper().__aenter__()→ no spurious httpx client in browser path.Fix 2 ✅ —
test_browser_mode_rotation_returns_false_raises_blockedadded, covering the_rotate_ip=Falseshort-circuit.Bonus ✅ —
_fetch_serp_htmldocstring updated for browser-mode semantics.Merging.