fix(tradein/scraper-kit): актуализация отпечатка Авито — impersonate, Sec-Fetch-Site, referer, проверка антибот-кук #3038
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#3038
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/3034-avito-fingerprint"
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?
Замер живого браузера 2026-08-21 (владелец прошёл маршрут Яндекс → выдача → карточка → телефон, сеть снималась и сверялась с кодом) разошёлся с тем, что шлёт прогретая curl_cffi-сессия. Четыре связанные правки; у одной широкий blast radius.
Разбор целиком — в волте:
research/Avito_Live_Browser_Recon_0821.md. Базовая точка для замера — комментарий в #3034.Что изменено
1.
impersonateсведён к единому источнику. Был захардкожен в 9+ местах; живой Chrome шлёт версию 151, мы заявляли 120 — разрыв в 31 мажорную версию и в TLS-отпечатке, и в client hints.DEFAULT_IMPERSONATEвproviders/_base.py→chrome146(максимум, доступный в установленном curl_cffi 0.15.0).Алиас
"chrome"намеренно не используется: он поедет сам при апгрейде библиотеки, и поведение изменится без ревью. Guard-тест грепает всё деревоscraper_kitна литералchrome120.Попутно вычищены литералы, которых не было в постановке:
cian/valuation.py,yandex/valuation.py, докстрингcian/session.pyи один лишний вbackend/app/tasks/avito_detail_backfill.py.2.
Sec-Fetch-Site— правка с широким охватом.DOCUMENT_HEADERSставил"none"на уровне сессии, а Referer добавлялся per-request, и curl_cffi мёржит per-request поверх session-level. У настоящего Chrome живого Referer рядом сnoneне бывает никогда:noneозначает «адрес введён руками», и Referer в этом случае отсутствует по определению.Добавлена
referer_headers()сSec-Fetch-Site: "cross-site"для вызывающих с чужедоменным Referer — это прогрев Авито (ya.ru/yandex.ru→avito.ru). Значение"none"в общем словаре осталось дефолтом для запросов без Referer, что корректно.Внутренний same-origin Referer в
fetch_detail(avito-search → avito-detail) не тронут — тамcross-siteбыл бы неверен. В коде оставлено обоснование.3. Referer прогрева
yandex.ru→ya.ru— реальный переход из яндексовой выдачи даёт именноya.ru.ysclidсознательно не добавлен: значение выпускает Яндекс, подделка может выйти хуже отсутствия. Отложено осознанно, комментарий в коде.4. Прогрев не проверял, что антибот-куки пришли. Сессия считалась готовой по HTTP-статусу и отсутствию firewall-маркеров, после чего detail-батч на непрогретой сессии сжигал прокси. Теперь
warm_up_session/research_in_sessionподнимаютAvitoWarmupCookiesMissingError— наследникAvitoBlockedError, поэтому существующиеexcept (AvitoBlockedError, AvitoRateLimitedError),ban_kind_of_exceptionи ротация прокси подхватывают без изменений.Префиксы
("__zzatw-", "cfidsw-")вынесены в константу с пометкой, что сняты с живого браузера 2026-08-21 и могут протухнуть.5. Пол
curl-cffiподнят 0.7.0 → 0.15.0 (отдельный коммит, найдено при ревью ветки). Обаpyprojectпродолжали объявлять>=0.7.0, где профиляchrome146не существует. Текущий прод не пострадал бы — там 0.15.0, — но любая пересборка окружения с резолвом к нижней границе положила бы каждый запрос всех трёх скрапперов на невалидном impersonate, и диагностировалось бы это как «скрапперы легли», а не как несогласованная зависимость.Проверено
uv run pytest -q -rs(backend, полный сьют) — 4672 passed, 31 skipped (все skip — отсутствие локальной Postgres/native deps, к правке не относятся)uv run ruff check .— чистоchrome120по всему дереву; 11 тестов на raise/no-raise проверки кук и на проводкуSec-Fetch-Sitetest_scraper_kit_providers_base.pyОтдельно к сведению: CI не гоняет ruff по
packages/scraper-kit— pre-existing gap, в scope этого PR не входит, проверено вручную теми же правилами.⚠️ Мержить и замерять поштучно
Правки независимы, и если влить их одним куском, мы не узнаем, что сработало. Есть реальный риск построить дорогую имитацию браузера там, где хватило бы смены версии профиля.
Конфаунд: владелец сменил IP на всех прокси около 12:30 UTC 2026-08-21. Эффект уже наблюдался — ручной прогон
avito_city_sweepв 12:53 прошёлdone(128 объявлений, 120 новых) после двух подрядIP banned. Поэтому сравнивать надо не с 21-дневным средним, а с окном после смены прокси и до мержа, иначе улучшение от новых IP запишется на счёт кода.Статистику набирать несколько суток: городские свипы ходят раз в 1-3 дня,
full_load— раз в неделю. Один зелёный прогон ничего не доказывает.Refs #3034
Живой замер браузера 2026-08-21 разошёлся с тем, что шлёт прогретая сессия: - impersonate="chrome120" был захардкожен в 9+ местах (avito/{serp,detail,imv, houses}.py, pipeline.py x4, cian/valuation.py, yandex/valuation.py) вместо единой DEFAULT_IMPERSONATE (providers/_base.py). Обновление до chrome146 (макс. доступный профиль curl_cffi 0.15.0; alias "chrome" НЕ используется - едет сам при апгрейде библиотеки без ревью) теперь меняется в одном месте. Guard-тест backend/tests/test_impersonate_single_source.py грепает всё дерево scraper_kit на литерал "chrome120". - DOCUMENT_HEADERS ставил Sec-Fetch-Site="none" на уровне сессии, а Referer добавлялся per-request (curl_cffi мёржит per-request headers поверх session-level) - живой Referer с "none" рядом не бывает у настоящего Chrome. Добавлен referer_headers() (Sec-Fetch-Site="cross-site") для caller'ов с чужедоменным Referer; avito warm-up (yandex/ya.ru -> avito) теперь его использует. Внутренний avito-search -> avito-detail Referer (fetch_detail, same-origin) НЕ тронут - отдельный явный комментарий почему. - Referer прогрева заменён с yandex.ru на ya.ru (живой переход из выдачи даёт короткий домен). ysclid сознательно не добавлен - значение выпускает Яндекс, подделка хуже отсутствия. - warm_up_session/research_in_session считали прогрев успешным по HTTP- статусу и отсутствию firewall-маркеров, не проверяя антибот-cookies (__zzatw-*/cfidsw-*, сняты с живого браузера) - detail-батч на такой "прогретой" сессии сжигал прокси на обречённых 403. Теперь поднимают AvitoWarmupCookiesMissingError (наследник AvitoBlockedError - существующие except-блоки/ban_kind/proxy-ротация в pipeline и backfill ловят без изменений). Полный backend pytest suite зелёный (4672 passed), ruff check чист. Refs #3034⚠️ Ловушка замера: доля
bannedсразу после мержа может ВЫРАСТИ — и это не регрессияПравка №4 (проверка антибот-кук) меняет не поведение сети, а честность отчётности. До неё непрогретая сессия считалась готовой и шла дальше: запросы отбивались по одному, прогон мог доехать до конца и записаться как
doneс тощим результатом либо упасть позже и с другой причиной. Теперь такая сессия падает сразу и явно —AvitoWarmupCookiesMissingError, классифицируется какban_kind='platform'.То есть часть прогонов, которые раньше молча деградировали, теперь честно попадут в
banned. Метрикаpct_badможет подскочить при том, что реальное состояние улучшилось — мы просто перестали врать себе и экономим прокси вместо того, чтобы жечь их на обречённых запросах.Поэтому при замере смотреть не только на долю отказов, а на пару:
Настоящий критерий успеха —
seen/new_rowsза сутки, а не доляbanned. Если собранных объявлений столько же или больше при выросшей доле отказов — правка сработала, просто отказы стали видимыми. Если просело и то и другое — откатывать.Два независимых конфаунда, оба уже в силе:
full_load— раз в неделю.Мержу этот PR первым и оставляю #3039 ждать.