Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d616e1f4f | ||
|
|
0e10fb671d | ||
|
|
4d90dc4a21 | ||
|
|
fdee12e2d8 |
No files matched your search
+1
-1
@@ -12,5 +12,5 @@ tab_width = 4
|
||||
[{*.har,*.jsb2,*.jsb3,*.json,*.jsonc,*.postman_collection,*.postman_collection.json,*.postman_environment,*.postman_environment.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,.ws-context,jest.config}]
|
||||
indent_size = 2
|
||||
|
||||
[{*.yaml,*.yml,environment.yaml,environment.yml}]
|
||||
[{*.yaml,*.yml,*.http,environment.yaml,environment.yml}]
|
||||
indent_size = 2
|
||||
@@ -9,3 +9,4 @@ build/
|
||||
client/dist/
|
||||
client/build/
|
||||
client/src/*.egg-info/
|
||||
http-client.private.env.json
|
||||
@@ -5,10 +5,10 @@ WHEELS_DIR := wheels
|
||||
REQUIREMENTS := requirements.txt
|
||||
ver ?= latest
|
||||
|
||||
MAKECMDGOALS_TARGETS := install freeze wheels sync sync-offline clean help build release sync-dev test test-browser up down logs smoke key-create key-list key-revoke stats push
|
||||
MAKECMDGOALS_TARGETS := install freeze wheels sync sync-offline clean help build release sync-dev test test-browser up down logs smoke key-create key-list key-revoke stats push dev dev-deps dev-key
|
||||
PKG := $(filter-out $(MAKECMDGOALS_TARGETS),$(MAKECMDGOALS))
|
||||
|
||||
.PHONY: install freeze wheels sync sync-offline clean help venv build release sync-dev test test-browser up down logs smoke key-create key-list key-revoke stats push
|
||||
.PHONY: install freeze wheels sync sync-offline clean help venv build release sync-dev test test-browser up down logs smoke key-create key-list key-revoke stats push dev dev-deps dev-key
|
||||
|
||||
# образы сервисов: api — без Chrome, worker — Chrome + Xvfb; rabbitmq и redis — сторонние, кладутся в релиз
|
||||
REGISTRY := docker.sokolab.xyz
|
||||
@@ -18,6 +18,24 @@ DEPS_IMAGES := rabbitmq:4.1-alpine redis:8.2-alpine postgres:17-alpine
|
||||
API_URL ?= http://localhost:8000
|
||||
API_KEY ?=
|
||||
|
||||
# make dev (#90): rabbitmq, redis, postgres — в Docker с портами на 127.0.0.1 (docker-compose.dev.yml), api и worker —
|
||||
# на хосте, Chrome открывается на рабочем столе. Порты нестандартные: 5672/5432/8000 часто заняты другими проектами.
|
||||
DEV_COMPOSE := docker compose -f docker-compose.yml -f docker-compose.dev.yml
|
||||
DEV_API_PORT ?= 8010
|
||||
DEV_RABBITMQ_PORT ?= 5673
|
||||
DEV_REDIS_PORT ?= 6380
|
||||
DEV_POSTGRES_PORT ?= 5433
|
||||
# окон Chrome на рабочем столе; потоков dramatiq — вдвое больше, как в Dockerfile
|
||||
DEV_SLOTS ?= 2
|
||||
RABBITMQ_PASSWORD ?= robotex
|
||||
POSTGRES_PASSWORD ?= robotex
|
||||
export DEV_RABBITMQ_PORT DEV_REDIS_PORT DEV_POSTGRES_PORT RABBITMQ_PASSWORD POSTGRES_PASSWORD
|
||||
DEV_ENV := RABBITMQ_URL=amqp://robotex:$(RABBITMQ_PASSWORD)@localhost:$(DEV_RABBITMQ_PORT)/ \
|
||||
REDIS_URL=redis://localhost:$(DEV_REDIS_PORT)/0 \
|
||||
DATABASE_URL=postgresql+asyncpg://robotex:$(POSTGRES_PASSWORD)@localhost:$(DEV_POSTGRES_PORT)/robotex \
|
||||
BROWSER_SLOTS=$(DEV_SLOTS) \
|
||||
LOG_LEVEL=DEBUG
|
||||
|
||||
help:
|
||||
@echo "make install <pkg> [<pkg> ...] - установить пакет(ы) в $(VENV), обновить $(REQUIREMENTS), собрать wheel в $(WHEELS_DIR)/"
|
||||
@echo "make sync - установить все пакеты из $(REQUIREMENTS) (из индекса)"
|
||||
@@ -27,10 +45,13 @@ help:
|
||||
@echo "make clean - удалить $(WHEELS_DIR)/"
|
||||
@echo "make build [ver=latest] - собрать образы $(API_IMAGE):<ver> и $(WORKER_IMAGE):<ver>"
|
||||
@echo "make push ver=<ver> - собрать образы и отправить в $(REGISTRY)"
|
||||
@echo "make release ver=<ver> - собрать образы и пакет для деплоя в build/<ver>/ (compose, .env, образы .tar.gz)"
|
||||
@echo "make release ver=<ver> [out=registry|file] - собрать образы и пакет для деплоя в build/<ver>/ (compose, .env);"
|
||||
@echo " образы: registry — push в $(REGISTRY) (по умолчанию), file — архив .tar.gz"
|
||||
@echo "make up - собрать и запустить локально: api, worker, rabbitmq, redis"
|
||||
@echo "make down - остановить локальный стенд"
|
||||
@echo "make logs - логи api и worker"
|
||||
@echo "make dev - api и worker на хосте с видимым Chrome, rabbitmq/redis/postgres в Docker; API на :$(DEV_API_PORT)"
|
||||
@echo "make dev-key name=<клиент> - выпустить API-ключ клиенту при make dev"
|
||||
@echo "make smoke API_KEY=... [API_URL=...] - проверить запущенный стенд: ключ, /v1/solve, /v2/tasks, удержание окна"
|
||||
@echo "make key-create name=<клиент> - выпустить API-ключ клиенту (в запущенном compose)"
|
||||
@echo "make key-list - клиенты и их ключи (без самих ключей)"
|
||||
@@ -88,6 +109,21 @@ down:
|
||||
logs:
|
||||
docker compose logs -f api worker
|
||||
|
||||
# контейнерные api и worker останавливаются: иначе worker в Docker тоже берёт задачи из общей очереди и выполняет их невидимо
|
||||
dev-deps:
|
||||
-docker compose stop api worker
|
||||
$(DEV_COMPOSE) up -d --wait rabbitmq redis postgres
|
||||
|
||||
# Ctrl+C останавливает оба процесса; инфраструктура остаётся запущенной (make down — остановить)
|
||||
dev: dev-deps
|
||||
cd src && export $(DEV_ENV) && \
|
||||
{ $(PYTHON) -m uvicorn main:app --reload --port $(DEV_API_PORT) & api=$$!; \
|
||||
trap 'kill $$api 2>/dev/null' EXIT INT TERM; \
|
||||
$(PYTHON) -m dramatiq worker.actors --processes 1 --threads $$(( $(DEV_SLOTS) * 2 )); }
|
||||
|
||||
dev-key: dev-deps
|
||||
cd src && $(DEV_ENV) $(PYTHON) -m db.cli create "$(name)"
|
||||
|
||||
smoke:
|
||||
$(PYTHON) tests/smoke.py $(API_URL) $(API_KEY)
|
||||
|
||||
@@ -123,33 +159,43 @@ test-browser: build
|
||||
xvfb-run -a --server-args="-screen 0 1920x1080x24" python -m pytest -m browser -p no:cacheprovider -s'
|
||||
|
||||
RELEASE_DIR := build/$(ver)
|
||||
# куда деваются образы при make release: registry — push в $(REGISTRY), file — архив .tar.gz в $(RELEASE_DIR)/
|
||||
out ?= registry
|
||||
|
||||
ifneq ($(filter release push,$(MAKECMDGOALS)),)
|
||||
ifeq ($(ver),latest)
|
||||
$(error Укажите версию: make release ver=v0.0.2)
|
||||
endif
|
||||
ifeq ($(filter $(out),registry file),)
|
||||
$(error out=registry или out=file, а не "$(out)")
|
||||
endif
|
||||
endif
|
||||
|
||||
release: build
|
||||
@echo "make release $(ver) -> $(RELEASE_DIR)/"
|
||||
ifeq ($(out),file)
|
||||
RELEASE_RUN := docker load -i robotex-$(ver).tar.gz
|
||||
else
|
||||
RELEASE_RUN := docker compose pull
|
||||
endif
|
||||
|
||||
release: $(if $(filter file,$(out)),build,push)
|
||||
@echo "make release $(ver) [out=$(out)] -> $(RELEASE_DIR)/"
|
||||
@mkdir -p $(RELEASE_DIR)
|
||||
sed -e 's|image: .*$(API_IMAGE):.*|image: $(REGISTRY)/$(API_IMAGE):$(ver)|' \
|
||||
-e 's|image: .*$(WORKER_IMAGE):.*|image: $(REGISTRY)/$(WORKER_IMAGE):$(ver)|' \
|
||||
docker-compose.prod.yml > $(RELEASE_DIR)/docker-compose.yml
|
||||
cp dot_env $(RELEASE_DIR)/.env
|
||||
ifeq ($(out),file)
|
||||
# сторонние образы (rabbitmq, redis, postgres) кладутся в архив; при out=registry сервер берёт их с Docker Hub
|
||||
for img in $(DEPS_IMAGES); do docker image inspect $$img >/dev/null 2>&1 || docker pull $$img; done
|
||||
# TODO: я хочу парамтером управлять сохранить в файл или отправить в свой реджестри
|
||||
# if produce == file:
|
||||
docker tag $(API_IMAGE):$(ver) $(REGISTRY)/$(API_IMAGE):$(ver)
|
||||
docker tag $(API_IMAGE):$(ver) $(REGISTRY)/$(API_IMAGE):$(ver)
|
||||
docker tag $(WORKER_IMAGE):$(ver) $(REGISTRY)/$(WORKER_IMAGE):$(ver)
|
||||
docker save $(REGISTRY)/$(API_IMAGE):$(ver) $(REGISTRY)/$(WORKER_IMAGE):$(ver) $(DEPS_IMAGES) | gzip > $(RELEASE_DIR)/robotex-$(ver).tar.gz
|
||||
# else:
|
||||
# docker push docker.sokolab.xyz/$(API_IMAGE):$(ver) docker.sokolab.xyz/$(WORKER_IMAGE):$(ver) docker.sokolab.xyz/$(DEPS_IMAGES)
|
||||
endif
|
||||
@printf '%s\n' \
|
||||
'# Robotex $(ver)' \
|
||||
'' \
|
||||
'```bash' \
|
||||
'docker load -i robotex-$(ver).tar.gz' \
|
||||
'$(RELEASE_RUN)' \
|
||||
'docker compose up -d' \
|
||||
'```' \
|
||||
'' \
|
||||
|
||||
@@ -167,14 +167,17 @@ Not done yet:
|
||||
## Local development
|
||||
|
||||
```bash
|
||||
make sync-offline # venv in ~/.py_envs/robotex; another path: make VENV=... <target>
|
||||
docker compose up -d rabbitmq redis # queue and storage
|
||||
cd src && RABBITMQ_URL=... REDIS_URL=... uvicorn main:app --reload
|
||||
cd src && dramatiq worker.actors --processes 1 --threads 4
|
||||
make sync-offline # venv in ~/.py_envs/robotex; another path: make VENV=... <target>
|
||||
make dev # api + worker on the host, Chrome windows visible on your desktop; API on :8010
|
||||
make dev-key name=local # API key for it (stored in the same postgres volume as make up)
|
||||
```
|
||||
|
||||
The compose network is not published, so for running the services on the host publish `5672` and `6379` yourself,
|
||||
or just use `make up`. Chrome runs headful, so a display is required (locally — your desktop, in Docker — Xvfb).
|
||||
`make dev` stops the containerized api and worker (otherwise the Docker worker also takes tasks from the common
|
||||
queue and runs them invisibly), starts rabbitmq, redis and postgres with ports published on 127.0.0.1
|
||||
(`docker-compose.dev.yml`: 5673, 6380, 5433), then runs uvicorn with `--reload` and the dramatiq worker in the
|
||||
foreground. Ctrl+C stops both; `make down` stops the infrastructure. Ports, slots: `DEV_API_PORT`,
|
||||
`DEV_RABBITMQ_PORT`, `DEV_REDIS_PORT`, `DEV_POSTGRES_PORT`, `DEV_SLOTS` (default 2). Chrome runs headful, so a display
|
||||
is required (locally — your desktop, in Docker — Xvfb).
|
||||
|
||||
Tests:
|
||||
|
||||
@@ -201,7 +204,8 @@ Releases:
|
||||
|
||||
```bash
|
||||
make build ver=v0.1.0 # docker images robotex-api:v0.1.0 and robotex-worker:v0.1.0
|
||||
make release ver=v0.1.0 # images + deploy bundle in build/v0.1.0/ (compose, .env, images .tar.gz)
|
||||
make release ver=v0.1.0 # push images to docker.sokolab.xyz + deploy bundle in build/v0.1.0/ (compose, .env)
|
||||
make release ver=v0.1.0 out=file # same, but images go to build/v0.1.0/robotex-v0.1.0.tar.gz instead of the registry
|
||||
```
|
||||
|
||||
Details (Xvfb, tini, `/dev/shm`, env settings, load measurements) — [docs/Deployment.md](docs/Deployment.md).
|
||||
@@ -0,0 +1,15 @@
|
||||
# Локальная разработка (make dev, #90): поверх docker-compose.yml открывает порты rabbitmq, redis и postgres
|
||||
# на 127.0.0.1, чтобы api и worker запускались на хосте — с видимым окном Chrome на рабочем столе.
|
||||
# Порты нестандартные: 5672/5432 на хосте часто заняты другими проектами.
|
||||
services:
|
||||
rabbitmq:
|
||||
ports:
|
||||
- "127.0.0.1:${DEV_RABBITMQ_PORT:-5673}:5672"
|
||||
|
||||
redis:
|
||||
ports:
|
||||
- "127.0.0.1:${DEV_REDIS_PORT:-6380}:6379"
|
||||
|
||||
postgres:
|
||||
ports:
|
||||
- "127.0.0.1:${DEV_POSTGRES_PORT:-5433}:5432"
|
||||
+3
-1
@@ -23,8 +23,10 @@ async def click_and_race(page, element, challenge_locator, timeout=30_000):
|
||||
timeout=timeout,
|
||||
)
|
||||
)
|
||||
# challenge_locator должен совпадать только с открытым окном челленджа: элементы челленджа могут быть в DOM
|
||||
# заранее (reCAPTCHA держит их за экраном), поэтому ждём видимости, а не появления
|
||||
challenge_task = asyncio.create_task(
|
||||
challenge_locator.wait_for(state="attached", timeout=timeout)
|
||||
challenge_locator.first.wait_for(state="visible", timeout=timeout)
|
||||
)
|
||||
|
||||
await element.click()
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import json
|
||||
import logging
|
||||
|
||||
from patchright.async_api import Page
|
||||
@@ -6,12 +5,26 @@ from patchright.async_api import Page
|
||||
from captcha.base import Captcha
|
||||
|
||||
from ..base import Detector, ChallengeInfo, Solver, AntiBotProvider, click_and_race
|
||||
from ..base import Stage
|
||||
from ..base import Stage, PWTimeoutError
|
||||
from ..registry import register
|
||||
from ..exceptions import IframeLoadError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# iframe окна с картинками (bframe) вставляется в DOM вместе с чекбоксом, но его контейнер стоит за экраном:
|
||||
# style="visibility: hidden; ... top: -10000px". Когда выпадает челлендж, контейнер переезжает на экран и
|
||||
# становится видимым — локатор совпадает только с открытым окном
|
||||
INTERACTIVE_MARKER_SELECTOR = (
|
||||
"xpath=//iframe[contains(@src, '/bframe')][not(ancestor::div[contains(@style, 'top: -10000px')])]"
|
||||
)
|
||||
|
||||
# признак reCAPTCHA, который есть уже в исходном HTML, до загрузки iframe с чекбоксом (он грузится долго):
|
||||
# скрипт api.js / enterprise.js, контейнер .g-recaptcha (у render=explicit его может не быть) или сам iframe.
|
||||
# #recaptcha-token не подходит — он внутри iframe чекбокса, с основной страницы его не видно
|
||||
DETECT_MARKER_SELECTOR = (
|
||||
"script[src*='/recaptcha/api.js'], script[src*='/recaptcha/enterprise.js'], "
|
||||
".g-recaptcha, iframe[title='reCAPTCHA']"
|
||||
)
|
||||
|
||||
JS_CHALLENGE_SUBMIT = """(token) => {
|
||||
const clients = window.___grecaptcha_cfg && window.___grecaptcha_cfg.clients;
|
||||
@@ -42,8 +55,8 @@ class ReCaptchaSolver(Solver):
|
||||
self.captcha_solver: Captcha = self._opt["captcha_solver"]
|
||||
|
||||
async def form_submit(self, token: str) -> bool:
|
||||
# noinspection UnnecessaryLabelJS
|
||||
return await self.page.evaluate(f"mw:({JS_CHALLENGE_SUBMIT})({json.dumps(token)})")
|
||||
# ___grecaptcha_cfg живёт в основном мире страницы, а patchright по умолчанию выполняет скрипт в изолированном
|
||||
return await self.page.evaluate(JS_CHALLENGE_SUBMIT, token, isolated_context=False)
|
||||
|
||||
async def solve(self, page: Page, info: ChallengeInfo):
|
||||
self.page = page
|
||||
@@ -55,18 +68,22 @@ class ReCaptchaSolver(Solver):
|
||||
log.debug("Нужно тыкнуть чекбокс")
|
||||
checkbox_selector = "#recaptcha-anchor"
|
||||
element = page.frame_locator("iframe[title='reCAPTCHA']").locator(checkbox_selector)
|
||||
# TODO: маркер всегда присутствует
|
||||
interactive_marker_selector = "//div[@class='g-recaptcha-bubble-arrow']/..//iframe"
|
||||
challenge_locator = page.locator(interactive_marker_selector)
|
||||
challenge_locator = page.locator(INTERACTIVE_MARKER_SELECTOR)
|
||||
result = await click_and_race(page, element, challenge_locator)
|
||||
log.debug(f"Ожидание завершено: {result}")
|
||||
log.debug("Возврат к детекту")
|
||||
return
|
||||
else:
|
||||
log.debug("Нужно решать капчу. Закрою окошко с картинками.")
|
||||
# полупрозрачная подложка на весь экран (position: fixed), клик по ней закрывает окно. В её центре лежит
|
||||
# само окно с картинками и перехватывает клик — поэтому кликаем в угол
|
||||
element = page.locator("xpath=//div[@class='g-recaptcha-bubble-arrow']/../div[1]")
|
||||
if await element.is_visible():
|
||||
await element.click()
|
||||
try:
|
||||
await element.click(position={"x": 1, "y": 1}, timeout=5_000)
|
||||
except PWTimeoutError:
|
||||
# не закрылось — не страшно, токен уходит через callback
|
||||
log.debug("Окошко с картинками закрыть не удалось")
|
||||
|
||||
log.debug("Решаем капчу...")
|
||||
assert info.detect_locator
|
||||
@@ -87,10 +104,9 @@ class ReCaptchaDetector(Detector):
|
||||
log.debug("ReCaptchaDetector: попытка определить есть ли преграда")
|
||||
info.meta = {}
|
||||
checkbox_selector = "#recaptcha-anchor"
|
||||
interactive_marker_selector = "//div[@class='g-recaptcha-bubble-arrow']/..//iframe"
|
||||
|
||||
# попытка определить что мы вообще на странице блокировки
|
||||
marker_selector = info.detect_locator or "#captcha-form"
|
||||
marker_selector = info.detect_locator or DETECT_MARKER_SELECTOR
|
||||
info.detect_locator = marker_selector
|
||||
|
||||
log.debug(f"ищем по detect: {marker_selector} success: {info.success_locator}")
|
||||
@@ -111,7 +127,7 @@ class ReCaptchaDetector(Detector):
|
||||
|
||||
log.debug("Вижу что это страница с проверкой. Определяю состояние.")
|
||||
iframe = page.frame_locator("iframe[title='reCAPTCHA']")
|
||||
# ожидаем когда будет загружен чекбокс
|
||||
# маркер есть — значит капча будет, ждём пока догрузится iframe с чекбоксом
|
||||
|
||||
info.stage = Stage.INTERSTITIAL
|
||||
checkbox = iframe.locator(checkbox_selector)
|
||||
@@ -126,7 +142,7 @@ class ReCaptchaDetector(Detector):
|
||||
log.debug("Чек бокс найден")
|
||||
info.stage = Stage.CHECKBOX
|
||||
|
||||
if await page.locator(interactive_marker_selector).count() > 0:
|
||||
if await page.locator(INTERACTIVE_MARKER_SELECTOR).first.is_visible():
|
||||
log.debug("Нужно решать капчу")
|
||||
# нужно решение
|
||||
info.stage = Stage.INTERACTIVE
|
||||
|
||||
@@ -30,6 +30,7 @@ class Captcha:
|
||||
return resp
|
||||
|
||||
async def turnstile(self, url, site_key):
|
||||
# TODO: not finished!
|
||||
resp = await self._run(self._solver.turnstile, site_key, url)
|
||||
return resp
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ class SolverReCaptcha(Captcha):
|
||||
:param page:
|
||||
:return:
|
||||
"""
|
||||
div_container = page.locator(selector)
|
||||
await div_container.wait_for()
|
||||
# selector может указывать на невидимый элемент (script) и совпадать с несколькими
|
||||
await page.locator(selector).first.wait_for(state="attached")
|
||||
|
||||
site_key_input_element = page.locator("iframe[title='reCAPTCHA']")
|
||||
src_value = await site_key_input_element.get_attribute("src")
|
||||
|
||||
@@ -10,10 +10,13 @@ import logging
|
||||
import os
|
||||
import socket
|
||||
|
||||
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
|
||||
logging.basicConfig(
|
||||
level=os.getenv("LOG_LEVEL", "INFO").upper(),
|
||||
level=LOG_LEVEL,
|
||||
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
||||
)
|
||||
# под dramatiq логирование уже настроено им самим (уровень INFO) и basicConfig ничего не делает — уровень явно
|
||||
logging.getLogger().setLevel(LOG_LEVEL)
|
||||
|
||||
import dramatiq # noqa: E402
|
||||
from dramatiq.asyncio import get_event_loop_thread # noqa: E402
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"dev": {
|
||||
"api_key": "rbx_...",
|
||||
"proxy": "http://user:pass@host:port",
|
||||
"captcha_key": "<ключ 2captcha>"
|
||||
}
|
||||
}
|
||||
+31
-8
@@ -1,4 +1,6 @@
|
||||
# Test your FastAPI endpoints
|
||||
# Секреты (api_key, proxy, captcha_key) — в tests/http-client.private.env.json (не в git, шаблон —
|
||||
# http-client.private.env.example.json); окружение dev выбирается в PyCharm над запросом.
|
||||
|
||||
GET http://127.0.0.1:8000/
|
||||
Accept: application/json
|
||||
@@ -13,7 +15,7 @@ Accept: application/json
|
||||
POST http://127.0.0.1:8000/v1/solve
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
X-API-Key: <key>
|
||||
X-API-Key: {{api_key}}
|
||||
|
||||
|
||||
{
|
||||
@@ -36,7 +38,7 @@ X-API-Key: <key>
|
||||
POST http://127.0.0.1:8000/v1/solve
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
X-API-Key: <key>
|
||||
X-API-Key: {{api_key}}
|
||||
Cookie: session_id=2f2100be-6ce7-40d5-bae5-61424b2504c8; Path=/; SameSite=lax
|
||||
|
||||
|
||||
@@ -53,7 +55,7 @@ Cookie: session_id=2f2100be-6ce7-40d5-bae5-61424b2504c8; Path=/; SameSite=lax
|
||||
POST http://127.0.0.1:8000/v1/solve
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
X-API-Key: <key>
|
||||
X-API-Key: {{api_key}}
|
||||
Cookie: session_id=2f2100be-6ce7-40d5-bae5-61424b2504c8; Path=/; SameSite=lax
|
||||
|
||||
|
||||
@@ -72,7 +74,7 @@ Cookie: session_id=2f2100be-6ce7-40d5-bae5-61424b2504c8; Path=/; SameSite=lax
|
||||
POST http://127.0.0.1:8000/v1/solve
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
X-API-Key: <key>
|
||||
X-API-Key: {{api_key}}
|
||||
Cookie: session_id=2f2100be-6ce7-40d5-bae5-61424b2504c8; Path=/; SameSite=lax
|
||||
|
||||
|
||||
@@ -91,7 +93,7 @@ Cookie: session_id=2f2100be-6ce7-40d5-bae5-61424b2504c8; Path=/; SameSite=lax
|
||||
POST http://127.0.0.1:8000/v1/solve
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
X-API-Key: <key>
|
||||
X-API-Key: {{api_key}}
|
||||
|
||||
|
||||
{
|
||||
@@ -112,11 +114,11 @@ X-API-Key: <key>
|
||||
POST http://localhost:8002/v2/tasks
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
X-API-Key: <key>
|
||||
X-API-Key: {{api_key}}
|
||||
|
||||
{
|
||||
"url": "https://freelancehunt.com/",
|
||||
"proxy": "http://BQLo3X:poqAWX@168.81.43.154:8000",
|
||||
"proxy": "{{proxy}}",
|
||||
"actions": [
|
||||
{
|
||||
"type": "inner_text",
|
||||
@@ -132,6 +134,27 @@ X-API-Key: <key>
|
||||
GET http://localhost:8002/v2/tasks/4d8105f8-93a8-43ee-aa6e-db917451363b
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
X-API-Key: <key>
|
||||
X-API-Key: {{api_key}}
|
||||
|
||||
###
|
||||
# @timeout 600
|
||||
POST http://localhost:8010/v1/solve
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
X-API-Key: {{api_key}}
|
||||
|
||||
{
|
||||
"url": "https://lessons.zennolab.com/captchas/recaptcha/v2_nosubmit.php?level=low",
|
||||
"proxy": "{{proxy}}",
|
||||
"actions": [
|
||||
{
|
||||
"type": "inner_text",
|
||||
"selector": "//div[@id='main']/div/h2"
|
||||
}
|
||||
],
|
||||
"captcha": "{{captcha_key}}",
|
||||
"captcha_type": "recaptcha",
|
||||
"captcha_success_locator": "//div[@id='main']"
|
||||
}
|
||||
|
||||
###
|
||||
Reference in new issue
Block a user