So lösen Sie Captchas in RQ-Jobs über das 180-Sekunden-Limit hinaus

rq captcha - How to Solve CAPTCHAs in RQ Jobs Past the 180-Second Limit

Um Captchas in RQ zu lösen, der Python-Job-Queue auf Basis von Redis, packen Sie den gesamten Ablauf in einen Job: die Seite abrufen, das Captcha mit CapSkip lösen, das Formular absenden. Ein RQ-Captcha-Job braucht dann nur noch eine geänderte Einstellung, sein Timeout. RQ gibt jedem Job 180 Sekunden, sofern Sie nichts anderes angeben. Das CapSkip-SDK wartet bis zu 300 Sekunden auf einen reCAPTCHA-Token, ein Lösevorgang, der hinter belegten Threads wartet, wird also mitten im Polling beendet. RQ verzeichnet eine JobTimeoutException, und CapSkip schließt den Lösevorgang für niemanden ab. Dieser Leitfaden behandelt den Job, das Timeout, die Worker, Wiederholungen und die Frage, wo der Worker laufen kann, Windows eingeschlossen.

Was Sie brauchen

  • CapSkip auf einem Windows-Rechner. Die Beispiele lösen reCAPTCHA v2, und dieselbe Job-Form funktioniert für jeden Typ, den CapSkip unterstützt.
  • Python 3.10 oder neuer mit RQ 2.x, dem Paket capskip und requests. Die Beispiele liefen unter RQ 2.12.
  • Ein Redis-Server ab Version 5, den sowohl der Code, der Jobs in die Queue stellt, als auch die Worker erreichen können. RQ akzeptiert auch Valkey 7.2 oder neuer.
  • Ein Rechner für die Worker. Der Standard-Worker von RQ setzt auf einen Unix-Aufruf, den Windows nicht hat. Ein Abschnitt weiter unten behandelt daher die Optionen unter Windows, unter WSL und auf einem anderen Rechner.
  • Eine Adresse für den Solver. Im Local-Modus antwortet CapSkip auf 127.0.0.1 nur für dieses Gerät. Im Server-Modus lauscht es auf Ihrer Netzwerkadresse oder öffentlichen IP, damit ein Worker auf einem anderen Rechner es über die API aufrufen kann. Eingestellt werden beide Modi unter Verbindungseinstellungen.
pip install rq capskip requests

Schritt 1: Den Job in einem eigenen Modul schreiben

RQ speichert einen Job als Importpfad einer Funktion plus deren Argumente, und der Worker importiert diese Funktion, wenn er den Job übernimmt. Eine Funktion, die in dem Skript definiert ist, das sie in die Queue stellt, lebt in __main__, und das kann der Worker nicht importieren. RQ lehnt sie deshalb schon beim Einreihen mit einem ValueError ab. Legen Sie den Job in ein eigenes Modul:

# tasks.py: the worker imports jobs by module path, so keep them here
# pip install rq capskip requests
import os
from html.parser import HTMLParser

import requests
from capskip import CapSkip

solver = CapSkip(
    apiKey=os.getenv("CAPSKIP_API_KEY", "capskip"),
    host=os.getenv("CAPSKIP_HOST", "127.0.0.1"),
    port=int(os.getenv("CAPSKIP_PORT", "8080")),
)


class FormPage(HTMLParser):
    """Collects the reCAPTCHA sitekey and the hidden fields of its form."""

    def __init__(self):
        super().__init__()
        self.sitekey, self.hidden, self._form = None, {}, None

    def handle_starttag(self, tag, attrs):
        a = dict(attrs)
        if tag == "form":
            self._form = {}
        if a.get("data-sitekey"):
            self.sitekey = a["data-sitekey"]
            if self._form is not None:
                self.hidden = self._form
        hidden = (a.get("type") or "").lower() == "hidden"
        if tag == "input" and hidden and a.get("name") and self._form is not None:
            self._form[a["name"]] = a.get("value") or ""

    def handle_endtag(self, tag):
        if tag == "form":
            self._form = None


