פתרון reCAPTCHA Enterprise באמצעות Rust

מאת solvethree4 דקות קריאה

השתמשו ב-REST API של solvethree מתוך Rust: צרו משימה, תשאלו עד שהיא מוכנה, ואז שלחו את solution.token ליעד באמצעות זהות הדפדפן שהוחזרה ואותו פרוקסי. הדוגמה הבאה משתמשת ישירות ב-reqwest, ב-Tokio וב-serde_json — לא ב-SDK רשמי ל-Rust.

בדף הזה

הגדירו את האתר והבקשה

תחילה, צרו פרופיל פעיל לשם מארח היעד במסוף של סביבת העבודה שלכם. שליחת שם מארח ופעולה אינה יוצרת פרופיל. ראו הגדרת פרופיל אתר אם היעד עדיין לא מוגדר.

הגדירו את משתני הסביבה הבאים בשרת שבו יפעל לקוח ה-Rust:

  • SOLVETHREE_API_KEY: מפתח ה-API של סביבת העבודה שלכם. שמרו אותו בצד השרת.
  • SOLVETHREE_PROXY: כתובת ה-URL של הפרוקסי לפתרון ולבקשה ליעד שתבוא אחריו.
  • REQUEST_ID: מזהה אופציונלי וקבוע למשימה הלוגית הזו. שמרו אותו לפני שליחת הבקשה כדי שתוכלו לשחזר לאחר ניתוק החיבור.
השדות שבהם משתמשת משימת ה-reCAPTCHA
השדות שבהם משתמשת משימת ה-reCAPTCHA
שדהמה לשלוח
siteשם מארח היעד. החליפו את accounts.example.com שבדוגמה.
actionהפעולה שהיעד מצפה לה. החליפו את login במידת הצורך.
proxyכתובת URL של פרוקסי יחיד, שנקראת מתוך SOLVETHREE_PROXY.
requestIdמפתח אידמפוטנטיות המכיל 1–64 תווי ASCII: אותיות, ספרות, קווים תחתונים או מקפים.
recaptchaTypeמושמט כאן כי ברירת המחדל שלו היא enterprise-v3.

אל תוסיפו שדות type או url לבקשת ה-reCAPTCHA הזו. נדרש בדיוק אחד מהשדות proxy או proxyCandidates; הדוגמה משתמשת ב-proxy כדי שגם ההעברה ליעד תוכל להשתמש באותו חיבור.

צרו את לקוח ה-Rust

הציבו את קובץ התלויות ב-Cargo.toml ואת הלקוח ב-src/main.rs של פרויקט בינארי של Cargo. לאחר הגדרת משתני הסביבה ושינוי שם מארח היעד והפעולה, הריצו את הפרויקט באמצעות Cargo. התלויות מקבעות את reqwest לגרסה 0.13.5.

Cargo.toml · תלויות שנבדקו
[package]
name = "solvethree-example"
version = "0.1.0"
edition = "2021"

[dependencies]
reqwest = { version = "=0.13.5", features = ["json"] }
serde_json = "1"
tokio = { version = "1", features = ["macros", "rt-multi-thread", "time"] }
Rust · קריאות REST אסינכרוניות מוגבלות
use std::{env, error::Error, time::{Duration, SystemTime, UNIX_EPOCH}};
use reqwest::{Client, redirect::Policy};
use serde_json::{json, Value};

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let api = "https://api.solvethree.com/v1";
    let key = env::var("SOLVETHREE_API_KEY")?;
    let request_id = env::var("REQUEST_ID").unwrap_or_else(|_| format!("rust-{}", SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos()));
    let input = json!({"site":"accounts.example.com", "action":"login", "proxy":env::var("SOLVETHREE_PROXY")?, "requestId":request_id});
    let client = Client::builder().redirect(Policy::none()).timeout(Duration::from_secs(30)).build()?;
    let mut task_id: Option<String> = None;
    let outcome = tokio::time::timeout(Duration::from_secs(180), async {
        let response = client.post(format!("{api}/tasks?waitMs=0")).bearer_auth(&key).json(&input).send().await?;
        if !response.status().is_success() { return Err(format!("API HTTP {}", response.status()).into()); }
        let mut result: Value = response.json().await?;
        let id = result["id"].as_str().ok_or("Missing task ID")?.to_owned();
        if !id.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_' || b == b'-') { return Err("Invalid task ID".into()); }
        task_id = Some(id.clone());
        loop {
            match result["status"].as_str() {
                Some("ready") => return Ok::<Value, Box<dyn Error>>(result),
                Some("failed") => return Err(format!("Task failed: {}", result["errorCode"]).into()),
                Some("pending") | Some("solving") => {},
                _ => return Err("Unexpected task status".into()),
            }
            tokio::time::sleep(Duration::from_secs(1)).await;
            let response = client.get(format!("{api}/tasks/{id}?waitMs=15000")).bearer_auth(&key).send().await?;
            if !response.status().is_success() { return Err(format!("API HTTP {}", response.status()).into()); }
            result = response.json().await?;
        }
    }).await;
    match outcome {
        Ok(Ok(result)) => {
            println!("Ready task: {}", result["id"].as_str().unwrap_or("unknown"));
            // Submit result["solution"]["token"] with returned browser headers
            // through the original proxy; do not log the solution.
            Ok(())
        },
        Ok(Err(error)) => {
            eprintln!("Recovery requestId={request_id} taskId={task_id:?}");
            Err(error)
        },
        Err(_) => {
            eprintln!("Recovery requestId={request_id} taskId={task_id:?}");
            Err("Local deadline reached; recover the existing task".into())
        }
    }
}

