Как запустить сервис решения капчи на FastAPI в Windows

Чтобы запустить сервис решения капчи на FastAPI, создайте один клиент AsyncCapSkip при запуске приложения. Откройте POST-эндпоинт, который принимает sitekey и URL страницы. Оберните каждое решение в asyncio.timeout и Semaphore и сопоставьте исключения SDK с HTTP-статусами. Парсеры, воркеры и скрипты на любом языке тогда обращаются к одному внутреннему адресу и никогда не видят порт и ключ CapSkip. CapSkip работает на вашей собственной машине с Windows, поэтому сервис не добавляет платы за каждое решение. В этом руководстве он собирается примерно в 100 строк, и показано, что делает каждая часть, когда CapSkip работает медленно, закрыт или молчит.
Что понадобится
- CapSkip, запущенный на ПК с Windows, с включённым решателем reCAPTCHA. Пошаговое руководство по настройке проведёт вас через первый запуск.
- Python 3.11 или новее, потому что код использует asyncio.timeout. Всё ниже запускалось на Windows 11 с Python 3.14.0, capskip 1.3.0, fastapi 0.150.0 и uvicorn 0.54.0.
- Окно PowerShell. Команды написаны в синтаксисе PowerShell.
- Страница с чекбоксом reCAPTCHA v2. Сервису нужны только её sitekey и её адрес.
- Адрес CapSkip. В режиме Local он отвечает на 127.0.0.1 и только для этого устройства. В режиме Server он слушает ваш сетевой адрес или публичный IP, чтобы сервис на другой машине мог обращаться к нему через API. Оба режима задаются в разделе Настройки подключения.
Числа в этом руководстве получены на имитации CapSkip на том же ПК. FastAPI, uvicorn и SDK настоящие. Имитация удерживает каждое решение заданное время, обычно две секунды, поэтому ваши реальные решения будут отличаться. Ничего из этого не запускалось ни с живым CapSkip, ни на живом сайте.
Зачем ставить сервис перед CapSkip
Если капчу решает один скрипт на Python, вызывайте SDK прямо из него. На странице сервиса распознавания капчи для Python это описано, а в руководстве по параллельному решению разобрано, как выполнять много решений в одном процессе. Сервис окупается, когда одним CapSkip пользуются несколько вызывающих сторон. Тогда четыре задачи логично решать в одном месте.
- Один дедлайн. SDK для Python по умолчанию ждёт до 300 секунд. Его обычный клиент вообще не задаёт HTTP-таймаут. Каждой вызывающей стороне не нужно иметь собственный таймер.
- Один лимит. CapSkip выполняет фиксированное число решений одновременно, а лишние задания ждут внутри него. Очередь, которую вы контролируете, понятнее.
- Один ключ. Вызывающие стороны получают ключ сервиса. Адрес и API-ключ CapSkip остаются в настройках сервиса.
- Любой язык. Воркер на C#, сайт на PHP и задача PowerShell одинаково работают с HTTP и JSON.
Шаг 1: устанавливаем пакеты
Сначала создайте папку и виртуальное окружение, если вы держите по одному на проект. Пакет capskip подтягивает httpx, который использует асинхронный клиент.
pip install capskip fastapi uvicorn
Шаг 2: задаём настройки переменными окружения
Сам SDK не читает переменные окружения, поэтому сервис читает их и передаёт каждое значение в SDK. Задайте их в окне PowerShell, из которого будет запускаться сервис. Замените оба ключа-заглушки. YOUR_CAPSKIP_KEY означает API-ключ из настроек CapSkip, и подойдёт любой текст, пока проверка ключа в CapSkip выключена. YOUR_API_KEY означает ключ, который вы придумываете для своих вызывающих сторон.
# Where CapSkip listens and which key it expects. $env:CAPSKIP_HOST = "127.0.0.1" $env:CAPSKIP_PORT = "8080" $env:CAPSKIP_API_KEY = "YOUR_CAPSKIP_KEY" # Match Max. Threads in the CapSkip settings. $env:CAPSKIP_MAX_THREADS = "10" # The key your callers must send to this service. $env:SOLVER_API_KEY = "YOUR_API_KEY" # Seconds a caller may wait, queue time included. $env:SOLVE_DEADLINE = "120"
Используйте 127.0.0.1 в качестве хоста. С этим клиентом отказ в подключении занял около 2.4 секунды на 127.0.0.1 и около 2.7 секунды на localhost. Обычный клиент requests занял около 4 секунд на localhost в более раннем тесте.
Шаг 3: читаем настройки
Создайте main.py. Блоки в шагах с 3 по 10 являются последовательными частями этого единственного файла. Полный файл в конце собирает их импорты вверху.
import asyncio
import os
from contextlib import asynccontextmanager
from capskip import AsyncCapSkip
from fastapi import FastAPI
# The SDK reads no env vars, so pass each setting in by hand.
HOST = os.getenv("CAPSKIP_HOST", "127.0.0.1")
PORT = int(os.getenv("CAPSKIP_PORT", "8080"))
KEY = os.getenv("CAPSKIP_API_KEY", "capskip")
MAX_THREADS = int(os.getenv("CAPSKIP_MAX_THREADS", "10"))
DEADLINE = float(os.getenv("SOLVE_DEADLINE", "120"))Шаг 4: создаём один клиент и один шлюз при запуске приложения
Функция lifespan выполняется один раз при запуске. Она создаёт клиент и Semaphore и кладёт оба в состояние приложения, чтобы их разделял каждый запрос. Дайте Semaphore по одному слоту на каждый поток CapSkip, то есть значение Max. Threads в CapSkip (по умолчанию 10).
AsyncCapSkip является настоящим асинхронным клиентом в Python. Он построен на httpx и опрашивает через asyncio.sleep, поэтому ожидание между опросами не блокирует цикл событий. Сами вызовы всё же требуют некоторого времени процессора, как объясняет раздел «Одна издержка, о которой стоит знать». Обычный клиент CapSkip вызывает requests без таймаута, поэтому молчащий CapSkip удерживал бы поток вечно. Если задать recaptchaTimeout равным дедлайну, собственные часы SDK будут идти так же, как наши. Если оба срока совпадают, первым срабатывает asyncio.timeout.
@asynccontextmanager
async def lifespan(app: FastAPI):
# One client and one gate, shared by every request.
app.state.cap = AsyncCapSkip(
apiKey=KEY, host=HOST, port=PORT,
recaptchaTimeout=DEADLINE,
)
app.state.slots = asyncio.Semaphore(MAX_THREADS)
yield
app = FastAPI(lifespan=lifespan)Шаг 5: проверяем ключ сервиса
Незащищённый эндпоинт решения на сетевом адресе превращается в бесплатный решатель для всех, кто может достучаться до порта. Эта зависимость сравнивает заголовок X-API-Key с помощью secrets.compare_digest, который выполняется за одно и то же время, где бы ни различались байты. Отсутствующий, пустой, неверный, более длинный и содержащий не-ASCII символы ключ на имитации дали 401. Верный ключ дал 200.
Проверка в начале не даёт сервису запуститься с пустым ключом. Без неё запрос без заголовка получал бы по умолчанию пустую строку и совпадал. Зависимость выполняется до проверки тела, поэтому запрос без ключа и с телом, не прошедшим валидацию, получает 401, а не 422. Тело, которое не является корректным JSON, всё равно получает 422, потому что FastAPI сначала декодирует JSON. Вызывающие стороны без ключа узнают о вашей схеме мало.
import secrets
from fastapi import Depends, Header, HTTPException
SERVICE_KEY = os.environ["SOLVER_API_KEY"]
if not SERVICE_KEY:
raise SystemExit("SOLVER_API_KEY must not be empty")
def require_key(x_api_key: str = Header(default="")):
good = secrets.compare_digest(
x_api_key.encode(), SERVICE_KEY.encode())
if not good:
raise HTTPException(
401, "missing or wrong X-API-Key",
headers={"WWW-Authenticate": "APIKey"})Шаг 6: проверяем запрос и задаём дедлайн
Модель запроса отклоняет пустой sitekey и URL, который не начинается с http. На некорректное тело FastAPI отвечает собственным 422 и перечисляет каждую проблему.
Здесь важнее всего дедлайн. asyncio.timeout охватывает ожидание слота и решение вместе. Сервис при небольшой нагрузке отвечает в сумме в пределах DEADLINE секунд. Если бы таймаут охватывал только решение, запрос мог бы простоять в очереди минуты, прежде чем его часы вообще начали бы идти. При большой нагрузке ответ может прийти позже, как объясняет раздел «Одна издержка, о которой стоит знать».
from fastapi import Request
from pydantic import BaseModel, Field
class SolveRequest(BaseModel):
sitekey: str = Field(min_length=1)
url: str = Field(pattern=r"^https?://")
async def solve_v2(state, sitekey: str, url: str) -> str:
# The deadline also covers the wait for a free slot.
async with asyncio.timeout(DEADLINE):
async with state.slots:
result = await state.cap.recaptcha(sitekey, url)
return result["code"]Шаг 7: добавляем эндпоинт решения
Эндпоинт занимает три строки. Сначала выполняется зависимость с проверкой ключа, затем решение, затем токен возвращается как JSON.
@app.post("/solve", dependencies=[Depends(require_key)])
async def solve(body: SolveRequest, request: Request):
token = await solve_v2(request.app.state, body.sitekey, body.url)
return {"token": token}Шаг 8: сопоставляем исключения SDK с HTTP-статусами
SDK выбрасывает четыре класса исключений, все они наследуют CapSkipError, и все они импортируются из capskip. Один обработчик для базового класса превращает каждое в код статуса и короткое имя ошибки. Подробности уходят в лог и никогда не доходят до вызывающей стороны, поэтому сообщения CapSkip остаются закрытыми.
import logging
from capskip import ApiException, CapSkipError, NetworkException
from capskip import TimeoutException, ValidationException
from fastapi.responses import JSONResponse
STATUS = {
NetworkException: (503, "capskip_unreachable"),
TimeoutException: (504, "capskip_timeout"),
ValidationException: (422, "bad_parameters"),
ApiException: (502, "capskip_error"),
}@app.exception_handler(CapSkipError)
async def capskip_error(request: Request, exc: CapSkipError):
# Log the detail here, keep it away from the caller.
logging.getLogger("uvicorn.error").warning("%r", exc)
status, code = STATUS.get(type(exc), (502, "capskip_error"))
if "UNSOLVABLE" in str(exc):
status, code = 422, "unsolvable"
return JSONResponse({"error": code}, status)Каждая строка ниже была вызвана намеренно на имитации.
| Статус | Что произошло | Сколько это заняло |
|---|---|---|
| 503 | CapSkip закрыт, поэтому соединение было отклонено | 2.2 s |
| 503 | CapSkip принял соединение и так и не ответил на отправку | 5.2 s |
| 422 | CapSkip сообщил ERROR_CAPTCHA_UNSOLVABLE | не замерялось |
| 502 | CapSkip отклонил собственный API-ключ сервиса | не замерялось |
| 504 | Дедлайн истёк во время опроса молчащего CapSkip (один запрос, дедлайн 10 с) | 10.0 s |
502 сделан намеренно. Отклонённый ключ CapSkip является ошибкой сервиса, а не вызывающей стороны. Строка на 5.2 секунды объясняется таймаутом httpx по умолчанию в 5 секунд, который наследует асинхронный клиент. Молчащий ответ на опрос устроен иначе. SDK считает опрос, который завершился ошибкой, признаком того, что результат ещё не готов, и пробует снова, поэтому закончить его может только дедлайн. ValidationException не может возникнуть при одних лишь sitekey и URL. Для неё нужны дополнительные параметры, например action при решении v2, которых этот сервис никогда не отправляет.
Шаг 9: отдельно перехватываем дедлайн
asyncio.timeout выбрасывает встроенное в Python исключение TimeoutError. TimeoutException из SDK является другим классом, и перехват встроенного его не ловит. Обработчик из шага 8 уже сопоставляет TimeoutException из SDK с ответом 504 по имени capskip_timeout. Этот второй обработчик покрывает дедлайн, и его имя ошибки сообщает вызывающей стороне, какой из таймеров истёк.
@app.exception_handler(TimeoutError)
async def deadline_hit(request: Request, exc: TimeoutError):
# asyncio.timeout raises the built-in TimeoutError. The SDK's
# TimeoutException is a different class, so it needs its own entry.
return JSONResponse({"error": "deadline_exceeded"}, 504)Шаг 10: добавляем проверку работоспособности
У CapSkip нет вызова ping, поэтому проверка открывает TCP-соединение с его портом и прекращает попытки через три секунды. При закрытом CapSkip она отвечала 503 примерно через 2 секунды, а при запущенной имитации сразу отвечала 200. Она сообщает скрипту мониторинга, что сервис работает, а CapSkip нет. Она не доказывает, что решение работает.
@app.get("/health")
async def health():
# CapSkip has no ping call, so check that its port accepts a
# connection.
try:
_, writer = await asyncio.wait_for(
asyncio.open_connection(HOST, PORT), timeout=3)
except (OSError, TimeoutError):
return JSONResponse({"ok": False}, 503)
writer.close()
return {"ok": True}Запуск в Windows
Запускайте uvicorn из папки, в которой лежит main.py. Если слушать 127.0.0.1, сервис отвечает только на этом ПК.
python -m uvicorn main:app --host 127.0.0.1 --port 8000
Для вызывающих сторон на других машинах смените хост на 0.0.0.0, то есть каждый сетевой адаптер. На тестовом ПК он отвечал по адресу этого ПК в сети LAN, а привязка к 127.0.0.1 такое обращение отклоняла. Сначала задайте настоящий ключ сервиса и проверьте, какое правило брандмауэра Windows создаёт для порта. Ключ сервиса передаётся открытым текстом по обычному HTTP, поэтому используйте это только в сети, которой вы доверяете. Запрос брандмауэра здесь не наблюдался, и доступ с другой машины не проверялся.
Запускайте один воркер. При параметре workers, равном 2, и лимите 3 на каждый имитация зафиксировала до 6 решений одновременно. Каждый воркер является отдельным процессом со своим Semaphore, поэтому лимит умножается. Параметр reload безопасно использовать, пока вы правите код. Изменение файла посреди решения не сбросило выполняющийся запрос, и новый запрос через 1.5 секунды тоже прошёл успешно.
Страницы документации по умолчанию открыты. Если сервис слушает сетевой адрес, создавайте приложение с docs_url и openapi_url, равными None, чтобы схема не была общедоступной.
Запуск сервиса отдельно от CapSkip
Сервис не обязан работать на том же ПК. Переведите CapSkip в режим Server на машине с Windows, которую вы контролируете, желательно со статическим публичным IP. Перед запуском uvicorn задайте CAPSKIP_HOST и CAPSKIP_PORT с этим адресом. Нужны три предосторожности. Включите проверку API-ключа в CapSkip и используйте тот же ключ в CAPSKIP_API_KEY. SDK работает по обычному HTTP, поэтому этот ключ передаётся по сети открытым текстом. CapSkip к тому же добавляет для своего порта правило, разрешающее любой адрес, поэтому добавьте блокирующее правило для каждого адреса, которому доступа к нему быть не должно. В разделе Настройки подключения руководства по настройке описаны оба режима.
Вызов из PowerShell
Вызывающая сторона отправляет sitekey, URL страницы и ключ сервиса. В ответ приходит JSON-объект с одним полем, token. Используйте токен в течение примерно двух минут и только один раз. В руководстве по сроку действия токена объясняется, как идёт отсчёт времени. Адрес ниже служит заглушкой для вашего сервиса.
Invoke-RestMethod -Method Post -Uri http://192.168.1.50:8000/solve `
-Headers @{ "X-API-Key" = "YOUR_API_KEY" } `
-ContentType "application/json" `
-Body '{"sitekey":"YOUR_SITEKEY","url":"https://example.com/page-with-recaptcha"}'POST на /solve/ с завершающим слэшем получает перенаправление 307. Invoke-RestMethod в Windows PowerShell 5.1 последовал за ним и сохранил тело и заголовок. Если вы предпочитаете curl.exe, передавайте JSON через stdin. Привычный аргумент с JSON в одинарных кавычках в PowerShell 5.1 не работает, потому что внутренние двойные кавычки удаляются, и сервис ответил ошибкой JSON.
# curl.exe reads the JSON from stdin, so no quote escaping is needed.
$body = '{"sitekey":"YOUR_SITEKEY","url":"https://example.com/page-with-recaptcha"}'
$body | curl.exe -s http://192.168.1.50:8000/solve `
-H "X-API-Key: YOUR_API_KEY" `
-H "Content-Type: application/json" `
--data-binary "@-"Что шлюз делает и чего не делает
Лимит потоков имитации был равен 3, а десять запросов пришли одновременно, и каждому требовалось решение в 2 секунды. Шлюз удерживал имитацию на трёх одновременных решениях, и все десять вернули 200. Без работающего шлюза все десять заданий доходили до CapSkip и вставали там в очередь. В имитации четыре из них сдались, прождав дольше её трёхсекундного лимита ожидания, и вернулись как нерешаемые. Такой ответ относится к собственной модели имитации и на настоящем CapSkip не подтверждался.
Ожидание слота находится внутри дедлайна не случайно. При дедлайне 5 секунд, решении в 1 секунду и десяти одновременных запросах шесть вернули 200, а четыре вернули 504. Один запрос так и не дошёл до CapSkip, а три из девяти дошедших были брошены посреди решения.
Слот удерживается чуть дольше самого решения. SDK делает опрос сразу, затем ждёт 0.25, 0.5, 1, 2 и 4 секунды, а потом по 5 секунд между опросами. Опросы приходятся на 0, 0.25, 0.75, 1.75, 3.75, 7.75 и 12.75 секунды, и слот остаётся занятым, пока опрос не прочитает ответ. Результат, который появился сразу после опроса, ждёт следующего. Опрос на 7.75 с не успевает за решением в 8 секунд на четверть секунды. На тестовом ПК медленные вызовы, описанные в следующем разделе, сдвинули этот опрос за 8 с, поэтому ответ был прочитан где-то между 8.4 и 8.9 с. Без этой издержки то же решение ждало опроса на 12.75 с и вернулось через 12.9 с при pollingInterval по умолчанию, равном 5, и через 8.9 с при pollingInterval, равном 1. Если решения долгие, а очередь длинная, меньший pollingInterval освобождает слоты быстрее.
Остаются два ограничения. Вызывающая сторона, которая разорвала соединение, не отменяет свой обработчик, поэтому осиротевшее решение удерживает слот, пока не закончится. С одним слотом и решением в 4 секунды следующий запрос занял в сумме 8.3 секунды против 4.7 секунды при свободном слоте. Имитация также продолжает работать над брошенным заданием после истечения дедлайна, а у SDK нет вызова отмены. Поэтому число слотов может несколько секунд опережать число реальных потоков CapSkip. Это смоделировано в имитации. Настоящее приложение не тестировалось.
Одна издержка, о которой стоит знать
AsyncCapSkip создаёт новый клиент httpx для каждого запроса, который отправляет, включая каждый опрос. На тестовом ПК это стоило примерно 141 мс блокирующей работы процессора каждый раз, в основном на загрузку пакета сертификатов, который для обычного HTTP-адреса не нужен. Десять одновременных решений по 2 секунды завершились примерно за 6 с, а запрос к /openapi.json, отправленный во время всплеска, ждал до 1.9 с. Те же десять через синхронный эндпоинт заняли 3.8 с при худшем случае в несколько миллисекунд, но такой путь не умеет прекращать зависший вызов. Дедлайн вокруг потока оставляет поток зависшим. После 41 зависшего запроса у сервиса было 41 зависший поток, и они остаются до перезапуска процесса.
Заблокированный цикл к тому же заставляет сроки сдвигаться под нагрузкой. При молчащем CapSkip и дедлайне 2 секунды 12 одновременных запросов дали 8 ответов в диапазоне от 2.6 до 2.9 с и 4 ответа с именем capskip_timeout на 8.5 с. При 41 запросе самый медленный занял 13.9 с. Одиночный запрос уложился в свой дедлайн. Причину ответов на 8.5 с выделить не удалось, но заблокированный цикл входит в неё, и тот же сдвиг показал один только SDK. Для горстки вызывающих сторон остановка безвредна. Она растёт вместе с числом решений в работе, поэтому держите число слотов на уровне того, что CapSkip действительно может выполнять.
Весь main.py
Это шаги с 3 по 10 вместе, с импортами, собранными вверху. Сохраните как main.py.
import asyncio
import logging
import os
import secrets
from contextlib import asynccontextmanager
from capskip import ApiException, AsyncCapSkip, CapSkipError
from capskip import NetworkException, TimeoutException, ValidationException
from fastapi import Depends, FastAPI, Header, HTTPException, Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field
# The SDK reads no env vars, so pass each setting in by hand.
HOST = os.getenv("CAPSKIP_HOST", "127.0.0.1")
PORT = int(os.getenv("CAPSKIP_PORT", "8080"))
KEY = os.getenv("CAPSKIP_API_KEY", "capskip")
MAX_THREADS = int(os.getenv("CAPSKIP_MAX_THREADS", "10"))
DEADLINE = float(os.getenv("SOLVE_DEADLINE", "120"))
@asynccontextmanager
async def lifespan(app: FastAPI):
# One client and one gate, shared by every request.
app.state.cap = AsyncCapSkip(
apiKey=KEY, host=HOST, port=PORT,
recaptchaTimeout=DEADLINE,
)
app.state.slots = asyncio.Semaphore(MAX_THREADS)
yield
app = FastAPI(lifespan=lifespan)
SERVICE_KEY = os.environ["SOLVER_API_KEY"]
if not SERVICE_KEY:
raise SystemExit("SOLVER_API_KEY must not be empty")
def require_key(x_api_key: str = Header(default="")):
good = secrets.compare_digest(
x_api_key.encode(), SERVICE_KEY.encode())
if not good:
raise HTTPException(
401, "missing or wrong X-API-Key",
headers={"WWW-Authenticate": "APIKey"})
class SolveRequest(BaseModel):
sitekey: str = Field(min_length=1)
url: str = Field(pattern=r"^https?://")
async def solve_v2(state, sitekey: str, url: str) -> str:
# The deadline also covers the wait for a free slot.
async with asyncio.timeout(DEADLINE):
async with state.slots:
result = await state.cap.recaptcha(sitekey, url)
return result["code"]
@app.post("/solve", dependencies=[Depends(require_key)])
async def solve(body: SolveRequest, request: Request):
token = await solve_v2(request.app.state, body.sitekey, body.url)
return {"token": token}
STATUS = {
NetworkException: (503, "capskip_unreachable"),
TimeoutException: (504, "capskip_timeout"),
ValidationException: (422, "bad_parameters"),
ApiException: (502, "capskip_error"),
}
@app.exception_handler(CapSkipError)
async def capskip_error(request: Request, exc: CapSkipError):
# Log the detail here, keep it away from the caller.
logging.getLogger("uvicorn.error").warning("%r", exc)
status, code = STATUS.get(type(exc), (502, "capskip_error"))
if "UNSOLVABLE" in str(exc):
status, code = 422, "unsolvable"
return JSONResponse({"error": code}, status)
@app.exception_handler(TimeoutError)
async def deadline_hit(request: Request, exc: TimeoutError):
# asyncio.timeout raises the built-in TimeoutError. The SDK's
# TimeoutException is a different class, so it needs its own entry.
return JSONResponse({"error": "deadline_exceeded"}, 504)
@app.get("/health")
async def health():
# CapSkip has no ping call, so check that its port accepts a
# connection.
try:
_, writer = await asyncio.wait_for(
asyncio.open_connection(HOST, PORT), timeout=3)
except (OSError, TimeoutError):
return JSONResponse({"ok": False}, 503)
writer.close()
return {"ok": True}Частые ошибки в сервисе решения капчи на FastAPI
| Что вы видите | Причина | Исправить |
|---|---|---|
| Сервис завершается при запуске | SOLVER_API_KEY не задан или пуст. Тогда Python выводит для него KeyError в окне PowerShell. | Задайте его в том же окне |
| Каждый вызов возвращает 401 | Заголовок X-API-Key не равен SOLVER_API_KEY | Отправляйте ключ сервиса, а не ключ CapSkip |
| 504 с deadline_exceeded | Ожидание слота плюс решение превысили дедлайн | Увеличьте SOLVE_DEADLINE или одновременно увеличьте Max. Threads в CapSkip и CAPSKIP_MAX_THREADS |
| 504 с capskip_timeout | Первым исчерпался лимит опроса SDK, обычно под нагрузкой | Уменьшите число запросов в работе или увеличьте SOLVE_DEADLINE |
| 503 с capskip_unreachable | Никто не принимает соединения на порту CapSkip | Запустите CapSkip и проверьте режим Local или Server |
| Лимит выше ожидаемого | Больше одного воркера uvicorn | Запускайте один воркер |
| 422 с json_invalid от curl.exe | PowerShell 5.1 удалил кавычки | Передайте JSON через stdin |
От самого FastAPI: документация по lifespan описывает хук запуска, а все коды ошибок CapSkip перечисляет Документация API.
FAQ
Нужен ли сервис, если капчу решает только один скрипт?
Нет. Вызывайте SDK прямо из этого скрипта. Сервис полезен, когда одним CapSkip пользуются несколько вызывающих сторон, в том числе на других языках, и вам нужны один дедлайн, один лимит и один ключ. В руководстве по reCAPTCHA v2 на Python показан прямой вызов.
Можно ли разместить сервис на облачной платформе?
Да, но размещённый сервис работает на чужой машине, поэтому не может обратиться к 127.0.0.1 на вашем ПК. Запустите CapSkip в режиме Server на машине с Windows, которую вы контролируете, желательно со статическим публичным IP, и задайте этот адрес в CAPSKIP_HOST. Включите проверку API-ключа, добавьте блокирующее правило для каждого другого адреса и помните, что ключ передаётся открытым текстом по обычному HTTP. Ни одна платформа хостинга для этого руководства не тестировалась.
Не лучше ли сделать эндпоинт обычной функцией def?
Для этой задачи нет. Эндпоинт с обычным def выполняется в потоке, а у обычного клиента CapSkip нет способа прервать зависший вызов. Зависший вызов никогда не возвращается, поэтому вызывающая сторона ждёт до своего собственного таймаута. Обёртка в дедлайн отвечает вовремя, но поток остаётся зависшим. Асинхронный клиент завершается чисто.
Может ли тот же эндпоинт решать reCAPTCHA v3?
Не в таком виде. Сервис вызывает метод для v2. Для v3 добавьте в вызов version и action, как принимает метод reCAPTCHA из SDK. Не передавайте значение score, потому что в CapSkip нет настройки score. На странице решателя reCAPTCHA v2 описан тип с чекбоксом, который используется в этом руководстве.
Коротко
Чтобы запустить сервис решения капчи на FastAPI, держите один AsyncCapSkip, один Semaphore и один asyncio.timeout вокруг ожидания и решения. Сначала проверяйте ключ сервиса. Перехватывайте TimeoutException из SDK отдельно от встроенного TimeoutError. Запускайте единственный воркер. Поставьте блокирующее правило перед CapSkip, если он работает в режиме Server.
- Все типы капчи, которые поддерживает пакет для Python, перечислены на странице сервиса распознавания капч для Python.
- Как работает решатель reCAPTCHA с чекбоксом, описано на странице сервиса распознавания reCAPTCHA v2.
- Если нужно много решений в одном скрипте, смотрите руководство по параллельному решению капчи на Python.
Общий сервис может обслуживать целую команду, и никому не нужно считать решения. CapSkip берёт на себя обход капчи на вашей собственной машине с Windows, поэтому каждое решение за сервисом остаётся без платы за вызов.