def sign_up(page_url, username):
    # One session per job, so the page's cookies go back with the form.
    session = requests.Session()
    page = FormPage()
    page.feed(session.get(page_url, timeout=30).text)

    # Solve inside the job, right before the post: tokens expire.
    token = solver.recaptcha(page.sitekey, page_url)["code"]

    resp = session.post(page_url, data={
        **page.hidden,
        "username": username,
        "g-recaptcha-response": token,
    }, headers={"Referer": page_url}, timeout=30)
    resp.raise_for_status()
    return resp.status_code

Jeder Job öffnet seine eigene requests.Session, sodass das Session-Cookie, das die Seite setzt, mit dem Formular zurückgeht. Der Parser behält die versteckten Felder des Formulars, das das Widget enthält, darunter den CSRF-Token, zu dem dieses Cookie gehört, und ignoriert ein Such- oder Newsletter-Formular an anderer Stelle der Seite. Das Beispiel sendet an die Seiten-URL zurück; zeigt die action des Formulars woandershin, senden Sie dorthin. Der Referer-Header steht dort, weil requests weder Referer noch Origin sendet, und manche Frameworks, darunter Django, lehnen einen HTTPS-Formular-POST ab, der keins von beiden hat.

raise_for_status macht aus einem HTTP-Fehler, etwa einem 403 nach einer gescheiterten CSRF-Prüfung, einen fehlgeschlagenen Job statt eines stillen Erfolgs, und das ist wichtig, sobald Wiederholungen aktiv sind. Eine Website, die den Token mit einer normalen 200-Seite ablehnt, zählt trotzdem als Erfolg. Prüfen Sie die Antwort deshalb zusätzlich auf das, was Ihre Website bei Erfolg anzeigt.

Der Solver-Client wird beim Importieren des Moduls erstellt. Mit dem Standard-Worker von RQ geschieht das für jeden Job neu, im frisch geforkten Kindprozess (dem Work Horse), und mit SimpleWorker einmal pro Worker. Beides ist in Ordnung, denn der Client hält zwischen den Aufrufen keine Verbindung offen. Das SDK liest Umgebungsvariablen nicht von selbst, deshalb liest der Code CAPSKIP_HOST, CAPSKIP_PORT und CAPSKIP_API_KEY aus und übergibt sie an den Konstruktor. Um einen anderen Worker auf einen anderen CapSkip-Rechner zu richten, ändern Sie seine Umgebung, nicht seinen Code.

Schritt 2: In die Queue stellen, mit einem Timeout, das zum Lösevorgang passt

Diese Einstellung entscheidet, ob ein RQ-Captcha-Job einen langsamen Lösevorgang übersteht. Übergeben Sie job_timeout bei jedem Einreihen, oder geben Sie dem Queue-Objekt ein default_timeout. Dieser Standardwert lebt im Python-Objekt, nicht in Redis. Jedes Skript, das Jobs einreiht, muss seine Queue daher auf dieselbe Weise erstellen:

# enqueue.py
from redis import Redis
from rq import Queue

from tasks import sign_up

# RQ's default is 180 seconds; a reCAPTCHA solve may take 300.
queue = Queue("captcha", connection=Redis(), default_timeout=420)

job = queue.enqueue(sign_up, "https://example.com/signup", "YOUR_USERNAME")
print(job.id)

Die Zahl ergibt sich, wenn Sie den schlimmsten Fall zusammenrechnen. Das SDK pollt einen reCAPTCHA-Lösevorgang bis zu 300 Sekunden lang, sein recaptchaTimeout, und löst dann TimeoutException aus. Der Abruf der Seite und das Absenden übergeben requests jeweils timeout=30, was den Verbindungsaufbau und jedes einzelne Warten auf Daten begrenzt, nicht die gesamte Anfrage. Zusammen sind das etwa 360 Sekunden, und 420 lassen eine Minute Puffer für eine langsame Seite.

