captcha token หมดอายุ: สาเหตุและวิธีแก้
captcha token คือหลักฐานระยะสั้นว่าการท้าทายผ่านแล้ว token ของ PopularCaptcha ของ Scarnox หมดอายุประมาณสองนาทีหลังถูกออก หากคุณ submit token หลังช่วงเวลานั้น เว็บจะปฏิเสธฟอร์ม มักพร้อมข้อความว่า captcha หมดอายุหรือไม่ถูกต้อง วิธีแก้เกือบทั้งหมดเป็นเรื่องของจังหวะเวลา: แก้ captcha ให้ใกล้กับการ submit ที่สุดเท่าที่ทำได้ ใช้ token แต่ละตัวเพียงครั้งเดียว และลองใหม่เฉพาะเมื่อคุณรู้ว่าความพยายามแรกไม่ได้รับการยอมรับ
ทำไม token จึงหมดอายุ
token ไม่ใช่ข้อมูลรับรองถาวร เว็บต้องการรู้ว่ามีคน (หรือ solver ที่ทำแทนคน) ทำการท้าทายสำเร็จเมื่อไม่นานมานี้ ดังนั้นจึงให้ token อายุสั้น นาฬิกาเริ่มเดินตั้งแต่ token ถูกออก ไม่ใช่ตอนที่คุณเรียก API ครั้งแรก ทุกอย่างที่เกิดขึ้นระหว่างการแก้กับการ submit กินเวลาสองนาทีนั้น
สาเหตุที่พบบ่อย
- แก้เร็วเกินไป การแก้ล่วงหน้าเป็นชุดแล้วเก็บไว้ใช้ภายหลังเป็นสาเหตุที่พบบ่อยที่สุด เมื่อถึงเวลาใช้ token ก็เก่าไปแล้ว
- โค้ดที่ช้าระหว่างการแก้กับการ submit การเขียนฐานข้อมูลที่ยาว คิว หรือ
time.sleepที่วางก่อนการโพสต์ฟอร์ม อาจทำให้ token เกินช่วงเวลาที่ใช้ได้ - ใช้ token ซ้ำ token ที่ถูกยอมรับไปแล้ว หรือที่ความพยายามที่ล้มเหลวใช้ไปแล้ว จะไม่ทำงานอีก ให้ถือว่าแต่ละ token ใช้ได้ครั้งเดียว
- รีโหลดหน้า หากหน้าถูกรีเฟรชหลังการแก้ ฟอร์มอาจออกการท้าทายใหม่และทำให้ token ที่คุณถืออยู่ใช้ไม่ได้
วิธีแก้: แก้ทันทีก่อน 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")
ระวังการลองใหม่กับฟอร์มที่สร้างบางอย่างขึ้นมา หากความพยายามแรกได้รับการยอมรับจริงแต่โค้ดของคุณอ่านการตอบกลับผิด การลองใหม่อาจสร้างบัญชี ออร์เดอร์ หรือข้อความซ้ำ ลองใหม่เฉพาะเมื่อคุณมั่นใจว่าการโพสต์แรกไม่ได้รับการยอมรับ และทำให้การตรวจสอบเจาะจงกับเว็บที่คุณกำลังทำงานด้วย
รักษาจังหวะเวลาให้ซื่อตรง
- อย่าแก้ในลูปของ token ที่คุณตั้งใจจะใช้ภายหลัง แก้ต่อการ submit
- รักษางานระหว่างการแก้กับการโพสต์ให้น้อยที่สุด
- ใช้ timeout ประมาณ 185 วินาทีกับคำขอแก้ เพราะการเรียกจะบล็อกจนกว่า token จะพร้อม timer แยกสำหรับการโพสต์ฟอร์มของคุณควรสั้นกว่าอายุของ token มาก
- หากต้องการไปป์ไลน์ที่เร็วขึ้น ดูความเร็วในการแก้ captcha ซึ่งครอบคลุมการแก้แบบขนานโดยไม่ต้องเก็บ token
ข้อผิดพลาดที่พบบ่อย
- แคช token ใน dictionary หรือ Redis เพื่อใช้ภายหลัง แคชนี้ดูไม่เป็นอันตรายจนกว่า token จะถูกใช้หลังช่วงเวลาและทุกการ submit ล้มเหลว
- แก้หนึ่งครั้งต่อ session session ที่ยืนยาวยังต้องการ token ใหม่สำหรับการโพสต์ฟอร์มแต่ละครั้ง เพราะการโพสต์แต่ละครั้งอาจรันการตรวจสอบของตัวเอง
- รอระหว่างการแก้กับการ submit ตัวจำกัดอัตราหรือการหยุดชั่วคราวที่ตั้งใจวางหลังการแก้จะใช้อายุของ token ไป ให้วางการหน่วงใด ๆ ไว้ก่อนการแก้แทน
- ถือทุกความล้มเหลวเหมือนกัน timeout ฝั่งคุณ token ที่ถูกปฏิเสธ และ error ของเว็บ ต้องการการจัดการที่ต่างกัน ให้บันทึกแยกกัน
บันทึกให้พอสำหรับวินิจฉัย
เมื่อการ submit ล้มเหลว ให้บันทึกเวลาที่ token ถูกออก เวลาที่โพสต์ฟอร์ม สถานะ HTTP และอักขระไม่กี่ร้อยตัวแรกของการตอบกลับ ด้วยค่าสี่ตัวนี้ คุณจะเห็นได้ทันทีว่า token หมดอายุหรือเว็บปฏิเสธด้วยเหตุผลอื่น อย่าบันทึก token เต็มในล็อกที่ใช้ร่วมกัน เพราะ token ที่ใช้ได้คือข้อมูลรับรองสำหรับการท้าทายครั้งนั้น
สิ่งที่ควรตรวจหากยังล้มเหลว
- พิมพ์เวลาระหว่างที่ token ถูกส่งกลับกับการโพสต์ฟอร์ม
- ยืนยันว่า
site_urlตรงกับหน้าที่แสดง captcha - ยืนยันว่าอ่าน sitekey จากหน้าปัจจุบัน ไม่ใช่สำเนาเก่า
- ยืนยันชื่อฟิลด์ของฟอร์ม token ของ PopularCaptcha ใส่ใน
h-captcha-responseและมักใส่ในg-recaptcha-responseด้วย
สำหรับรายการการตอบกลับ error ทั้งหมดและวิธีจัดการแต่ละอย่าง ดูการจัดการข้อผิดพลาดของ captcha solver