استخدام CAPTCHA API مع Selenium بلغة Java

بقلم solvethreeمدة القراءة: 5 دقيقة

اطلب الرمز أولًا، ثم أعِدّ Chrome باستخدام الوكيل نفسه وuserAgent وsecChUa المُعادين قبل الانتقال إلى الصفحة. ضع solution.token في حقل CAPTCHA في تطبيقك، وأرسل النموذج، وانتظر حالة النجاح في التطبيق، لا مجرد مهمة جاهزة.

تستخدم الأمثلة أداة REST مساعدة مكتوبة بلغة Java وSelenium ChromeDriver. ولا تتضمن كشفًا تلقائيًا لـ CAPTCHA أو إضافة للمتصفح.

في هذه الصفحة

تجهيز مشروع Java والموقع المستهدف

  1. 1.

    إعداد المشروع

    استخدم Java 21 أو أحدث. في مشروع Maven، اضبط مستوى ترجمة Java على 21 وأضف Gson 2.15.0 وorg.seleniumhq.selenium:selenium-java:4.52.0. أعِدّ Chrome وChromeDriver باستخدام وثائق Chrome في Selenium.

  2. 2.

    حفظ ملفَي Java

    احفظ المثال الأول باسم src/main/java/Solve.java والثاني باسم src/main/java/BrowserSolve.java. أبقهما في الحزمة نفسها؛ فالملفان المقدّمان لا يتضمنان تصريح حزمة. يستدعي BrowserSolve الدالة Solve.api، وليس Solve.main.

  3. 3.

    تفعيل ملف تعريف الموقع

    أعِدّ اسم المضيف المستهدف في لوحة تحكم solvethree، وتأكد من أن ملف تعريفه نشط لمساحة عملك. إرسال اسم مضيف وإجراء لا ينشئ ملف تعريف. راجع إعداد ملف تعريف الموقع.

  4. 4.

    إعداد البيئة

    اضبط متغيرات البيئة الثلاثة أدناه في العملية التي ستشغّل BrowserSolve. احتفظ بمفتاح API لمساحة العمل في عملية Java الموثوقة، لا في الصفحة أو JavaScript داخل المتصفح.

متغيرات البيئة لـ BrowserSolve
متغيرات البيئة لـ BrowserSolve
المتغيرالقيمة
SOLVETHREE_API_KEYمفتاح API لمساحة عملك.
SOLVETHREE_PROXYعنوان URL لوكيل HTTP يسمح بالوصول بناءً على قائمة عناوين IP مسموح بها، دون اسم مستخدم أو كلمة مرور مضمّنين، ودون مسار أو استعلام أو جزء fragment.
TARGET_URLعنوان URL الكامل لصفحة التطبيق التي تحتوي على النموذج.

يتطلب هذا المثال وكيلًا يسمح بالوصول بناءً على قائمة عناوين IP مسموح بها، لأنه لا يدعم تمرير بيانات اعتماد الوكيل عبر عنوان URL للوكيل في Chrome. تُمرَّر قيمة الوكيل نفسها إلى API المهام وChrome.

إضافة Solve.java لطلبات API

ترسل Solve.api طلبات مصادَقًا عليها إلى https://api.solvethree.com/v1، وتتحقق من حالة HTTP قبل تحليل استجابة ناجحة، وتقيّد كل طلب بالوقت المتبقي من المهلة المحلية.

Java 21+ · Solve.java (اعتمادية Gson)
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.UUID;

public class Solve {
    static final HttpClient HTTP = HttpClient.newBuilder()
        .connectTimeout(Duration.ofSeconds(15)).build();
    static final String KEY = System.getenv("SOLVETHREE_API_KEY");

    static JsonObject api(String path, JsonObject body, long deadline) throws Exception {
        long remaining = deadline - System.nanoTime();
        if (remaining <= 0) throw new IllegalStateException("Keep the task ID and resume polling");
        var builder = HttpRequest.newBuilder(URI.create("https://api.solvethree.com/v1" + path))
            .timeout(Duration.ofNanos(Math.min(remaining, Duration.ofSeconds(30).toNanos())))
            .header("Authorization", "Bearer " + KEY)
            .header("Content-Type", "application/json");
        if (body == null) builder.GET();
        else builder.POST(HttpRequest.BodyPublishers.ofString(body.toString()));
        var response = HTTP.send(builder.build(), HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() < 200 || response.statusCode() >= 300)
            throw new IllegalStateException("API HTTP " + response.statusCode());
        return JsonParser.parseString(response.body()).getAsJsonObject();
    }
    public static void main(String[] args) throws Exception {
        String proxy = System.getenv("SOLVETHREE_PROXY");
        if (KEY == null || proxy == null) throw new IllegalArgumentException("Set the API key and proxy");
        var input = new JsonObject();
        input.addProperty("site", "accounts.example.com");
        input.addProperty("action", "login");
        input.addProperty("proxy", proxy);
        input.addProperty("requestId", UUID.randomUUID().toString());
        // Save input before creation if the task must be recovered after restart.
        long deadline = System.nanoTime() + Duration.ofSeconds(180).toNanos();
        var task = api("/tasks?waitMs=0", input, deadline);
        while (task.get("status").getAsString().equals("pending") || task.get("status").getAsString().equals("solving")) {
            String id = URLEncoder.encode(task.get("id").getAsString(), StandardCharsets.UTF_8);
            task = api("/tasks/" + id + "?waitMs=15000", null, deadline);
            if (task.get("status").getAsString().equals("pending") || task.get("status").getAsString().equals("solving"))
                Thread.sleep(1000);
        }
        if (!task.get("status").getAsString().equals("ready"))
            throw new IllegalStateException("Task failed: " + task.get("errorCode")
                + ": " + task.get("error"));
        var solution = task.getAsJsonObject("solution");
        // Submit solution.token with userAgent/secChUa and the original proxy.
        System.out.println("Ready task: " + task.get("id").getAsString());
    }
}

توضّح الطريقة المستقلة main إنشاء المهام والاستقصاء عنها. اسم المضيف accounts.example.com والإجراء login فيها قيمتان نائبتان. لتكامل Selenium، استخدم هذا الملف بوصفه أداة مساعدة وشغّل BrowserSolve بدلًا منه؛ فتشغيل طريقتَي main سيؤدي إلى إنشاء مهام منفصلة.

يحتاج طلب reCAPTCHA إلى اسم مضيف في site، وaction الخاص بالتطبيق، وواحد فقط من proxy أو proxyCandidates. لا تضف type أو url. يؤدي إغفال recaptchaType إلى اختيار enterprise-v3، وهو ما تستخدمه هذه الأمثلة.

بدء Chrome بهوية الحل الجاهز

يستخرج BrowserSolve قيمة site من TARGET_URL، وينشئ المهمة، ويستقصي عنها ما دامت حالتها pending أو solving. وحدها المهمة ذات الحالة ready توفّر الحل الذي يستخدمه المتصفح.

Java · تمرير حل جاهز إلى Chrome
// Save as BrowserSolve.java beside Solve.java from the Java REST quickstart.
// Add Selenium Java 4.52.0 and Gson 2.15.0 to your Maven dependencies; Java 21+.
import com.google.gson.JsonObject;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Map;
import java.util.UUID;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.WebDriverWait;

public class BrowserSolve {
    public static void main(String[] args) throws Exception {
        String target = System.getenv("TARGET_URL");
        String proxy = System.getenv("SOLVETHREE_PROXY");
        URI proxyUri = URI.create(proxy);
        if (proxyUri.getUserInfo() != null) throw new IllegalArgumentException("Use an IP-allowlisted proxy");
        var input = new JsonObject();
        input.addProperty("site", URI.create(target).getHost());
        input.addProperty("action", "login");
        input.addProperty("proxy", proxy);
        input.addProperty("requestId", UUID.randomUUID().toString());
        long deadline = System.nanoTime() + Duration.ofSeconds(180).toNanos();
        var task = Solve.api("/tasks?waitMs=0", input, deadline);
        while (task.get("status").getAsString().matches("pending|solving")) {
            String id = URLEncoder.encode(task.get("id").getAsString(), StandardCharsets.UTF_8);
            task = Solve.api("/tasks/" + id + "?waitMs=15000", null, deadline);
            if (task.get("status").getAsString().matches("pending|solving")) Thread.sleep(1000);
        }
        if (!task.get("status").getAsString().equals("ready")) throw new IllegalStateException("Task failed");
        var solution = task.getAsJsonObject("solution");
        var options = new ChromeOptions();
        options.addArguments("--proxy-server=" + proxy, "--user-agent=" + solution.get("userAgent").getAsString());
        var browser = new ChromeDriver(options);
        try {
            browser.executeCdpCommand("Network.enable", Map.of());
            browser.executeCdpCommand("Network.setExtraHTTPHeaders", Map.of("headers",
                Map.of("sec-ch-ua", solution.get("secChUa").getAsString())));
            browser.get(target);
            var wait = new WebDriverWait(browser, Duration.ofSeconds(20));
            var field = wait.until(driver -> driver.findElement(By.name("g-recaptcha-response")));
            browser.executeScript("arguments[0].value=arguments[1]; arguments[0].dispatchEvent(new Event('input',{bubbles:true}));",
                field, solution.get("token").getAsString());
            browser.findElement(By.cssSelector("button[type=submit]")).click();
            wait.until(driver -> driver.findElement(By.id("success"))); // Your application's success marker.
        } finally { browser.quit(); }
    }
}

الترتيب مهم: يضبط ChromeOptions الوكيل ووكيل المستخدم المُعاد، ثم يضبط ChromeDriver ترويسة sec-ch-ua المُعادة عبر CDP قبل browser.get(target). لا تنتقل أولًا بهوية غير مرتبطة ثم تغيّرها عند إرسال الرمز فقط.