CapSkip misst außerdem selbst die Zeit. In seinen reCAPTCHA-Einstellungen darf eine Aufgabe 250 Sekunden auf einen seiner 10 Threads warten und hat dann bis zu 250 Sekunden, um die Challenge zu laden, und weitere 250, um sie zu lösen. Eine Aufgabe, die eines dieser Limits ausschöpft, lässt CapSkip mit einer ApiException scheitern. Eine Aufgabe, die eine Weile auf einen Thread wartet und dann langsam gelöst wird, kann zuerst die 300 Sekunden des SDK überschreiten, was mit einer TimeoutException endet. Das SDK setzt allerdings kein Timeout für seine eigenen HTTP-Aufrufe an CapSkip. Hört CapSkip mitten in einer Anfrage auf zu antworten, beendet der Standard-Worker von RQ den Job trotzdem bei job_timeout; SimpleWorker, der weiter unten behandelt wird, kann das nicht.

So sieht es dagegen mit dem Standardwert aus. Nach 180 Sekunden löst RQ eine JobTimeoutException im Job aus, wo auch immer er gerade steht, und bei einem Captcha-Job ist das fast immer die Poll-Schleife des SDK. Der Job landet in der Registry der fehlgeschlagenen Jobs, mit „Task exceeded maximum timeout value (180 seconds)“ als Fehler. CapSkip erfährt davon nichts. Es arbeitet weiter, erzeugt einen Token, den niemand abholt, und der Thread, den es dafür belegt hat, war umsonst. Genau das haben wir mit einem Lösevorgang nachgestellt, der 200 Sekunden dauerte.

Fangen Sie um den Lösevorgang nicht Exception ab

JobTimeoutException ist eine gewöhnliche Unterklasse von Exception, die in Ihrem Code ausgelöst wird. Ein breites except Exception um den Lösevorgang fängt sie ab, der Job kehrt normal zurück, und RQ verbucht ihn als abgeschlossen, ohne jede Spur des Timeouts. In unserem Test lief ein so geschriebener Job mitten im Lösevorgang in sein Limit, und der Worker protokollierte „Job OK“. Fangen Sie stattdessen die eigene Basisklasse des SDK ab:

import logging

from capskip import CapSkipError

log = logging.getLogger(__name__)

try:
    token = solver.recaptcha(page.sitekey, page_url)["code"]
except CapSkipError as exc:
    # The SDK's errors only. RQ's JobTimeoutException is not one of
    # them, so a job that ran out of time still fails as it should.
    log.warning("solve failed for %s: %s", page_url, exc)
    raise

Schritt 3: So viele Worker betreiben, wie CapSkip Threads hat

Ein RQ-Worker führt einen Job nach dem anderen aus. Bei RQ-Captcha-Jobs ist die Zahl der Worker also die Zahl der gleichzeitig laufenden Lösevorgänge. CapSkip löst standardmäßig 10 reCAPTCHAs gleichzeitig, seine Einstellung Max. Threads, und lässt jede weitere Aufgabe auf einen freien Thread warten. Worker über diese Zahl hinaus erledigen nicht mehr Arbeit. Ihre Aufgaben stehen stattdessen in der Warteschlange von CapSkip, und dieses Warten zehrt am Timeout jedes Jobs. Ein Worker pro Thread ist die richtige Größe:

# Ten workers for CapSkip's ten reCAPTCHA threads
rq worker-pool captcha -n 10

worker-pool startet die Worker und startet jeden neu, der abstürzt. Bei jedem Worker, den es startet, ist der Scheduler eingeschaltet, und einer davon führt ihn für die Queue aus. Davon hängen die Wiederholungen im nächsten Schritt ab. Wenn Sie Max. Threads in CapSkip erhöhen, vergrößern Sie den Pool entsprechend, und zählen Sie jeden Worker auf jedem Rechner mit, der dasselbe CapSkip aufruft.

Schritt 4: Wiederholungen, und warum sie einen Scheduler brauchen

