Szybkie rozwiązywanie captcha: jak skrócić opóźnienie w potoku
Szybki solver captcha to taki, który wywołujesz we właściwym momencie, równolegle, nie marnując żądań. Wywołanie Scarnox zwraca wynik, gdy token jest gotowy, więc jego czas zależy od typu captcha, docelowego serwisu i bieżącego obciążenia. Ten przewodnik nie obiecuje stałego czasu. Pokazuje, co kontrolujesz we własnym kodzie, jak to mierzyć i jak uruchamiać wiele rozwiązań naraz, aby czas, który spędzasz, był dobrze wykorzystany.
Co decyduje o czasie rozwiązania
Liczą się trzy rzeczy, a tylko jedna z nich zależy od Ciebie:
- Sama captcha. Niektóre typy wyzwań trwają dłużej niż inne. To cecha captcha danego serwisu, a nie Twojego kodu.
- Obciążenie solvera. Zajęty okres może spowolnić pojedyncze wywołanie. Logika ponawiania powinna sobie z tym radzić, a nie porzucać wywołanie zbyt wcześnie.
- Twój własny potok. Rozwiązywanie zbyt wcześnie, szeregowanie każdego żądania i czekanie na niezwiązaną pracę dokładają czasu, który możesz usunąć.
Skup wysiłek na trzecim punkcie. To jedyna część, którą możesz zmierzyć i zmienić.
Mierz przed optymalizacją
Owiń wywołanie licznikiem czasu i zapisz wynik. Chcesz znać czas rozwiązania i czas, jaki Twój kod spędza wokół niego:
import time
import requests
API_KEY = "scarnox_YOURAPIKEY"
URL = "https://api.scarnox.com/api/tasks/create"
def timed_solve(sitekey: str, page_url: str) -> dict:
started = time.perf_counter()
r = requests.post(
URL,
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"captcha_type": "PopularCaptchaTaskProxyless",
"site_key": sitekey,
"site_url": page_url,
},
timeout=185,
)
elapsed = time.perf_counter() - started
data = r.json()
if data.get("errorId") != 0:
return {"ok": False, "error": data.get("errorCode"), "seconds": elapsed}
return {"ok": True, "token": data["solution"]["token"], "seconds": elapsed}
result = timed_solve("10000000-ffff-ffff-ffff-000000000001", "https://example.com/signup")
print(result["ok"], round(result["seconds"], 2), "s")
Uruchamiaj to na stronach, na których Ci zależy, o porach, o których uruchamiasz swoje zadania. Zachowuj wyniki. Pokażą, czy zmiana rzeczywiście pomogła.
Rozwiązuj równolegle
Rozwiązania są niezależne. Jeśli masz do obsłużenia dziesięć sitekey, czekanie na każde po kolei marnuje czas, który mógłbyś poświęcić pozostałym. Pula wątków wystarcza w większości skryptów, bo każde rozwiązanie to jedno żądanie HTTP, które większość czasu spędza na czekaniu:
from concurrent.futures import ThreadPoolExecutor
jobs = [
("10000000-ffff-ffff-ffff-000000000001", "https://example.com/signup"),
("10000000-ffff-ffff-ffff-000000000001", "https://example.com/register"),
("10000000-ffff-ffff-ffff-000000000001", "https://example.com/contact"),
]
def run(job):
sitekey, page_url = job
return timed_solve(sitekey, page_url)
# Zacznij od małej liczby. Zwiększaj liczbę workerów tylko, dopóki błędów jest mało.
with ThreadPoolExecutor(max_workers=5) as pool:
results = list(pool.map(run, jobs))
for r in results:
status = "ok" if r["ok"] else r["error"]
print(f"{r['seconds']:.2f} s {status}")
Jeśli przy zwiększaniu liczby workerów pojawiają się błędy wyglądające na limity tempa lub brak pojemności, zmniejsz ją. Równoległość pomaga tylko wtedy, gdy serwer za nią nadąża.
Nie rozwiązuj zawczasu
Najszybszy kod to kod, który nie czeka na token, którego jeszcze nie użyje. Zamawianie tokenów z wyprzedzeniem i ich przechowywanie to typowy błąd. Tokeny wygasają po około dwóch minutach od wydania, więc zapisany token jest często nieaktualny, gdy go potrzebujesz. Rozwiązuj, gdy formularz ma być za chwilę wysłany. Szczegółowe uzasadnienie znajdziesz w artykule token captcha wygasł: przyczyny i poprawki.
Skróć resztę ścieżki
- Używaj jednej sesji HTTP na serwis, aby nie łączyć się ponownie przy każdym żądaniu.
- Nie wykonuj wolnego parsowania ani pracy w bazie danych między rozwiązaniem a wysłaniem.
- Ustaw rozsądny timeout dla własnych żądań. Wywołanie solvera używa około 185 sekund, bo blokuje się, aż token będzie gotowy. Pozostałe wywołania powinny być znacznie krótsze.
- Wysyłaj proxy tylko wtedy, gdy zadanie tego wymaga. Typy zadań z proxy przyjmują pole
proxyw formaciehttp://user:pass@host:port, ale samo dodanie proxy nie przyspiesza niczego.
Używaj ponownie tego, co nie wygasa
Część pracy można bezpiecznie współdzielić między rozwiązaniami. Trzymaj to poza gorącą ścieżką:
- Sitekey. Odczytaj sitekey raz na szablon strony i zapisz go. Nie wygasa tak jak token. Odczytuj go ponownie dopiero wtedy, gdy formularz zaczyna zawodzić.
- Połączenia HTTP. Sesja z keep-alive unika nowego uzgadniania TLS przy każdym wywołaniu. Twórz jedną sesję na wątek lub worker, a nie jedną na żądanie.
- Sparsowana konfiguracja. Wczytaj listę celów, listę proxy i ustawienia raz, przy starcie, zamiast odczytywać pliki wewnątrz funkcji rozwiązującej.
Te zmiany kosztują niewiele i usuwają narzut, który pojawia się przy każdym wywołaniu.
Mierz ogon, a nie średnią
Średni czas rozwiązania ukrywa wolne wywołania, które wstrzymują partię. Zapisuj czas każdego rozwiązania, a potem patrz na medianę i 95. percentyl. Potok, który kończy się w czasie najwolniejszego zadania, jest ograniczony przez ogon, więc niewielka zmiana w sposobie ponawiania wolnych wywołań może mieć większe znaczenie niż jakakolwiek zmiana mediany:
import statistics
def summarise(seconds: list[float]) -> str:
ordered = sorted(seconds)
p95 = ordered[int(0.95 * (len(ordered) - 1))]
return f"median {statistics.median(ordered):.2f} s, p95 {p95:.2f} s, n={len(ordered)}"
print(summarise([r["seconds"] for r in results]))
Porównuj podsumowanie przed i po każdej zmianie. Jeśli p95 prawie się nie rusza, czas jest tracony gdzie indziej w Twoim kodzie.
Sprawdzaj wynik, a nie tylko szybkość
Szybka porażka to nadal porażka. Licz rozwiązania zwracające błąd i traktuj rosnący odsetek błędów jako sygnał, by zwolnić. Nieudane rozwiązania nie są naliczane, ale czas na nie poświęcony to nadal czas, który potok stracił. Pełny wzorzec ponawiania znajdziesz w artykule obsługa błędów solvera captcha.