שימוש ב-Guzzle עם API המשימות של CAPTCHA

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

השתמשו ב-Guzzle 7 כדי ליצור משימה, ואז תשאלו את המזהה שלה עד שהמשימה תהיה במצב ready או failed. שמרו את requestId למקרה שמשהו ישתבש: חריגה מזמן החיבור אינה מוכיחה שיצירת המשימה נכשלה.

זהו שילוב REST, לא SDK רשמי ל-PHP.

בדף הזה

הכינו את בקשת reCAPTCHA

  • התקינו את Guzzle 7 באמצעות Composer. פקודת ההתקנה מופיעה בראש הדוגמה.
  • הגדירו את SOLVETHREE_API_KEY למפתח ה-API של סביבת העבודה שלכם ואת SOLVETHREE_PROXY לכתובת ה-URL של הפרוקסי. שמרו את המפתח בשרת.
  • צרו במסוף פרופיל פעיל לאתר היעד שלכם לפני בקשת פתרון. ראו הגדרת פרופיל אתר.
  • החליפו את accounts.example.com ואת login בשם המארח ובפעולה של היעד. השדה site מקבל שם מארח, לא כתובת URL מלאה.

הדוגמה מבקשת reCAPTCHA Enterprise v3, ברירת המחדל של recaptchaType. אל תוסיפו שדות type או url. היא מספקת פרוקסי אחד; אם אתם משתמשים במקום זאת ב-proxyCandidates, השמיטו את proxy לחלוטין.

צרו פעם אחת, ואז תשאלו במסגרת זמן קבועה

Guzzle · בקשת POST אחת ותשאול מוגבל
<?php
// composer require guzzlehttp/guzzle:^7
require __DIR__.'/vendor/autoload.php';

use GuzzleHttp\Client;

$client = new Client([
    'base_uri' => 'https://api.solvethree.com/v1/',
    'headers' => ['Authorization' => 'Bearer '.getenv('SOLVETHREE_API_KEY'), 'Accept' => 'application/json'],
    'allow_redirects' => false,
    'http_errors' => false,
]);
$input = ['site' => 'accounts.example.com', 'action' => 'login',
    'proxy' => getenv('SOLVETHREE_PROXY'), 'requestId' => bin2hex(random_bytes(16))];
$deadline = microtime(true) + 180;
$call = function (string $method, string $path, array $options = []) use ($client, $deadline): array {
    $remaining = $deadline - microtime(true);
    if ($remaining <= 0) throw new RuntimeException('Local deadline reached');
    $response = $client->request($method, $path, $options + ['timeout' => min(25, $remaining), 'connect_timeout' => min(10, $remaining)]);
    if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) {
        throw new RuntimeException('API HTTP '.$response->getStatusCode());
    }
    return json_decode((string)$response->getBody(), true, 512, JSON_THROW_ON_ERROR);
};
$id = null;
try {
    $result = $call('POST', 'tasks?waitMs=0', ['json' => $input]);
    $id = $result['id'];
    while (in_array($result['status'], ['pending', 'solving'], true)) {
        $result = $call('GET', 'tasks/'.rawurlencode($id).'?waitMs=15000');
        if (in_array($result['status'], ['pending', 'solving'], true)) usleep(1000000);
    }
    if ($result['status'] !== 'ready') {
        throw new RuntimeException('Task failed: '.($result['errorCode'] ?? 'unknown'));
    }
    echo 'Ready task: '.$id.PHP_EOL;
    // Submit $result['solution']['token'] with userAgent/secChUa and the same proxy.
} catch (Throwable $error) {
    fwrite(STDERR, 'Recovery requestId='.$input['requestId'].' taskId='.($id ?? 'unknown').PHP_EOL);
    throw $error;
}

הלקוח מזדהה באמצעות מפתח Bearer. ה-base_uri שלו מסתיים ב-/v1/, ונתיבי הבקשות יחסיים, כך שהם שומרים על קידומת ה-API הזו. האפשרות json של Guzzle שולחת את קלט המשימה כ-JSON.

פונקציית העזר שולחת בקשת POST אחת ליצירה. לאחר מכן היא מתשאלת את מזהה המשימה שהוחזר כל עוד status הוא pending או solving. היצירה והתשאול חולקים מסגרת זמן של 180 שניות; כל קריאת HTTP מוגבלת לזמן שנותר, עם זמני המתנה מרביים של 25 שניות לבקשה ו-10 שניות לחיבור, בהתאמה. תשאול חדש אינו מאפס את מסגרת הזמן הכוללת.

הפניות מחדש מושבתות. הגדרת http_errors ל-false מאפשרת לפונקציית העזר לבדוק במפורש תגובות שאינן 2xx לפני פענוח תוכן של תגובה מוצלחת; היא אינה משתיקה שגיאות חיבור או חריגות מזמן ההמתנה.

  • תגובת HTTP שאינה 2xx גורמת לזריקת שגיאת HTTP של ה-API. אבחנו את הסטטוס ואת גוף התגובה במקום להתייחס אליה כאל פתרון שנכשל.
  • משימה במצב הסופי failed עוצרת את התשאול. הדוגמה זורקת את ה-errorCode שלה; בדקו גם את השדה error של המשימה כדי לקבל את הודעת האבחון. על פתרונות שנכשלו ניתן החזר.
  • רק ready מכיל פתרון שמיש. השורה המודפסת “Ready task” מדווחת שהמשימה מוכנה, לא שאתר היעד קיבל את הפתרון.

שחזור בקשה שחרגה מהזמן הקצוב בלי לשלם על משימה נוספת

שמרו את requestId שנוצר ואת קלט המשימה המדויק לפני שליחת ה-POST. בלוק ה-catch מדפיס את requestId, ואם היצירה החזירה מזהה — גם את taskId. הפלט הזה מסייע בשחזור, אך אינו תחליף לשמירת הבקשה לפני שליחתה.

  1. 1.

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

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

  2. 2.

    אם היצירה חרגה מזמן ההמתנה בלי להחזיר מזהה, שחזרו באמצעות requestId

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

  3. 3.

    אל תריצו שוב את הסקריפט כקיצור דרך לשחזור

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

שלחו את האסימון דרך אותו פרוקסי

הדוגמה נעצרת לפני הפנייה ליעד. שלחו את solution.token — לא את מזהה המשימה — והשתמשו ב-solution.userAgent וב-solution.secChUa שהוחזרו ככותרות User-Agent ו-Sec-CH-UA של בקשת היעד.

הפרוקסי שב-JSON של המשימה מורה ל-solvethree באיזה פרוקסי להשתמש לפתרון. הוא אינו מגדיר את החיבור של Guzzle ל-API או את הבקשה שתשלחו בהמשך ליעד. הגדירו את בקשת היעד בנפרד לשימוש באותו פרוקסי.

השתמשו בלקוח נפרד לבקשת היעד כדי שכותרת Authorization המוגדרת כברירת מחדל בלקוח ה-API לא תישלח לאתר. שמרו יחד את האסימון, זהות הדפדפן שהוחזרה, הפרוקסי והיעד המיועד. גם אסימון מוכן עלול להידחות אם הבקשה שאחריו אינה תואמת.

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

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

יצירת סביבת עבודה
שימוש ב-Guzzle עם API המשימות של CAPTCHA · solvethree