RQ wiederholt standardmäßig nichts. Ein fehlgeschlagener Job landet in der Registry der fehlgeschlagenen Jobs und bleibt dort. Damit RQ es erneut versucht, übergeben Sie beim Einreihen ein Retry:

from rq import Retry

job = queue.enqueue(
    sign_up, "https://example.com/signup", "YOUR_USERNAME",
    # Up to two more runs: 30 seconds, then 120 seconds after a failure.
    retry=Retry(max=2, interval=[30, 120]),
)

Eine Wiederholung mit Intervall wird nicht wieder in die Queue gestellt. RQ plant sie ein, und nur ein Worker, der den Scheduler ausführt, stellt sie jemals zurück in die Queue. worker-pool führt immer einen aus. Ein einzelner Worker, der mit einfachem rq worker gestartet wurde, tut das nicht, und dann wartet jeder wiederholte Job für immer in der Registry der geplanten Jobs, ohne Fehlermeldung. Genau das haben wir beobachtet, und derselbe Job lief innerhalb von Sekunden, sobald ein Worker mit Scheduler startete. Starten Sie einzelne Worker so:

rq worker captcha --with-scheduler

Eine Wiederholung führt den gesamten Job erneut aus: eine frische Seite, ein frischer Lösevorgang und ein frisches Absenden. Genau das wollen Sie, denn ein reCAPTCHA-Token wird nur einmal akzeptiert, es gibt also nichts fortzusetzen. Deshalb sollte der Job auch mit dem Absenden enden. Alles, was nach einem erfolgreichen Absenden noch scheitern kann, lässt die Wiederholung ein zweites Mal absenden.

Lösen und Absenden im selben Job lassen

Es ist verlockend, die Arbeit aufzuteilen. Ein Job löst und gibt den Token zurück, und ein zweiter Job (oder der Code, der den ersten eingereiht hat) sendet ihn später ab. Tun Sie das nicht. Ein reCAPTCHA-Token läuft etwa zwei Minuten nach seiner Ausstellung ab, und RQ gibt keine Garantie, wann der nächste Job startet. Unter Last wartet er hinter allem anderen in der Queue. Außerdem bewahrt RQ den Rückgabewert eines Jobs standardmäßig nur 500 Sekunden auf. Wenn Sie im Job lösen, direkt vor dem Absenden, bleibt die gesamte Lebensdauer des Tokens innerhalb eines einzigen Funktionsaufrufs. Der Leitfaden zur Gültigkeitsdauer von reCAPTCHA-Tokens behandelt die Ablaufregeln im Detail.

Wo der RQ-Worker laufen kann: Windows, WSL oder ein anderer Rechner

Der Standard-Worker von RQ forkt für jeden Job einen frischen Prozess. Windows hat kein os.fork, daher bricht der Worker dort beim ersten Job mit einem AttributeError ab. Die Dokumentation von RQ sagt, dass Worker fork brauchen, und verweist Windows-Nutzer auf WSL, obwohl ihre Seite zu den Worker-Klassen SpawnWorker für Windows anbietet. Unter RQ 2.12 scheitert auch SpawnWorker, weil er mit os.wait4 auf jeden Job wartet, und das hat Windows ebenfalls nicht. Damit bleiben drei Wege.

Auf dem CapSkip-PC selbst verwenden Sie SimpleWorker. Er führt jeden Job im Worker-Prozess selbst aus statt in einem Kindprozess. Unter Windows mit RQ 2.12 hat er unsere Jobs und den Scheduler ausgeführt, und worker-pool hat mehrere solcher Worker parallel gestartet. Betrachten Sie das als von uns getestet, nicht als vom RQ-Projekt offiziell unterstützt. Er setzt job_timeout mit einem Timer durch, aber der Timer greift nur zwischen Python-Anweisungen. Als wir einen Aufruf an CapSkip unter einem job_timeout von 8 Sekunden 40 Sekunden lang hängen ließen, scheiterte der Job nach 40 Sekunden, nicht nach 8. Ein Aufruf, der nie zurückkehrt, blockiert diesen Worker, bis Sie ihn neu starten. CapSkip bleibt im Local-Modus, weil der Worker auf demselben Rechner läuft. Für Redis gibt es keinen offiziellen Windows-Build, aber ein Redis, das unter WSL läuft, antwortet Windows-Programmen auf 127.0.0.1, und so haben wir dieses Setup getestet:

