Scarnox · Blog

captcha token หมดอายุ: สาเหตุและวิธีแก้

captcha token คือหลักฐานระยะสั้นว่าการท้าทายผ่านแล้ว token ของ PopularCaptcha ของ Scarnox หมดอายุประมาณสองนาทีหลังถูกออก หากคุณ submit token หลังช่วงเวลานั้น เว็บจะปฏิเสธฟอร์ม มักพร้อมข้อความว่า captcha หมดอายุหรือไม่ถูกต้อง วิธีแก้เกือบทั้งหมดเป็นเรื่องของจังหวะเวลา: แก้ captcha ให้ใกล้กับการ submit ที่สุดเท่าที่ทำได้ ใช้ token แต่ละตัวเพียงครั้งเดียว และลองใหม่เฉพาะเมื่อคุณรู้ว่าความพยายามแรกไม่ได้รับการยอมรับ

ทำไม token จึงหมดอายุ

token ไม่ใช่ข้อมูลรับรองถาวร เว็บต้องการรู้ว่ามีคน (หรือ solver ที่ทำแทนคน) ทำการท้าทายสำเร็จเมื่อไม่นานมานี้ ดังนั้นจึงให้ token อายุสั้น นาฬิกาเริ่มเดินตั้งแต่ token ถูกออก ไม่ใช่ตอนที่คุณเรียก API ครั้งแรก ทุกอย่างที่เกิดขึ้นระหว่างการแก้กับการ submit กินเวลาสองนาทีนั้น

สาเหตุที่พบบ่อย

วิธีแก้: แก้ทันทีก่อน submit

รูปแบบที่เชื่อถือได้ที่สุดคือทำให้การแก้และการ submit เป็นขั้นตอนเดียวกัน ขอ token ใส่ลงในฟอร์ม โพสต์ฟอร์ม และไม่ทำอะไรช้า ๆ ระหว่างนั้น

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,  # การเรียกจะรอจนกว่า token จะพร้อม
    )
    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)  # แก้ทันทีก่อนโพสต์
    return requests.post(
        page_url,
        data={**form, "h-captcha-response": token, "g-recaptcha-response": token},
        timeout=30,
    )

สังเกตว่า submit_form ไม่เก็บ token ไว้ในแคชหรือคิว มันขอ token หนึ่งตัว โพสต์ และไปต่อ การแก้ที่ล้มเหลวไม่มีค่าใช้จ่าย ดังนั้นคำขอที่ล้มเหลวจึงไม่เสียอะไรในฝั่งการแก้

ลองใหม่อย่างปลอดภัย

เว็บบางแห่งปฏิเสธ token ที่หมดอายุด้วยการตอบกลับที่ชัดเจน บางแห่งส่งหน้าปกติที่มีข้อความ error อยู่ข้างใน ดังนั้นรหัสสถานะเพียงอย่างเดียวจะไม่บอกคุณ ดูการตอบกลับที่เว็บส่งจริงเมื่อ token หมดอายุ และสร้างการตรวจสอบของคุณจากตรงนั้น ลูปลองใหม่อาจมีลักษณะดังนี้

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  # ถือว่าผ่าน ตามที่การตอบกลับของเว็บนี้แสดง
    raise RuntimeError("captcha token rejected after retries")

ระวังการลองใหม่กับฟอร์มที่สร้างบางอย่างขึ้นมา หากความพยายามแรกได้รับการยอมรับจริงแต่โค้ดของคุณอ่านการตอบกลับผิด การลองใหม่อาจสร้างบัญชี ออร์เดอร์ หรือข้อความซ้ำ ลองใหม่เฉพาะเมื่อคุณมั่นใจว่าการโพสต์แรกไม่ได้รับการยอมรับ และทำให้การตรวจสอบเจาะจงกับเว็บที่คุณกำลังทำงานด้วย

รักษาจังหวะเวลาให้ซื่อตรง

ข้อผิดพลาดที่พบบ่อย

บันทึกให้พอสำหรับวินิจฉัย

เมื่อการ submit ล้มเหลว ให้บันทึกเวลาที่ token ถูกออก เวลาที่โพสต์ฟอร์ม สถานะ HTTP และอักขระไม่กี่ร้อยตัวแรกของการตอบกลับ ด้วยค่าสี่ตัวนี้ คุณจะเห็นได้ทันทีว่า token หมดอายุหรือเว็บปฏิเสธด้วยเหตุผลอื่น อย่าบันทึก token เต็มในล็อกที่ใช้ร่วมกัน เพราะ token ที่ใช้ได้คือข้อมูลรับรองสำหรับการท้าทายครั้งนั้น

สิ่งที่ควรตรวจหากยังล้มเหลว

  1. พิมพ์เวลาระหว่างที่ token ถูกส่งกลับกับการโพสต์ฟอร์ม
  2. ยืนยันว่า site_url ตรงกับหน้าที่แสดง captcha
  3. ยืนยันว่าอ่าน sitekey จากหน้าปัจจุบัน ไม่ใช่สำเนาเก่า
  4. ยืนยันชื่อฟิลด์ของฟอร์ม token ของ PopularCaptcha ใส่ใน h-captcha-response และมักใส่ใน g-recaptcha-response ด้วย

สำหรับรายการการตอบกลับ error ทั้งหมดและวิธีจัดการแต่ละอย่าง ดูการจัดการข้อผิดพลาดของ captcha solver

ขั้นตอนถัดไป