Compare commits

..
2 Commits
Author SHA1 Message Date
protokeyandClaude Opus 5.5 ae5cefc6df chore: __pycache__ в .gitignore
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:28:44 +04:00
protokeyandClaude Opus 5.5 08a6b52b9d build: сборка в docker
- Dockerfile: python 3.12, Google Chrome (patchright), Xvfb, tini как PID 1 против зомби-процессов
- docker-compose: volume для extra/, shm_size 2gb, порт через API_PORT
- requirements: добавлены playwright и camoufox
- docs: Deployment.md, README и доки актуализированы под Chrome

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:17:28 +04:00
9 changed files with 163 additions and 21 deletions

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
.git
.idea
.venv
wheels
extra
docs
tests
**/__pycache__
*.pyc
.env
+1
View File
@@ -1,6 +1,7 @@
extra/ extra/
.venv/ .venv/
.idea/ .idea/
__pycache__/
wheels/ wheels/
docs/*.bkp docs/*.bkp
docs/*.dtmp docs/*.dtmp
+38
View File
@@ -0,0 +1,38 @@
FROM python:3.12-slim-bookworm
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# Xvfb — браузер запускается в headful-режиме (headless=False), нужен виртуальный дисплей
RUN apt-get update \
&& apt-get install -y --no-install-recommends tini xvfb xauth fonts-liberation fonts-noto-color-emoji ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
# real_chrome=True → channel "chrome": ставим Google Chrome stable вместе с системными зависимостями
RUN patchright install --with-deps chrome \
&& rm -rf /var/lib/apt/lists/*
RUN useradd --create-home --uid 1000 app \
&& mkdir -p /app/extra \
&& chown -R app:app /app
COPY --chown=app:app src/ ./src/
USER app
WORKDIR /app/src
# профили браузера (extra/user_data_dir) и geoip-кэш — в volume
VOLUME ["/app/extra"]
EXPOSE 8000
# tini как PID 1: пробрасывает сигналы и подбирает осиротевшие процессы Chrome/Xvfb (иначе — зомби)
ENTRYPOINT ["/usr/bin/tini", "-g", "--"]
CMD ["xvfb-run", "-a", "--server-args=-screen 0 1920x1080x24", \
"uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
+30 -18
View File
@@ -13,15 +13,15 @@ Early MVP (v0), under active development. Single browser engine, no queue, no bi
Client → Caddy (TLS) → FastAPI → API-key auth → semaphore acquire Client → Caddy (TLS) → FastAPI → API-key auth → semaphore acquire
│ │
▼ ▼
Camoufox browser context Chrome (Patchright) context
(run actions, collect result) (run actions, collect result)
│ │
▼ ▼
release semaphore → response release semaphore → response
``` ```
- **Engine**: [Camoufox](https://camoufox.com/) (stealth Firefox, driven via Playwright). Chosen for v1 because - **Engine**: real Google Chrome (`channel="chrome"`) driven via [Patchright](https://github.com/Kaliiiiiiiiii-Vinyzu/patchright-python)
Firefox-based fingerprinting is less commoditized against anti-bot vendors than patched Chromium. (undetected Playwright fork), headful under Xvfb. Firefox is not supported yet.
- **API**: FastAPI, single versioned endpoint (`POST /v1/solve`). - **API**: FastAPI, single versioned endpoint (`POST /v1/solve`).
- **Concurrency**: bounded by an `asyncio.Semaphore` over browser contexts — no external job queue in v1. Simpler to - **Concurrency**: bounded by an `asyncio.Semaphore` over browser contexts — no external job queue in v1. Simpler to
run, hard ceiling on throughput per instance (see Roadmap for scaling out). run, hard ceiling on throughput per instance (see Roadmap for scaling out).
@@ -29,28 +29,30 @@ Client → Caddy (TLS) → FastAPI → API-key auth → semaphore acquire
- **Logging**: every request's outcome (success/failure/duration) is persisted — this becomes the success-rate data used - **Logging**: every request's outcome (success/failure/duration) is persisted — this becomes the success-rate data used
to evaluate the service and talk to clients about reliability. to evaluate the service and talk to clients about reliability.
- **Proxies**: not managed by the service in v1 — the client supplies their own proxy per request if they need one. - **Proxies**: not managed by the service in v1 — the client supplies their own proxy per request if they need one.
- **Deployment**: single Docker container behind a Caddy reverse proxy. - **Deployment**: single Docker container behind a Caddy reverse proxy — see [Docker](#docker) and
[docs/Deployment.md](docs/Deployment.md).
- **Canvas fingerprint** по дизайну генерируется каждую сессию новый. - **Canvas fingerprint** по дизайну генерируется каждую сессию новый.
### Planned project layout ### Project layout
``` ```
src/ src/
main.py # FastAPI app + router wiring main.py # FastAPI app, POST /v1/solve
config.py # settings (env vars)
api/ api/
solve.py # POST /v1/solve
deps.py # API-key auth dependency
engine/
browser.py # Camoufox context pool + semaphore
actions.py # fill / click / wait_for execution actions.py # fill / click / wait_for execution
schemas.py # SolveRequest / SolveResponse / Action models schemas.py # SolveRequest / Action models
db/ geoip.py # proxy → locale / timezone
models.py # api_keys, request_logs engine/
session.py stealthy.py # AsyncStealthySession (Patchright + Chrome)
session.py # browser / context launch options
page_pool.py # page pool
antibot/ # anti-bot detection & bypass orchestrator (Cloudflare, reCAPTCHA)
captcha/ # captcha solvers (Turnstile, reCAPTCHA, hCaptcha)
tests/ tests/
wheels/ # whl библиотеки для оффлайн установки под Ubuntu Noble wheels/ # whl библиотеки для оффлайн установки под Ubuntu Noble
Dockerfile Dockerfile
docker-compose.yml
.dockerignore
requirements.txt requirements.txt
env.example env.example
``` ```
@@ -74,7 +76,7 @@ X-API-Key: <key>
"proxy": "socks5://user:pass@host:port", "proxy": "socks5://user:pass@host:port",
"screen": "1280x920", "screen": "1280x920",
"user_agent": "Firefox", "user_agent": "Mozilla/5.0 (...) Chrome/...",
"timeout": 120 "timeout": 120
} }
``` ```
@@ -131,7 +133,7 @@ Deliberately out of scope for v1:
- Job queue (Redis/RabbitMQ) + multiple worker VPS for horizontal scaling - Job queue (Redis/RabbitMQ) + multiple worker VPS for horizontal scaling
- Real billing/usage plans beyond a flat API-key check - Real billing/usage plans beyond a flat API-key check
- Broader action vocabulary beyond fill/click/wait_for - Broader action vocabulary beyond fill/click/wait_for
- Second engine (Patchright/Chromium) for targets where the Firefox fingerprint doesn't fit - Second engine (Firefox, e.g. Camoufox) for targets where the Chrome fingerprint doesn't fit
## Local development ## Local development
@@ -139,5 +141,15 @@ Deliberately out of scope for v1:
python -m venv .venv python -m venv .venv
source .venv/bin/activate source .venv/bin/activate
make sync-offline make sync-offline
uvicorn app.main:app --reload cd src && uvicorn main:app --reload
``` ```
## Docker
```bash
docker compose up -d --build
```
The API listens on `http://localhost:8000`. Browser profiles (`extra/user_data_dir`) and the GeoIP cache live in the
`extra` named volume, so sessions survive container restarts. Details (Xvfb, tini, `/dev/shm`) —
[docs/Deployment.md](docs/Deployment.md).
+17
View File
@@ -0,0 +1,17 @@
services:
api:
build: .
image: robotex-api
restart: unless-stopped
ports:
- "${API_PORT:-8000}:8000"
env_file:
- path: .env
required: false
volumes:
- extra:/app/extra
# Chrome активно использует /dev/shm, дефолтных 64MB не хватает
shm_size: "2gb"
volumes:
extra:
+2 -2
View File
@@ -57,7 +57,7 @@ src/antibot/
```python ```python
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum from enum import Enum
from playwright.async_api import Page from patchright.async_api import Page
class Stage(str, Enum): class Stage(str, Enum):
INTERSTITIAL = "interstitial" INTERSTITIAL = "interstitial"
@@ -188,7 +188,7 @@ async def solve_interactive(page, info, captcha_key: str):
1. **Траектория мыши** — не прыжок в точку, а несколько промежуточных точек по кривой Безье от текущей позиции курсора до цели, с нелинейным (ease-in-out) распределением скорости и небольшим шумом (±1-2px) на каждом шаге. `page.mouse.move(x, y, steps=N)` даёт линейную интерполяцию — этого мало, нужно звать `mouse.move` несколько раз вручную по расчётным точкам. 1. **Траектория мыши** — не прыжок в точку, а несколько промежуточных точек по кривой Безье от текущей позиции курсора до цели, с нелинейным (ease-in-out) распределением скорости и небольшим шумом (±1-2px) на каждом шаге. `page.mouse.move(x, y, steps=N)` даёт линейную интерполяцию — этого мало, нужно звать `mouse.move` несколько раз вручную по расчётным точкам.
2. **Ускорение/замедление клика** — задержка `mouse.down()` -> `mouse.up()` рандомная (100-250мс), плюс случайный сдвиг координаты клика внутри bounding box (не в центр, как это делает и сам Scrapling — `randint(26,28)`). 2. **Ускорение/замедление клика** — задержка `mouse.down()` -> `mouse.up()` рандомная (100-250мс), плюс случайный сдвиг координаты клика внутри bounding box (не в центр, как это делает и сам Scrapling — `randint(26,28)`).
3. **Общая стелс-часть** (не про капчу, но без неё стадия A вообще не пройдёт): подмена `navigator.webdriver`, canvas noise, консистентные timezone/locale/UA — то, что в вашем проекте уже частично закрыто Camoufox-фингерпринтом в [main.py](../src/main.py) (`generate_context_fingerprint`). Это нужно сохранить как есть — не переизобретать, Camoufox с этим справляется хорошо, вопрос отказа от него у Scrapling был про производительность/стабильность, а не про качество стелса. 3. **Общая стелс-часть** (не про капчу, но без неё стадия A вообще не пройдёт): подмена `navigator.webdriver`, canvas noise, консистентные timezone/locale/UA — сейчас закрывается связкой Patchright + настоящий Google Chrome (`real_chrome=True`) в [engine/stealthy.py](../src/engine/stealthy.py), timezone/locale берутся из геолокации прокси ([api/geoip.py](../src/api/geoip.py)). Camoufox (Firefox) пока остаётся в зависимостях, но как движок не используется — проблемы были скорее с генератором отпечатков, чем с самим Camoufox.
## 6. Оркестратор (`orchestrator.py`) ## 6. Оркестратор (`orchestrator.py`)
+62
View File
@@ -0,0 +1,62 @@
# Развёртывание в Docker
## Быстрый старт
```bash
docker compose up -d --build
docker compose logs -f api
```
API доступно на `http://localhost:8000` (`POST /v1/solve`).
Без compose:
```bash
docker build -t robotex-api .
docker run -d --name robotex -p 8000:8000 --shm-size=2g -v robotex-extra:/app/extra robotex-api
```
## Что внутри образа
| Компонент | Зачем |
|---|---|
| `python:3.12-slim-bookworm` | базовый образ, Python той же версии, что и в `.venv` |
| `requirements.txt` | ставится из PyPI (папка `wheels/` в образ не попадает — она под Ubuntu Noble и неполная) |
| Google Chrome stable (`patchright install --with-deps chrome`) | сессия запускается с `real_chrome=True` → channel `chrome` |
| Xvfb (`xvfb-run`) | браузер работает в headful-режиме (`headless=False`), нужен виртуальный дисплей 1920x1080x24 |
| `tini` (ENTRYPOINT, PID 1) | пробрасывает сигналы и подбирает осиротевшие процессы Chrome/Xvfb — без него копятся зомби-процессы |
| пользователь `app` (uid 1000) | Chrome не запускается от root без `--no-sandbox` |
Рабочая директория — `/app/src`, uvicorn запускается как `uvicorn main:app` (импорты в проекте вида `from api import ...`).
## Данные и volume
`/app/extra` вынесен в volume `extra`:
- `extra/user_data_dir/<session_id>` — профили браузера (cookies, local storage, fingerprint) по `session_id`;
- `extra/geoip_cache.json` — кэш геолокации прокси.
Профили со временем накапливаются — чистить вручную:
```bash
docker compose exec api sh -c 'rm -rf /app/extra/user_data_dir/*'
```
## Настройки compose
- `shm_size: 2gb` — Chrome активно использует `/dev/shm`, дефолтных 64 МБ не хватает (вкладки падают с «Aw, Snap»).
- `API_PORT` — порт на хосте (по умолчанию 8000): `API_PORT=8001 docker compose up -d`.
- `restart: unless-stopped`.
- `.env` подключается, если существует (шаблон — `env.example`).
## Reverse proxy
В проде контейнер ставится за Caddy (TLS). Пример `Caddyfile`:
```
api.example.com {
reverse_proxy localhost:8000
}
```
Таймауты прокси должны быть не меньше `timeout` запроса (по умолчанию 120 с).
+1 -1
View File
@@ -10,7 +10,7 @@
- proxy - обязательно нужно задать прокси, система не станет палить свою инфраструктуру - proxy - обязательно нужно задать прокси, система не станет палить свою инфраструктуру
- time_zone - временная зона - time_zone - временная зона
- locale - языковая настройка - locale - языковая настройка
- user_agent - заголовок браузера Firefox, если не передать будет использован свой - user_agent - User-Agent браузера Chrome, если не передать будет использован свой
- screen - размер окна, по умолчанию: 1280х920 - screen - размер окна, по умолчанию: 1280х920
- timeout - общий таймаут выполнения запроса, по умолчанию: 120 - timeout - общий таймаут выполнения запроса, по умолчанию: 120
- captcha - токен сервиса распознавания, если требуется решить капчу на странице - captcha - токен сервиса распознавания, если требуется решить капчу на странице
+2
View File
@@ -10,6 +10,7 @@ apify_fingerprint_datapoints==0.15.0
attrs==26.1.0 attrs==26.1.0
blessed==1.49.0 blessed==1.49.0
browserforge==1.2.4 browserforge==1.2.4
camoufox==0.5.6
certifi==2026.7.22 certifi==2026.7.22
charset-normalizer==3.5.1 charset-normalizer==3.5.1
click==8.5.0 click==8.5.0
@@ -47,6 +48,7 @@ numpy==2.5.3
orjson==3.12.0 orjson==3.12.0
patchright==1.63.0 patchright==1.63.0
platformdirs==4.11.7 platformdirs==4.11.7
playwright==1.62.0
propcache==0.5.2 propcache==0.5.2
psutil==7.2.2 psutil==7.2.2
pydantic==2.13.5 pydantic==2.13.5