שימוש ב-API של CAPTCHA עם Selenium ב-C#

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

פנו אל solvethree באמצעות HttpClient, המתינו למשימה מוכנה, ואז הכניסו את solution.token לשדה ה-CAPTCHA של היישום שלכם. Chrome חייב להשתמש באותו פרוקסי ובזהות הדפדפן שהוחזרה לפני פתיחת דף היעד.

הדוגמה הבאה משתמשת ב-.NET 8 וב-Selenium Chrome לשליחת טופס שבבעלותכם. זהו שילוב ישיר באמצעות REST, ולא SDK רשמי ל-C# או תוסף ל-Selenium.

בדף הזה

הכנת הפרויקט והיעד

  • השתמשו בפרויקט מסוף של .NET 8 עם הוראות ה-using המשתמעות המופעלות כברירת מחדל. הוסיפו את חבילות NuGet‏ Selenium.WebDriver ו-Selenium.Support, שתיהן בגרסה 4.52.0.
  • התקינו את Chrome מקומית. Selenium Manager מספק דרייבר תואם ל-Chrome.
  • ודאו שלשם המארח של היעד יש פרופיל אתר פעיל שזמין לסביבת העבודה שלכם. שליחת שם מארח ופעולה אינה יוצרת פרופיל; תחילה פעלו לפי הוראות הגדרת פרופיל האתר.
  • השתמשו בפרוקסי עם רשימת כתובות IP מורשות, ללא פרטי הזדהות בכתובת שלו. הדוגמה מעבירה את אותו פרוקסי ל-solvethree ול-Chrome.
משתני סביבה המשמשים בדוגמה
משתני סביבה המשמשים בדוגמה
משתנהמה לספק
SOLVETHREE_API_KEYמפתח ה-API של סביבת העבודה שלכם. שמרו אותו במחשב המהימן שמריץ את האוטומציה, ולא בדף.
SOLVETHREE_PROXYכתובת הפרוקסי עם רשימת כתובות ה-IP המורשות, המשמש הן לפתרון והן לשליחה מהדפדפן.
TARGET_URLהכתובת המלאה של דף הטופס ביישום שלכם. הדוגמה מחלצת ממנה את שם המארח עבור site של המשימה.

שנו את action בדוגמה מ-login אם השילוב שלכם משתמש בפעולה אחרת. הדוגמה מסתמכת על ערך ברירת המחדל של recaptchaType, שהוא enterprise-v3; בקשת המשימה אינה זקוקה לשדה type או url.

יצירת המשימה ושליחת הטופס

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

C# · יצירת משימה ושליחת הטופס שבבעלותכם
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json.Nodes;

var key = Environment.GetEnvironmentVariable("SOLVETHREE_API_KEY")
    ?? throw new Exception("Set SOLVETHREE_API_KEY");
var proxy = Environment.GetEnvironmentVariable("SOLVETHREE_PROXY")
    ?? throw new Exception("Set SOLVETHREE_PROXY");
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", key);
using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(180));

async Task<JsonObject> Api(HttpMethod method, string path, JsonObject? body = null)
{
    using var request = new HttpRequestMessage(method, "https://api.solvethree.com/v1" + path);
    if (body is not null)
        request.Content = new StringContent(body.ToJsonString(), Encoding.UTF8, "application/json");
    using var response = await http.SendAsync(request, deadline.Token);
    response.EnsureSuccessStatusCode();
    return JsonNode.Parse(await response.Content.ReadAsStringAsync(deadline.Token))!.AsObject();
}

var requestId = Guid.NewGuid().ToString(); // Save with these inputs for recovery.
var input = new JsonObject {
    ["site"] = new Uri(Environment.GetEnvironmentVariable("TARGET_URL") ?? throw new Exception("Set TARGET_URL")).Host, ["action"] = "login",
    ["proxy"] = proxy, ["requestId"] = requestId
};
var task = await Api(HttpMethod.Post, "/tasks?waitMs=0", input);
while (task["status"]!.GetValue<string>() is "pending" or "solving")
{
    var id = Uri.EscapeDataString(task["id"]!.GetValue<string>());
    task = await Api(HttpMethod.Get, "/tasks/" + id + "?waitMs=15000");
    if (task["status"]!.GetValue<string>() is "pending" or "solving")
        await Task.Delay(1000, deadline.Token);
}
if (task["status"]!.GetValue<string>() != "ready")
    throw new Exception("Task failed: " + task["errorCode"]?.GetValue<string>()
        + ": " + task["error"]?.GetValue<string>());