# On Windows: no fork, so run jobs inside the worker process
rq worker-pool captcha -n 10 -w rq.worker.SimpleWorker

Unter WSL 2 auf demselben PC behandeln Sie den Worker wie einen anderen Rechner. Im standardmäßigen NAT-Netzwerkmodus von WSL 2 ist 127.0.0.1 innerhalb von Linux nicht Windows, sondern die Linux-VM. Ein Worker dort kann CapSkip im Local-Modus also nicht erreichen. Wir haben es geprüft, und die Verbindung schlägt fehl. Schalten Sie CapSkip in den Server-Modus und richten Sie CAPSKIP_HOST auf die Adresse des Windows-Hosts, so wie Linux sie sieht. Die Microsoft-Anleitung zum Netzwerkbetrieb in WSL zeigt, wie Sie diese Adresse finden, und beschreibt außerdem den gespiegelten Netzwerkmodus (mirrored) unter Windows 11 22H2 und neuer, in dem Linux Windows-Server auf 127.0.0.1 erreichen kann.

Auf einem Linux-Server, einem Container-Host oder einer Plattform verwenden Sie den Server-Modus. CapSkip lauscht dann auf Ihrer Netzwerkadresse oder öffentlichen IP, und jeder Worker verbindet sich über dieselbe API. Verwenden Sie eine statische öffentliche IP, wenn der Weg über das Internet führt, und aktivieren Sie die Validierung des API-Schlüssels. CapSkip öffnet seinen Port beim Start des Server-Modus für jede Adresse. Um ihn auf Ihre Worker zu beschränken, fügen Sie in der Windows Firewall eine Blockierregel für die übrigen Adressen hinzu. Der Solver bleibt Ihr eigener Windows-Rechner, und abgerechnet wird pro Lösung weiterhin nichts, ganz gleich, wie viele Worker ihn aufrufen.

Vollständiges lauffähiges Beispiel

Zwei Dateien und ein Befehl. tasks.py enthält den Job mit der engen Exception-Behandlung aus Schritt 2; enqueue.py setzt das Timeout und die Wiederholungen.

# tasks.py
# pip install rq capskip requests
import logging
import os
from html.parser import HTMLParser

import requests
from capskip import CapSkip, CapSkipError

log = logging.getLogger(__name__)
solver = CapSkip(
    apiKey=os.getenv("CAPSKIP_API_KEY", "capskip"),
    host=os.getenv("CAPSKIP_HOST", "127.0.0.1"),
    port=int(os.getenv("CAPSKIP_PORT", "8080")),
)


class FormPage(HTMLParser):
    def __init__(self):
        super().__init__()
        self.sitekey, self.hidden, self._form = None, {}, None

    def handle_starttag(self, tag, attrs):
        a = dict(attrs)
        if tag == "form":
            self._form = {}
        if a.get("data-sitekey"):
            self.sitekey = a["data-sitekey"]
            if self._form is not None:
                self.hidden = self._form
        hidden = (a.get("type") or "").lower() == "hidden"
        if tag == "input" and hidden and a.get("name") and self._form is not None:
            self._form[a["name"]] = a.get("value") or ""

    def handle_endtag(self, tag):
        if tag == "form":
            self._form = None


def sign_up(page_url, username):
    session = requests.Session()
    page = FormPage()
    page.feed(session.get(page_url, timeout=30).text)
    if not page.sitekey:
        raise ValueError(f"no data-sitekey on {page_url}")

    try:
        token = solver.recaptcha(page.sitekey, page_url)["code"]
    except CapSkipError as exc:
        # RQ's JobTimeoutException is not a CapSkipError, so it passes.
        log.warning("solve failed for %s: %s", page_url, exc)
        raise

    resp = session.post(page_url, data={
        **page.hidden,
        "username": username,
        "g-recaptcha-response": token,
    }, headers={"Referer": page_url}, timeout=30)
    resp.raise_for_status()
    return resp.status_code
