fix(tradein/browser): куки клались на поддомен и вечно держали QRATOR-загрузчик
Сайдкар инжектил куки на домен из url карточки — для ekaterinburg.domclick.ru это `.ekaterinburg.domclick.ru`. Настоящие куки площадки живут на `.domclick.ru` (видно в записи ручной сессии 29.08: `.domclick.ru qrator_jsid2`, `.domclick.ru qrator_jsr`). Куки поддомена родительские не заменяют, а сосуществуют с ними: как только площадка выдаёт свежий `qrator_jsid2` на `.domclick.ru`, браузер шлёт в одном запросе ДВЕ куки с этим именем, и первой — более специфичную, нашу протухшую. QRATOR читает её и держит страницу на 274-байтном загрузчике, рукопожатие не завершается никогда. Замер 29.08, узел 10, чередование, свежий контекст на пробу: куки на поддомене — 0 успехов из 3 (все три ровно 274 байта), те же куки на `.domclick.ru` — 3 из 3. Заодно шрифты в образе сайдкара: контейнер нёс 8 файлов / 3 семейства (только DejaVu) при заявленном camoufox `os="windows"`. Добавлены метрически совместимые Liberation/Carlito/Caladea + `fc-cache` и build-time проверка. С текущими отказами Домклика это НЕ связано — правка про правдоподобие отпечатка, не про этот баг. Тестов пока нет сознательно: правка ждёт подтверждения на сервере на отдохнувшем пуле.
This commit is contained in:
parent
6cf987c55b
commit
f403198c7e
2 changed files with 65 additions and 2 deletions
|
|
@ -16,8 +16,42 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||||
libx11-xcb1 \
|
libx11-xcb1 \
|
||||||
libxtst6 \
|
libxtst6 \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
|
fontconfig \
|
||||||
|
fonts-liberation \
|
||||||
|
fonts-crosextra-carlito \
|
||||||
|
fonts-crosextra-caladea \
|
||||||
&& useradd --create-home --uid 1000 app
|
&& useradd --create-home --uid 1000 app
|
||||||
|
|
||||||
|
# Замер прод-контейнера (2026-08-29): `fc-list` отдавал 8 файлов / 3 семейства
|
||||||
|
# (только DejaVu Sans/Sans Mono/Serif), а камуфокс запускается с os="windows"
|
||||||
|
# (см. browser/server.py, _launch_browser) — браузер, называющий себя Windows,
|
||||||
|
# но не знающий Arial/Times New Roman/Calibri, легко ловится перечислением
|
||||||
|
# шрифтов (стандартная анти-бот проверка). Набор шрифтов подменить нельзя,
|
||||||
|
# как navigator/UA/экран/таймзону — он меряется рендерингом текста, поэтому
|
||||||
|
# нужны настоящие файлы шрифтов в системе. Это устранение отдельной нестыковки
|
||||||
|
# фингерпринта сама по себе, безотносительно к какому-либо конкретному замеру
|
||||||
|
# отказов площадок — с текущими отказами Домклика (домен инжектируемых кук) это
|
||||||
|
# не связано и их не чинит.
|
||||||
|
#
|
||||||
|
# fonts-liberation/fonts-crosextra-carlito/fonts-crosextra-caladea — метрически
|
||||||
|
# совместимые клоны Arial/Times New Roman/Courier New (Liberation), Calibri
|
||||||
|
# (Carlito) и Cambria (Caladea): те же ширины символов на кегль, просто другой
|
||||||
|
# рисунок. Детект по перечню шрифтов чаще всего меряет именно ширину текста
|
||||||
|
# (measureText), а не байты файла шрифта — метрический клон с алиасом
|
||||||
|
# fontconfig проходит проверку «Arial есть» неотличимо от настоящего Arial.
|
||||||
|
# Алиасы (Arial→Liberation Sans, Calibri→Carlito, Cambria→Caladea) кладёт сам
|
||||||
|
# пакет fontconfig файлом 30-metric-aliases.conf — отдельно их не пишем.
|
||||||
|
#
|
||||||
|
# Настоящие шрифты Microsoft (ttf-mscorefonts-installer) сознательно НЕ ставим:
|
||||||
|
# пакет качает их с SourceForge на этапе сборки и требует интерактивного
|
||||||
|
# принятия EULA — хрупко и недетерминированно для CI-сборки образа.
|
||||||
|
RUN fc-cache -f \
|
||||||
|
&& FONT_COUNT="$(fc-list | wc -l)" \
|
||||||
|
&& echo "tradein-browser: fc-list count after install: $FONT_COUNT" \
|
||||||
|
&& [ "$FONT_COUNT" -ge 30 ] \
|
||||||
|
&& fc-match Arial | grep -qi "Liberation Sans" \
|
||||||
|
&& echo "tradein-browser: fc-match Arial -> Liberation Sans OK"
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Пины, а не плавающие версии: единственный лок этого сайдкара — вот эти строки.
|
# Пины, а не плавающие версии: единственный лок этого сайдкара — вот эти строки.
|
||||||
|
|
|
||||||
|
|
@ -1775,6 +1775,35 @@ async def _wait_out_pow_challenge(
|
||||||
return settled
|
return settled
|
||||||
|
|
||||||
|
|
||||||
|
def _cookie_domain(url: str) -> str:
|
||||||
|
"""Домен для инъекции кук: РЕГИСТРИРУЕМЫЙ, а не хост из url.
|
||||||
|
|
||||||
|
Раньше здесь было ``f".{hostname}"``, то есть для карточки
|
||||||
|
``ekaterinburg.domclick.ru`` куки ложились на ``.ekaterinburg.domclick.ru``.
|
||||||
|
Настоящие куки площадки живут на ``.domclick.ru`` — это видно в записи ручной
|
||||||
|
сессии 29.08.2026 (``.domclick.ruqrator_jsid2``, ``.domclick.ruqrator_jsr``).
|
||||||
|
|
||||||
|
Куки поддомена родительские НЕ заменяют, а сосуществуют с ними: как только
|
||||||
|
площадка выдаёт свежий ``qrator_jsid2`` на ``.domclick.ru``, браузер шлёт в
|
||||||
|
одном запросе ДВЕ куки с этим именем, и первой — более специфичную, нашу
|
||||||
|
протухшую. QRATOR читает её и держит страницу на 274-байтном загрузчике,
|
||||||
|
рукопожатие не завершается никогда. Замер 29.08 через узел 10, чередование,
|
||||||
|
свежий контекст на пробу: куки на поддомене — 0 успехов из 3 (все три
|
||||||
|
застряли на 274 байтах), те же куки на ``.domclick.ru`` — 3 из 3.
|
||||||
|
|
||||||
|
Ограничение сознательное: берём два последних лейбла. Для доменов вида
|
||||||
|
``example.co.uk`` это дало бы ``.co.uk``, но все площадки проекта — в зонах
|
||||||
|
первого уровня (domclick.ru, avito.ru, cian.ru, yandex.ru), а тащить сюда
|
||||||
|
publicsuffix-список ради гипотетического случая дороже, чем он стоит.
|
||||||
|
Хост из одного-двух лейблов возвращаем как есть.
|
||||||
|
"""
|
||||||
|
host = urlparse(url).hostname or ""
|
||||||
|
labels = host.split(".")
|
||||||
|
if len(labels) > 2:
|
||||||
|
host = ".".join(labels[-2:])
|
||||||
|
return f".{host}"
|
||||||
|
|
||||||
|
|
||||||
async def _get_or_create_context(
|
async def _get_or_create_context(
|
||||||
provider: str,
|
provider: str,
|
||||||
browser: object,
|
browser: object,
|
||||||
|
|
@ -1793,7 +1822,7 @@ async def _get_or_create_context(
|
||||||
return existing
|
return existing
|
||||||
context = await browser.new_context() # type: ignore[attr-defined]
|
context = await browser.new_context() # type: ignore[attr-defined]
|
||||||
if cookies:
|
if cookies:
|
||||||
cookie_domain = f".{urlparse(url).hostname or ''}"
|
cookie_domain = _cookie_domain(url)
|
||||||
await context.add_cookies( # type: ignore[attr-defined]
|
await context.add_cookies( # type: ignore[attr-defined]
|
||||||
[
|
[
|
||||||
{"name": name, "value": value, "domain": cookie_domain, "path": "/"}
|
{"name": name, "value": value, "domain": cookie_domain, "path": "/"}
|
||||||
|
|
@ -1876,7 +1905,7 @@ async def _fetch_once(
|
||||||
else:
|
else:
|
||||||
page = await browser.new_page() # type: ignore[attr-defined]
|
page = await browser.new_page() # type: ignore[attr-defined]
|
||||||
if cookies:
|
if cookies:
|
||||||
cookie_domain = f".{urlparse(url).hostname or ''}"
|
cookie_domain = _cookie_domain(url)
|
||||||
await page.context.add_cookies( # type: ignore[attr-defined]
|
await page.context.add_cookies( # type: ignore[attr-defined]
|
||||||
[
|
[
|
||||||
{"name": name, "value": value, "domain": cookie_domain, "path": "/"}
|
{"name": name, "value": value, "domain": cookie_domain, "path": "/"}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue