การจัดการข้อผิดพลาดของ captcha solver: การลองใหม่ backoff และสิ่งที่ไม่ควรลองซ้ำ
การแก้ captcha คือการเรียกเครือข่ายไปยังระบบที่คุณไม่ได้ควบคุม เพื่อท้าทายที่อาจเปลี่ยนโดยไม่แจ้ง การจัดการความล้มเหลวให้ดีคือความแตกต่างระหว่างไปป์ไลน์ที่ลดระดับลงอย่างสง่างามกับไปป์ไลน์ที่เสียเวลาไปกับพายุการลองซ้ำ
กับ Scarnox ค่าใช้จ่ายช่วยคุณอยู่แล้ว การแก้ที่ล้มเหลวไม่มีค่าใช้จ่าย ดังนั้นการลองซ้ำมีต้นทุนแค่เวลา แต่การลองซ้ำความล้มเหลวที่ผิดประเภทก็ยังเสียเวลานั้นไป และการลองซ้ำหนักเกินไปอาจก่อปัญหาของตัวเอง คู่มือนี้ให้นโยบายการลองซ้ำที่คุณนำไปวางในงาน Python ได้
Scarnox แก้ PopularCaptcha ได้ในวันนี้ และการจัดการข้อผิดพลาดด้านล่างใช้ได้กับ captcha solver ทุกตัว นโยบายจึงไม่ขึ้นอยู่กับประเภทของ captcha
ตรวจ errorId ก่อนเสมอ
การตอบกลับการแก้มีฟิลด์ errorId 0 หมายถึงสำเร็จ และ token อยู่ภายใต้ solution.token ค่าอื่นใดหมายถึงการแก้ล้มเหลว การตอบกลับที่ล้มเหลวจะมี errorCode และ errorDescription ด้วย ให้บันทึกทั้งสองค่า เพราะโค้ดคือสิ่งที่คุณจะค้นหาภายหลัง และคำอธิบายคือสิ่งที่คุณจะอ่านเมื่อการรันมีปัญหา
{"errorId": 1, "errorCode": "...", "errorDescription": "..."}
อย่าสมมติว่าการตอบกลับเป็นความสำเร็จเพียงเพราะสถานะ HTTP เป็น 200 ตรวจ errorId ทุกครั้ง
ความล้มเหลวที่ควรลองใหม่
ความล้มเหลวบางอย่างเป็นการชั่วคราว ให้ลองใหม่ด้วย exponential backoff และการหน่วงสุ่มเล็กน้อย
- ข้อผิดพลาดเครือข่าย การเชื่อมต่อถูกรีเซ็ต ปัญหา DNS ชั่วคราว หรือ read timeout ฝั่งคุณ
- ข้อผิดพลาดของเซิร์ฟเวอร์ การตอบกลับ HTTP 5xx ซึ่งหมายถึงปัญหาฝั่งบริการ
- การจำกัดอัตรา HTTP 429 ซึ่งหมายความว่าคุณส่งคำขอเร็วเกินไป ให้ชะลอลง
- timeout การแก้ใช้เวลานานกว่าที่ client ของคุณรอ ลองใหม่พร้อม timeout ที่ยาวขึ้น หากคุณยังไม่ได้ตั้งค่านั้น
ความล้มเหลวเหล่านี้คุ้มค่าแก่การลองใหม่ เพราะการลองครั้งถัดไปอาจสำเร็จโดยที่คุณไม่ต้องเปลี่ยนอะไร
ความล้มเหลวที่ต้องแก้ก่อนลองใหม่
ความล้มเหลวอื่นจะไม่หายไปด้วยการรอ การลองใหม่ด้วยอินพุตเดิมจะล้มเหลวแบบเดิม ให้แก้สิ่งเหล่านี้ก่อน
- ประเภทงานผิด ตรวจว่า
captcha_typeสะกดตรงกับเอกสาร - sitekey ผิดหรือหายไป อ่าน sitekey จากหน้าใหม่ sitekey จากการ deploy เก่าเป็นสาเหตุที่พบบ่อย
- URL ของหน้าผิด
site_urlควรเป็นหน้าที่แสดง widget - ปัญหา proxy proxy ที่ตาย พิมพ์ผิด หรือถูกบล็อกจะทำให้ทุกการลองล้มเหลว ทดสอบ proxy แยกต่างหากก่อน ดู proxy กับ proxyless
- ปัญหาบัญชี key ที่ไม่ถูกต้องหรือบัญชีที่ยอดเงินไม่พอจะล้มเหลวทุกครั้งจนกว่าจะมีคนแก้บัญชี ให้แจ้งคนและอย่าลองซ้ำในลูป
เมื่อความล้มเหลวไม่อยู่ในรายการใดเลย ให้ถือว่าเป็นความล้มเหลวประเภทต้องแก้ก่อน ดูข้อความ error ตัดสินใจ และเพิ่มโค้ดลงในรายการที่เหมาะสมในโค้ดของคุณ รักษารายการให้สั้นและผ่านการทบทวน
นโยบายที่นำไปวางได้
import os
import random
import time
import requests
API_URL = "https://api.scarnox.com/api/tasks/create"
HEADERS = {"Authorization": f"Bearer {os.environ['SCARNOX_KEY']}"}
MAX_ATTEMPTS = 4
class SolveError(Exception):
def __init__(self, code: str, description: str, retryable: bool):
super().__init__(f"{code}: {description}")
self.code = code
self.retryable = retryable
def solve_once(payload: dict) -> str:
try:
r = requests.post(API_URL, headers=HEADERS, json=payload, timeout=185)
except requests.RequestException as exc:
# ข้อผิดพลาดของเครือข่ายหรือ timeout ฝั่งเรา: ลองใหม่ได้อย่างปลอดภัย
raise SolveError("NETWORK", str(exc), retryable=True)
if r.status_code == 429 or r.status_code >= 500:
raise SolveError(f"HTTP_{r.status_code}", r.text[:200], retryable=True)
try:
data = r.json()
except ValueError:
raise SolveError(f"HTTP_{r.status_code}", r.text[:200], retryable=False)
if data.get("errorId") == 0:
return data["solution"]["token"]
# API รายงานความล้มเหลว ลองใหม่เฉพาะโค้ดที่เคยเห็นว่าเป็นการชั่วคราวเท่านั้น
code = data.get("errorCode", "UNKNOWN")
retryable = code in TRANSIENT_CODES
raise SolveError(code, data.get("errorDescription", ""), retryable=retryable)
# เพิ่มรหัสข้อผิดพลาดของ API ที่นี่ต่อเมื่อเห็นใน log แล้วว่าเป็นชั่วคราวเท่านั้น
TRANSIENT_CODES: set[str] = set()
def solve(payload: dict) -> str:
for attempt in range(MAX_ATTEMPTS):
try:
return solve_once(payload)
except SolveError as err:
last_attempt = attempt == MAX_ATTEMPTS - 1
if not err.retryable or last_attempt:
raise
delay = min(30, 2 ** attempt) + random.random() # รอนานขึ้นทีละขั้น + ค่าสุ่มเล็กน้อย
print(f"attempt {attempt + 1} failed ({err.code}), retrying in {delay:.1f}s")
time.sleep(delay)
raise RuntimeError("unreachable")
นโยบายนี้ไม่ลองซ้ำความล้มเหลวประเภทต้องแก้ก่อน และหยุดหลังจำนวนครั้งที่กำหนดไว้ ชุด TRANSIENT_CODES เริ่มต้นว่างโดยตั้งใจ: เพิ่มโค้ดเฉพาะเมื่อ log ของคุณแสดงว่าโค้ดนั้นเป็นการชั่วคราวแล้วเท่านั้น
ทำไม jitter จึงสำคัญ
หากผู้ทำงานห้าสิบคนล้มเหลวพร้อมกันและทุกคนหลับสองวินาทีพอดี พวกเขาจะลองใหม่พร้อมกันและล้มเหลวพร้อมกันอีกครั้ง การสุ่มเศษวินาทีเล็กน้อยช่วยกระจายพวกเขาออกไป นี่คือคำแนะนำเดียวกับที่ผู้ให้บริการ API รายใหญ่หลายรายมอบให้ไคลเอนต์ของตนเอง
timeout
ตั้ง timeout ของ HTTP ให้สูงกว่าเวลาการแก้ปกติมาก ประมาณ 185 วินาที คำขอจะบล็อกจนกว่า token จะพร้อม ดังนั้น timeout ฝั่ง client ที่สั้นอาจเปลี่ยนความสำเร็จที่ช้าให้เป็นความล้มเหลวที่คุณลองใหม่โดยเปล่าประโยชน์ timeout ของ client ที่ยาวกว่าการแก้คือการตั้งค่าที่ปลอดภัย
token แคชไม่ได้
token หมดอายุภายในประมาณสองนาที อย่าแคช token ข้ามการรัน และอย่าใช้ token เดิมซ้ำสำหรับการ submit ครั้งที่สอง หาก submit ล้มเหลวหลังจากใช้ token ไปแล้ว ให้แก้ใหม่ ไม่ใช่ส่ง token เก่าซ้ำ คู่มือการหมดอายุของ token อธิบายจังหวะเวลาอย่างละเอียดกว่า
การทำงานพร้อมกันและ backoff ข้ามผู้ทำงาน
หากผู้ทำงานหลายคนใช้ key เดียวกัน backoff ของผู้ทำงานคนหนึ่งจะไม่ชะลอคนอื่น เพิ่มขีดจำกัดร่วมของการแก้พร้อมกัน เช่น semaphore หรือ queue เพื่อไม่ให้การลองซ้ำที่พุ่งพรวดกลายเป็นคำขอที่พุ่งพรวด การจำกัดการทำงานพร้อมกันอย่างง่ายแก้ความล้มเหลวจากการจำกัดอัตราได้ส่วนใหญ่ก่อนจะเกิดขึ้น
การบันทึกที่มีประโยชน์
สำหรับทุกความพยายามที่ล้มเหลว ให้บันทึก errorCode หมายเลขความพยายาม sitekey โฮสต์ของหน้า และว่าความล้มเหลวนั้นถูกลองใหม่หรือไม่ อย่าบันทึก API key หรือรหัสผ่านของ proxy แดชบอร์ดที่นับโค้ดต่อชั่วโมงบอกคุณได้เร็วว่าปัญหาอยู่ที่โค้ดของคุณ proxy หรือบริการ
ขั้นตอนถัดไป
- วิธีแก้ PopularCaptcha ด้วย Python
- Proxy หรือ proxyless: ควรใช้ประเภทงานใด
- ทำ CI ให้ผ่านเมื่อแอปมี captcha
- ทดลองใช้ captcha solver ฟรี
ทดสอบการจัดการข้อผิดพลาดของคุณกับการทดลองใช้ฟรี: สร้างบัญชี และเริ่มด้วยเครดิตทดลองใช้ฟรี ไม่ต้องใช้บัตร