يرسل هذا المثال عمدًا proxy واحدًا، ليتمكن Chrome من استخدام الوكيل نفسه دون الحاجة إلى الاختيار من بين عدة خيارات. حافظ على اتساق عنوان URL المستهدف مع اسم المضيف والإجراء المستخدمين للمهمة.

عدّل النموذج قبل تشغيل BrowserSolve

يستهدف مثال المتصفح نموذجًا تملكه، يحتوي على حقل استجابة CAPTCHA وزر إرسال وعنصر نجاح. غيّر هذه القيم الخاصة بالتطبيق قبل تشغيل BrowserSolve.main مع تبعيات Maven ضمن مسار الفئات الخاص به.

  • action: استبدل login بالإجراء الذي يستخدمه تطبيقك لهذا الإرسال.
  • g-recaptcha-response: تحقق من اسم الحقل الذي يرسله نموذجك فعليًا. يعيّن المثال solution.token ويطلق حدث input ينتشر إلى العناصر الأصلية.
  • button[type=submit]: استبدل هذا المحدِّد إذا لم يكن يحدد زر إرسال النموذج المقصود.
  • success: استبدل معرّف العنصر هذا بحالة لا تظهر إلا بعد نجاح الإرسال الحالي.

تعيين حقل وإطلاق حدث ليسا آلية استدعاء راجع عامة لـ CAPTCHA. إذا كان منطق النموذج في تطبيقك لا يستخدم ذلك الحقل أو الحدث، فعدّل خطوة الإرسال لتناسب آلية الربط الفعلية. افحص الطلب الصادر للتأكد من احتوائه على solution.token، لا معرّف المهمة أو رمز قديم.

يتحقق انتظار WebDriver النهائي من وجود عنصر نجاح في التطبيق. اختر هذه العلامة بعناية: فوجود عنصر قبل الإرسال لا يثبت شيئًا بشأن الطلب الجديد. تعني جاهزية المهمة أن solvethree أنشأ حلًا؛ أما قبول الوجهة المستهدفة فيُثبت من خلال استجابة التطبيق.

تعامل مع حالات الفشل دون إنشاء مهمة أخرى بلا داعٍ

تعيد واجهة API استجابة HTTP غير ناجحة.

تحقق من استجابة HTTP بمعزل عن حالة المهمة. يطرح Solve.api استثناءً قبل التعامل معها كحمولة نجاح.

جرّب هذا: شخّص خطأ الطلب قبل إعادة المحاولة. تحقق من حقول الطلب وملف تعريف الموقع النشط بدلًا من تكرار إرسال مدخلات غير صالحة دون تغيير.

تصل المهمة إلى الحالة failed.

اقرأ errorCode وerror من استجابة المهمة. لا يعرض الاستثناء العام في BrowserSolve هذين الحقلين، بينما يعرضهما مثال Solve.java المستقل.

جرّب هذا: توقف عن الاستعلام الدوري عن تلك المهمة، وأظهر هذين الحقلين في معالجة الأخطاء. تُردّ تكلفة محاولات الحل الفاشلة؛ فلا تفترض أن كل فشل سببه مشكلة في الرصيد.

تنتهي مهلة الإنشاء أو تنقضي المهلة المحلية.

تحقق مما إذا كان لديك بالفعل معرّف مهمة أو كنت قد حفظت جسم الطلب الأصلي وrequestId. انتهاء مهلة الاتصال لا يثبت فشل الإنشاء.

جرّب هذا: استأنف الاسترجاع باستخدام معرّف المهمة، أو استعد عملية الإنشاء باستخدام requestId نفسه ومدخلات مطابقة. احفظ جسم الطلب قبل الإنشاء إذا كان يجب أن تظل الاستعادة ممكنة بعد إعادة التشغيل؛ فإعادة تشغيل الدالة main المقدَّمة تولّد requestId جديدًا.

المهمة جاهزة، لكن التطبيق يرفض الإرسال.

افحص الرمز الصادر والإجراء وعنوان URL المستهدف والوكيل ووكيل المستخدم وsec-ch-ua. تأكد من أن التطبيق أرسل بالفعل قيمة الحقل المعيّنة حديثًا.

جرّب هذا: صحّح القيمة غير المتطابقة أو آلية ربط الإرسال قبل الدفع مقابل حل آخر. احتفظ بالوكيل الناجح والهوية المُعادة معًا.

مهلة واجهة API البالغة 180 ثانية وانتظار التطبيق البالغ 20 ثانية حدّان محليان اختارتهما الأمثلة، وليسا ضمانات من الخدمة. لمزيد من التفاصيل عن أخطاء واجهة API، راجع أخطاء واجهة CAPTCHA API.

شغّل مهمتك الأولى.

ابدأ برصيد قدره $5 في مساحة العمل.

إنشاء مساحة عمل
استخدام CAPTCHA API مع Selenium بلغة Java · solvethree