var solution = task["solution"]!.AsObject();
// Use solution["token"], ["userAgent"] and ["secChUa"] with the original proxy.
Console.WriteLine("Ready task: " + task["id"]!.GetValue<string>());
var target = Environment.GetEnvironmentVariable("TARGET_URL") ?? throw new Exception("Set TARGET_URL");
if (!string.IsNullOrEmpty(new Uri(proxy).UserInfo)) throw new Exception("Use an IP-allowlisted proxy");
var options = new ChromeOptions();
options.AddArgument("--proxy-server=" + proxy);
options.AddArgument("--user-agent=" + solution["userAgent"]!.GetValue<string>());
using var browser = new ChromeDriver(options);
browser.ExecuteCdpCommand("Network.enable", new Dictionary<string, object>());
browser.ExecuteCdpCommand("Network.setExtraHTTPHeaders", new Dictionary<string, object> {
    ["headers"] = new Dictionary<string, object> { ["sec-ch-ua"] = solution["secChUa"]!.GetValue<string>() }
});
browser.Navigate().GoToUrl(target);
var wait = new WebDriverWait(browser, TimeSpan.FromSeconds(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["token"]!.GetValue<string>());
browser.FindElement(By.CssSelector("button[type=submit]")).Click();
wait.Until(driver => driver.FindElement(By.Id("success"))); // Your application's success marker.

זמן ה-HTTP הקצוב של 30 שניות, מגבלת ה-API של 180 שניות והמתנת Selenium של 20 שניות הם הגדרות בדוגמה הזו, ולא התחייבויות של השירות לזמני ביצוע. התאימו אותם למדיניות הזמנים הקצובים של היישום שלכם.

התאמת העברת הטוקן ליישום שלכם

  1. 1.

    שימוש בפרוקסי ובזהות שהוחזרה יחד

    ChromeOptions מחיל את הפרוקסי המקורי ואת solution.userAgent. פקודות ה-CDP מפעילות תקשורת רשת ומגדירות את sec-ch-ua מתוך solution.secChUa לפני הניווט. הקפידו על הסדר הזה; אל תפתחו את הדף תחילה ותשנו את הזהות לאחר מכן.

  2. 2.

    כתיבת הטוקן במקום שבו היישום קורא אותו

    הדוגמה מאתרת שדה בשם g-recaptcha-response, מקצה לו את solution.token ומשגרת אירוע input. החליפו את מנגנון ההעברה הזה אם היישום שלכם קורא את הטוקן ממקום אחר. מילוי שדה עוזר רק אם הבקשה היוצאת מהיישום אכן כוללת את הערך שלו.

  3. 3.

    החלפת בוררי השליחה וההצלחה

    הדוגמה לוחצת על button[type=submit] וממתינה לרכיב עם המזהה success. השתמשו בבוררים מהיישום האמיתי שלכם, ובחרו סמן הצלחה שמאשר שהפעולה שנשלחה הצליחה — ולא רק שנלחץ הכפתור.

משימה מוכנה פירושה שפתרון זמין. הבדיקה שלאחר השליחה נפרדת: היעד עדיין מחליט אם לקבל את בקשת היישום.

טיפול בכשלים בלי ליצור משימות מיותרות

פונקציית העזר של ה-API זורקת חריגה בעקבות תגובת HTTP.

בדקו את סטטוס ה-HTTP ואת תגובת השגיאה. מצב זה שונה ממשימה שמגיעה למצב failed.

כדאי לנסות: תקנו את בעיית הבקשה או האימות שדווחה לפני ניסיון חוזר. אל תמשיכו לשלוח שוב קלט לא תקין ללא שינוי.

המשימה מגיעה לסטטוס failed.

קראו את errorCode ואת error;‏ site-unreachable היא אחת משגיאות המשימה האפשריות.

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

המשימה מוכנה, אך היעד דוחה את השליחה.

בדקו תחילה את הבקשה היוצאת מהיישום: האם היא מכילה את solution.token, ולא את מזהה המשימה או טוקן ישן? לאחר מכן בדקו את הפרוקסי, סוכן המשתמש, רמזי הלקוח וכתובת היעד.

כדאי לנסות: תקנו את העברת הטוקן או את אי-ההתאמה בזהות לפני שתשלמו על פתרון נוסף.

תם הזמן הקצוב של Selenium לאחר לחיצה על שליחה.

בדקו את הדף ואת תגובת היישום. הבורר success הוא רכיב לדוגמה, ולא אות קבלה אוניברסלי.

כדאי לנסות: החליפו אותו בתנאי ההצלחה האמיתי של היישום שלכם, או טפלו בדחייה שהיישום החזיר.

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

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

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

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