Token captcha wygasł: przyczyny i jak to naprawić
Token captcha to krótkotrwały dowód, że wyzwanie zostało pokonane. Tokeny PopularCaptcha w Scarnox wygasają około dwóch minut po wydaniu. Jeśli wyślesz token po tym oknie, serwis odrzuci formularz, często z komunikatem, że captcha wygasła lub jest nieprawidłowa. Poprawka prawie zawsze dotyczy czasu: rozwiąż captcha jak najbliżej wysyłki, używaj każdego tokenu jeden raz i ponawiaj tylko wtedy, gdy wiesz, że pierwsza próba nie została zaakceptowana.
Dlaczego tokeny wygasają
Token nie jest trwałym poświadczeniem. Serwis chce wiedzieć, że człowiek (albo solver działający w jego imieniu) ukończył wyzwanie niedawno, więc nadaje tokenowi krótki czas życia. Zegar rusza, gdy token zostaje wydany, a nie gdy po raz pierwszy wywołujesz API. Wszystko, co dzieje się między rozwiązaniem a wysyłką, zjada te dwie minuty.
Częste przyczyny
- Rozwiązywanie zbyt wcześnie. Wstępne rozwiązanie partii tokenów i trzymanie ich na później to najczęstsza przyczyna. Gdy token jest używany, jest już nieaktualny.
- Wolny kod między rozwiązaniem a wysyłką. Długi zapis do bazy, kolejka albo
time.sleepumieszczony przed wysłaniem formularza mogą wypchnąć token poza jego okno. - Ponowne użycie tokenu. Token, który został już zaakceptowany albo który użyła nieudana próba, nie zadziała ponownie. Traktuj każdy token jako jednorazowy.
- Przeładowanie strony. Jeśli strona zostanie odświeżona po rozwiązaniu, formularz może wygenerować nowe wyzwanie i unieważnić token, który trzymasz.
Poprawka: rozwiąż tuż przed wysłaniem
Najprostszy niezawodny wzorzec to połączenie rozwiązania i wysyłki w jeden krok. Poproś o token, wstaw go do formularza, wyślij formularz i nie rób nic wolnego pomiędzy:
import requests
API_KEY = "scarnox_YOURAPIKEY"
CREATE_URL = "https://api.scarnox.com/api/tasks/create"
def fresh_token(sitekey: str, page_url: str) -> str:
r = requests.post(
CREATE_URL,
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"captcha_type": "PopularCaptchaTaskProxyless",
"site_key": sitekey,
"site_url": page_url,
},
timeout=185, # wywołanie blokuje się, aż token będzie gotowy
)
data = r.json()
if data.get("errorId") != 0:
raise RuntimeError(f"{data.get('errorCode')}: {data.get('errorDescription')}")
return data["solution"]["token"]
def submit_form(page_url: str, sitekey: str, form: dict) -> requests.Response:
token = fresh_token(sitekey, page_url) # rozwiąż tuż przed wysłaniem
return requests.post(
page_url,
data={**form, "h-captcha-response": token, "g-recaptcha-response": token},
timeout=30,
)
Zauważ, że submit_form nie trzyma tokenu w cache ani w kolejce. Prosi o jeden, wysyła go i idzie dalej. Nieudane rozwiązania nie są naliczane, więc nieudane żądanie nic nie kosztuje po stronie rozwiązania.
Ponawiaj bezpiecznie
Niektóre serwisy odrzucają wygasły token w wyraźny sposób. Inne zwracają zwykłą stronę z komunikatem o błędzie w środku, więc sam kod statusu nic nie powie. Sprawdź, jaką odpowiedź serwis faktycznie zwraca dla wygasłego tokenu, i zbuduj kontrolę na jej podstawie. Pętla ponawiania może wyglądać tak:
def submit_with_retry(page_url: str, sitekey: str, form: dict, attempts: int = 2):
for _ in range(attempts):
resp = submit_form(page_url, sitekey, form)
if resp.status_code < 400 and "captcha" not in resp.text.lower():
return resp # zaakceptowane, o ile wskazuje na to odpowiedź tego serwisu
raise RuntimeError("captcha token rejected after retries")
Zachowaj ostrożność przy ponowieniach w formularzach, które coś tworzą. Jeśli pierwsza próba faktycznie została zaakceptowana, ale Twój kod źle odczytał odpowiedź, ponowienie może utworzyć duplikat konta, zamówienia lub wiadomości. Ponawiaj tylko wtedy, gdy masz pewność, że pierwsze wysłanie nie zostało zaakceptowane, i uczyń kontrolę konkretną dla serwisu, z którym pracujesz.
Dbaj o uczciwy czas
- Nie rozwiązuj w pętli tokenów, które zamierzasz użyć później. Rozwiązuj przy każdej wysyłce.
- Ogranicz pracę między rozwiązaniem a wysłaniem do minimum.
- Używaj timeoutu około 185 sekund na żądanie rozwiązania, bo wywołanie blokuje się, aż token będzie gotowy. Osobny timer na własnym wysłaniu formularza powinien być znacznie krótszy niż czas życia tokenu.
- Jeśli potrzebujesz szybszego potoku, zobacz szybkie rozwiązywanie captcha, które omawia równoległe rozwiązania bez przechowywania tokenów.
Częste błędy
- Cache'owanie tokenów w słowniku lub w Redis na później. Cache wygląda niegroźnie, dopóki token nie zostanie użyty po swoim oknie i każde wysłanie nie zawiedzie.
- Rozwiązywanie raz na sesję. Długo żyjąca sesja nadal potrzebuje nowego tokenu przy każdym wysłaniu formularza, bo każde wysłanie może uruchomić własną weryfikację wyzwania.
- Uśpienie między rozwiązaniem a wysłaniem. Ogranicznik tempa albo celowa pauza umieszczona po rozwiązaniu zużywa czas życia tokenu. Umieść wszelkie odstępy przed rozwiązaniem.
- Traktowanie każdego błędu tak samo. Timeout po Twojej stronie, odrzucony token i błąd serwisu wymagają różnej obsługi. Zapisuj je osobno.
Zapisuj dość, by zdiagnozować problem
Gdy wysyłka się nie powiedzie, zapisz czas wydania tokenu, czas wysłania formularza, status HTTP i pierwsze kilkaset znaków odpowiedzi. Dzięki tym czterem wartościom od razu zobaczysz, czy token się przeterminował, czy serwis odrzucił go z innego powodu. Nie zapisuj pełnego tokenu we wspólnych logach, bo ważny token to poświadczenie dla tego jednego wyzwania.
Co sprawdzić, gdy nadal nie działa
- Wypisz czas między zwróceniem tokenu a wysłaniem formularza.
- Potwierdź, że
site_urlzgadza się ze stroną, na której pojawia się captcha. - Potwierdź, że sitekey został odczytany z aktualnej strony, a nie z nieaktualnej kopii.
- Potwierdź nazwy pól formularza. Tokeny PopularCaptcha trafiają do
h-captcha-response, a często także dog-recaptcha-response.
Pełną listę odpowiedzi błędów i sposób obsługi każdej z nich znajdziesz w artykule obsługa błędów solvera captcha.