Scarnox · Blog

Captcha token đã hết hạn: nguyên nhân và cách khắc phục

Token captcha là bằng chứng ngắn hạn rằng một thử thách đã được vượt qua. Token PopularCaptcha của Scarnox hết hạn khoảng hai phút sau khi được cấp. Nếu bạn submit token sau khoảng thời gian đó, website sẽ từ chối form, thường kèm thông báo rằng captcha đã hết hạn hoặc không hợp lệ. Cách sửa hầu như luôn liên quan đến thời điểm: giải captcha càng gần lúc submit càng tốt, mỗi token chỉ dùng một lần, và chỉ thử lại khi bạn biết lần đầu chưa được chấp nhận.

Vì sao token hết hạn

Token không phải là một thông tin xác thực vĩnh viễn. Website muốn biết rằng một người (hoặc một bộ giải thay mặt người đó) vừa hoàn thành thử thách, nên nó cho token một thời gian sống ngắn. Đồng hồ bắt đầu chạy khi token được cấp, không phải khi bạn gọi API lần đầu. Mọi việc diễn ra giữa lúc giải và lúc submit đều làm mất đi khoảng hai phút đó.

Các nguyên nhân thường gặp

Cách sửa: giải ngay trước khi submit

Mẫu đáng tin cậy nhất là biến việc giải và submit thành một bước. Yêu cầu một token, đặt nó vào form, gửi form, và không làm gì chậm ở giữa:

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,  # lời gọi bị chặn cho đến khi token sẵn sàng
    )
    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)  # giải ngay trước khi gửi
    return requests.post(
        page_url,
        data={**form, "h-captcha-response": token, "g-recaptcha-response": token},
        timeout=30,
    )

Lưu ý submit_form không giữ token trong cache hay hàng đợi. Nó yêu cầu một token, gửi đi, rồi tiếp tục. Các lần giải thất bại không bị tính phí, nên một yêu cầu thất bại không tốn gì ở phía giải.

Thử lại an toàn

Một số website từ chối token hết hạn bằng một phản hồi rõ ràng. Những website khác trả về một trang bình thường với thông báo lỗi bên trong, nên mã trạng thái một mình không cho bạn biết được điều gì. Hãy xem phản hồi thực tế mà website trả về cho token hết hạn và xây dựng kiểm tra của bạn dựa trên đó. Một vòng lặp thử lại có thể trông như sau:

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  # được chấp nhận, theo phản hồi của website này
    raise RuntimeError("captcha token rejected after retries")

Hãy cẩn thận với việc thử lại trên các form tạo ra thứ gì đó. Nếu lần thử đầu thực sự được chấp nhận nhưng code của bạn đọc sai phản hồi, một lần thử lại có thể tạo ra tài khoản, đơn hàng hoặc tin nhắn trùng lặp. Chỉ thử lại khi bạn chắc chắn lần gửi đầu chưa được chấp nhận, và làm cho kiểm tra cụ thể với website bạn đang làm việc cùng.

Canh thời gian cho đúng

Các lỗi thường gặp

Ghi log đủ để chẩn đoán

Khi một lần submit thất bại, hãy ghi thời điểm token được cấp, thời điểm form được gửi, mã HTTP và vài trăm ký tự đầu của phản hồi. Với bốn giá trị này, bạn có thể thấy ngay token đã hết hạn hay website từ chối vì lý do khác. Đừng ghi toàn bộ token vào log dùng chung, vì một token hợp lệ là thông tin xác thực cho đúng thử thách đó.

Kiểm tra gì khi vẫn thất bại

  1. In ra khoảng thời gian giữa lúc token được trả về và lúc gửi form.
  2. Xác nhận site_url khớp với trang đang hiển thị captcha.
  3. Xác nhận sitekey được đọc từ trang hiện tại, không phải từ một bản sao cũ.
  4. Xác nhận tên trường form. Token PopularCaptcha đi vào h-captcha-response, và thường cả g-recaptcha-response.

Để xem đầy đủ các phản hồi lỗi và cách xử lý từng loại, xem xử lý lỗi bộ giải captcha.

Các bước tiếp theo