# enqueue.py
from redis import Redis
from rq import Queue, Retry

from tasks import sign_up

queue = Queue("captcha", connection=Redis(), default_timeout=420)

for user in ["YOUR_USERNAME_1", "YOUR_USERNAME_2"]:
    job = queue.enqueue(
        sign_up, "https://example.com/signup", user,
        retry=Retry(max=2, interval=[30, 120]),
    )
    print(job.id)
# Linux or WSL; on Windows add -w rq.worker.SimpleWorker
rq worker-pool captcha -n 10

Starten Sie den Pool aus dem Ordner, der tasks.py enthält, denn von dort importieren die Worker die Datei. rq info zeigt für jede Queue die Zahl der wartenden, laufenden, abgeschlossenen und fehlgeschlagenen Jobs sowie die Worker. Die Registry der geplanten Jobs, in der Wiederholungen mit Intervall warten, zählt es nicht mit. Ein fehlgeschlagener Job behält seinen Traceback standardmäßig ein Jahr lang: Holen Sie den Job mit Job.fetch und lesen Sie sein exc_info.

Häufige Fehler und was sie bedeuten

Was Sie sehenUrsacheBeheben
JobTimeoutException: Task exceeded maximum timeout value (180 seconds)Der Job lief mit dem Standard-Timeout von RQ, und der Lösevorgang dauerte längerSetzen Sie job_timeout oder das default_timeout der Queue auf 420
Der Job ist als abgeschlossen markiert, aber auf der Website ist nichts angekommenEin breites except Exception hat die JobTimeoutException von RQ abgefangenFangen Sie um den Lösevorgang CapSkipError ab, nicht Exception
Wiederholte Jobs laufen nie wiederDie Wiederholung hat ein Intervall, und kein Worker führt den Scheduler aus. Die Jobs warten in der Registry der geplanten Jobs, die rq info nicht mitzähltVerwenden Sie worker-pool, oder starten Sie einzelne Worker mit der Scheduler-Option
ValueError: Functions from the __main__ module cannot be processed by workersDie Job-Funktion ist in dem Skript definiert, das sie einreihtVerschieben Sie sie in ein Modul wie tasks.py und importieren Sie sie
AttributeError für os.fork oder os.wait4, wenn ein Windows-Worker einen Job übernimmtDer Standard-Worker und SpawnWorker verwenden beide Aufrufe, die es nur unter Unix gibtStarten Sie die Worker mit rq.worker.SimpleWorker. Der Job, den jener Worker übernommen hatte, bleibt als laufend (executing) stehen, bis RQ ihn beim Aufräumen als fehlgeschlagen markiert
Die Website lehnt das Absenden als abgelaufene Session abSeite und Absenden liefen über verschiedene Sessions oder ohne RefererVerwenden Sie eine requests.Session pro Job und senden Sie die Seiten-URL als Referer
Eine NetworkException beim ersten Lösevorgang aus WSL oder von einem anderen Rechner127.0.0.1 ist dort nicht der CapSkip-RechnerSchalten Sie CapSkip in den Server-Modus und setzen Sie CAPSKIP_HOST auf seine Adresse
Eine ApiException mit ERROR_CAPTCHA_UNSOLVABLECapSkip hat die Aufgabe als fehlgeschlagen beendet, zum Beispiel nach seinem eigenen Warte- oder Löse-TimeoutLassen Sie Retry den Job erneut ausführen, und halten Sie die Zahl der Worker bei oder unter Max. Threads
TimeoutException nach 300 SekundenEine Aufgabe hat auf einen Thread gewartet und wurde dann langsam gelöst, sodass beides zusammen 300 Sekunden überschritt, oder CapSkip wurde mitten im Lösevorgang geschlossenDimensionieren Sie den Pool nach Max. Threads und prüfen Sie, ob CapSkip noch läuft