הלקוח שולח בקשת יצירה אחת אל POST /v1/tasks, עם אימות באמצעות טוקן Bearer. הוא שומר את מזהה המשימה שהוחזר ומשתמש ב-GET /v1/tasks/:id לבקשות הסטטוס הבאות.

שתי מגבלות מונעות מהלקוח להמתין ללא הגבלת זמן: הזמן הקצוב של reqwest מגביל כל בקשת HTTP ל-30 שניות, ואילו הזמן הקצוב של Tokio מגביל את היצירה והתשאול יחד ל-180 שניות. אלה בחירות בצד הלקוח, לא התחייבויות לזמן השלמת השירות. הפניות מושבתות באמצעות ClientBuilder של reqwest.

רק pending ו-solving ממשיכים את לולאת התשאול. ready מחזיר את התוצאה, failed עוצר עם שגיאה, וגם סטטוס לא צפוי עוצר במקום לתשאל לנצח. מסלול ההצלחה מדפיס את מזהה המשימה, לא את הטוקן.

שחזרו בקשה שנקטעה בלי לבצע פתרון נוסף

הדוגמה שומרת בזיכרון את requestId ואת מזהה המשימה שהוחזר, ומדפיסה אותם כשמתרחשת שגיאת HTTP, תעבורה, פענוח או חריגה מהמועד האחרון. היא אינה שומרת אותם באחסון מתמיד לאחר סיום התהליך. לשחזור לאחר הפעלה מחדש, שמרו את מזהה הבקשה ואת קלטי המשימה המקוריים לפני היצירה, ואז שמרו את מזהה המשימה מיד כשהוא מגיע.

  1. 1.

    אם יש לכם מזהה משימה, אחזרו את המשימה הזו

    המשיכו באמצעות GET /v1/tasks/:id במקום ליצור משימה נוספת. נקודת הכניסה הרגילה של הדוגמה תמיד יוצרת משימה; מסלול הפעלה מחדש צריך להשתמש במזהה שנשמר.

  2. 2.

    אם יש לכם רק מזהה בקשה, שחזרו את היצירה

    שלחו את אותה בקשת משימה עם אותו requestId. היא מחזירה את המשימה הקיימת. שינוי הקלטים תוך שימוש חוזר במזהה גורם להתנגשות.

  3. 3.

    טפלו בסטטוס שאוחזר בשחזור

    המשיכו לתשאל משימות בסטטוס pending או solving. השתמשו בפתרון בסטטוס ready, או עצרו בסטטוס failed ובדקו את errorCode ואת error. פקיעת זמן קצוב מקומי לבדה אינה מעידה שהמשימה המרוחקת נכשלה.

כשלי HTTP וכשלי משימות דורשים טיפול שונה. הלקוח בודק אם תגובות ה-HTTP אינן מציינות הצלחה לפני פענוח מטען נתוני הצלחה. לעומת זאת, כישלון סופי של משימה מגיע כסטטוס המשימה failed; על פתרונות שנכשלו ניתן החזר כספי. הודעת הכישלון בדוגמה כוללת רק את errorCode, ולכן בעת האבחון בדקו גם את error בתגובת המשימה. ראו טיפול בשגיאות API לפני ניסיון חוזר עם קלטים ללא שינוי.

שלחו את הטוקן עם זהות הדפדפן שלו

הדוגמה נעצרת כשהמשימה מוכנה; היא אינה שולחת את בקשת היישום ליעד. הבקשה הזו זקוקה ל-solution.token, לא למזהה המשימה. השתמשו בזהות userAgent ו-secChUa שהוחזרה דרך הפרוקסי המקורי, ושלחו ליעד המיועד במקום לערבב ערכים מהפעלת דפדפן אחרת.

משימה מוכנה פירושה שנוצר פתרון, לא שהיעד קיבל את בקשת היישום שלכם. הוסיפו את השליחה המותאמת ליעד במקום המסומן בהערה במסלול ההצלחה; ראו את מדריך השילוב של reCAPTCHA Enterprise להנחיות להעברת הטוקן.

הפעלת המשימה הראשונה שלך.

התחילו עם $5 בקרדיט לסביבת העבודה.

יצירת סביבת עבודה
פתרון reCAPTCHA Enterprise באמצעות Rust · solvethree