Scarnox · Blog

Middleware для капчі в Scrapy: розв'язання капчі у вашому павуку (2026)

Scrapy побудована навколо запитів і колбеків, тож капча не вкладається в неї охайно. Правильний спосіб її обробити - downloader middleware: він бачить кожну відповідь, помічає перевірку, отримує токен, і павук відправляє форму з цим токеном. Цей посібник будує такий middleware для сторінок PopularCaptcha, які Scarnox сьогодні розв'язує без проксі.

Почніть з безкоштовного пробного кредиту, картка не потрібна і протестуйте код на тестовій сторінці, перш ніж запускати його на чомусь іншому.

Як працює middleware

Middleware працює з кожною відповіддю:

  1. Якщо відповідь не має data-sitekey, вона пропускається далі.
  2. Якщо має, middleware розв'язує капчу і повертає копію того самого запиту з токеном, збереженим у meta.
  3. Scrapy знову надсилає цей запит. Middleware бачить токен у meta і пропускає відповідь.
  4. Павук читає токен з meta і відправляє форму через FormRequest.from_response.

Виклик розв'язання виконується в потоці через deferToThread, тож він не блокує reactor Scrapy. Інші запити рухаються, поки капча розв'язується.

Налаштування проєкту

Ключ зберігайте у змінній середовища, а не в коді:

# settings.py
import os

SCARNOX_API_KEY = os.environ["SCARNOX_API_KEY"]
SCARNOX_TIMEOUT = 185

DOWNLOADER_MIDDLEWARES = {
    "myproject.middlewares.PopularCaptchaMiddleware": 543,
}

ROBOTSTXT_OBEY = True
DOWNLOAD_DELAY = 3
CONCURRENT_REQUESTS_PER_DOMAIN = 1

Затримка завантаження і одна паралельна задача на домен тут є навмисно. Вони тримають навантаження на сайт низьким і зменшують кількість капч, які ви бачите.

Middleware

# myproject/middlewares.py
import requests
from scrapy import Request
from scrapy.http import Response
from twisted.internet.threads import deferToThread

API_URL = "https://api.scarnox.com/api/tasks/create"


class PopularCaptchaMiddleware:
    def __init__(self, api_key: str, timeout: int):
        self.api_key = api_key
        self.timeout = timeout

    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            api_key=crawler.settings.get("SCARNOX_API_KEY"),
            timeout=crawler.settings.getint("SCARNOX_TIMEOUT", 185),
        )

    def _solve(self, sitekey: str, page_url: str) -> str:
        r = requests.post(
            API_URL,
            headers={"Authorization": f"Bearer {self.api_key}"},
            json={
                "captcha_type": "PopularCaptchaTaskProxyless",
                "site_key": sitekey,
                "site_url": page_url,
            },
            timeout=self.timeout,  # виконується в робочому потоці, а не в reactor
        )
        data = r.json()
        if data.get("errorId") != 0:
            raise RuntimeError(f"{data.get('errorCode')}: {data.get('errorDescription')}")
        return data["solution"]["token"]

    def process_response(self, request: Request, response: Response, spider):
        # Уже розв'язано або це відправка форми, яку ми створили самі: пропускаємо.
        if request.meta.get("captcha_token") or request.meta.get("skip_captcha"):
            return response

        sitekey = response.css("[data-sitekey]::attr(data-sitekey)").get()
        if not sitekey:
            return response

        spider.logger.info("PopularCaptcha on %s, solving", response.url)

        def retry_with_token(token: str) -> Request:
            # Повернення Request перепланує його. dont_filter обходить фільтр дублікатів.
            return request.replace(
                dont_filter=True,
                meta={**request.meta, "captcha_token": token},
            )

        return deferToThread(self._solve, sitekey, response.url).addCallback(retry_with_token)

Scrapy підтримує повернення Deferred з process_response. Колбек перетворює токен на повторний запит, і Scrapy планує його, як будь-який інший.

Павук

# myproject/spiders/search.py
import scrapy
from scrapy.http import FormRequest


class SearchSpider(scrapy.Spider):
    name = "search"
    start_urls = ["https://search.example.com/search?q=web+scraping"]

    def parse(self, response):
        token = response.meta.get("captcha_token")
        if token and response.css("[data-sitekey]"):
            # Scrapy заповнює власні поля форми; ми додаємо токен.
            yield FormRequest.from_response(
                response,
                formdata={
                    "h-captcha-response": token,
                    "g-recaptcha-response": token,
                },
                callback=self.parse_results,
                meta={"skip_captcha": True},
                dont_filter=True,
            )
            return

        yield from self.parse_results(response)

    def parse_results(self, response):
        for item in response.css("div.result"):
            yield {
                "title": item.css("h3::text").get(),
                "url": item.css("a::attr(href)").get(),
            }

Селектори - заповнювачі. Замініть їх на справжні з сайту, який скрапите.

Прапорець skip_captcha не дає middleware розв'язувати відправку форми знову. Без нього кожна відправка коштувала б ще одне розв'язання.

Запуск

export SCARNOX_API_KEY=scarnox_YOURAPIKEY
scrapy crawl search -O results.json

Стежте за логом. Рядок на кшталт PopularCaptcha on ..., solving означає, що перевірку знайдено і запитано токен. Якщо цей рядок з'являється на кожній сторінці, сповільніться. Часті перевірки - ознака того, що сайт помітив краулінг.

Час життя токена

Токен PopularCaptcha спливає приблизно за дві хвилини. Middleware запитує повтор одразу, як тільки приходить токен, а павук відправляє форму в тому самому ланцюжку колбеків. Тримайте ланцюжок коротким. Не додавайте повільних завантажень або довгої обробки між токеном і відправкою форми. Більше про це вікно див. у терміні дії токена капчі.

Помилки

Невдале розв'язання викликає RuntimeError у потоці. Scrapy зафіксує це як невдалий запит. Невдалі розв'язання не тарифікуються, тож повторити дешево. Для політики повторів з обмеженнями використовуйте вбудований RetryMiddleware Scrapy і обгорніть _solve у короткий цикл. Наш посібник про обробку помилок розв'язувача капчі перелічує шаблони, які витримують продакшен.

Скрапінг відповідально

Про правовий бік див. чи законний веб-скрапінг.

Ціни

Scarnox бере плату за кожне успішне розв'язання, а невдалі розв'язання безкоштовні. Актуальні тарифи дивіться на сторінці тарифів. Почніть з безкоштовного пробного кредиту, картка не потрібна.

Що далі