FAQ

Stoppt das Job-Timeout von RQ den Lösevorgang in CapSkip?

Nein. Das Timeout beendet Ihren Python-Code, nicht die Aufgabe, an der CapSkip arbeitet. CapSkip schließt sie ab oder lässt sie nach seiner eigenen Uhr scheitern, und das Ergebnis wartet auf eine Abfrage, die nie kommt. Deshalb gehört das Timeout über die Lösezeit und nicht darunter: Ein Job, der mitten im Lösevorgang beendet wird, kostet Sie den Job und einen CapSkip-Thread.

Sollte der Job AsyncCapSkip verwenden?

Es bringt keinen Vorteil. RQ führt einen async def-Job zwar aus, in einer eigenen Event-Loop. Jeder Worker führt aber trotzdem einen Job nach dem anderen aus, die Parallelität kommt also in beiden Fällen von der Zahl der Worker. Der einfache CapSkip-Client hält den Job schlicht. AsyncCapSkip lohnt sich in einem langlebigen asyncio-Programm, das viele Lösevorgänge aus einem Prozess abfeuert.

Können Worker auf einer gehosteten Plattform CapSkip erreichen?

Ja. Schalten Sie CapSkip in den Verbindungseinstellungen in den Server-Modus, damit es auf einer Netzwerkadresse statt auf Loopback lauscht. Setzen Sie CAPSKIP_HOST in der Umgebung der Worker; der Job-Code ändert sich nicht. Worker auf einem VPS, einer Container-Plattform oder einem verwalteten Host verbinden sich alle über dieselbe HTTP-API. Verwenden Sie eine statische öffentliche IP und eine Blockierregel in der Windows Firewall für jede Adresse, die kein Worker ist. Der Solver bleibt auf Hardware, die Ihnen gehört, zusätzliche Worker verursachen also keine Kosten pro Lösung.

Worin unterscheidet sich das vom Lösen von Captchas in Celery?

Der Job ist derselbe: Lösen und Absenden in einem Task, mit einem Zeitlimit über dem des Solvers. Die Mechanik drumherum unterscheidet sich. Celery hat ein weiches und ein hartes Zeitlimit und ein Bestätigungsmodell mit eigenen Fallen. RQ hat ein Timeout pro Job, standardmäßig 180 Sekunden, das im Job als Exception ausgelöst wird, und sein Standard-Worker beendet den Prozess des Jobs 60 bis 90 Sekunden später, falls er dann noch läuft. Wiederholungen mit Intervall brauchen außerdem den Scheduler von RQ. Wie das in Celery aussieht, zeigt der Leitfaden zum Lösen von Captchas in einem Celery-Task.

Die Kurzfassung

Packen Sie bei einem RQ-Captcha-Job den Abruf, den Lösevorgang und das Absenden in eine einzige Job-Funktion in einem eigenen Modul. Reihen Sie ihn mit einem job_timeout von 420 Sekunden ein, oder geben Sie der Queue diesen Standardwert, denn die 180 Sekunden von RQ sind kürzer, als ein reCAPTCHA-Lösevorgang dauern kann. Fangen Sie um den Lösevorgang CapSkipError ab, nie Exception. Betreiben Sie mit worker-pool so viele Worker, wie CapSkip Threads hat; worker-pool führt auch den Scheduler aus, den Wiederholungen mit Intervall brauchen. Verwenden Sie unter Windows SimpleWorker; aus WSL oder von einem anderen Rechner aus schalten Sie CapSkip in den Server-Modus.

Zehn Worker, die rund um die Uhr lösen, sind genau die Last, die ein nutzungsbasiert abrechnender Dienst pro tausend Lösungen berechnet. Auf Ihrem eigenen Rechner bewältigt ein Captcha-Löser diese Last ohne Abrechnung pro Lösung.