حل reCAPTCHA Enterprise باستخدام Rust
استخدم واجهة REST API الخاصة بـ solvethree من Rust: أنشئ مهمة، واستعلم عنها دوريًا حتى تصبح جاهزة، ثم أرسل solution.token إلى الهدف باستخدام هوية المتصفح المُرجعة والوكيل نفسه. يستخدم المثال أدناه reqwest وTokio وserde_json مباشرةً، وليس Rust SDK رسميًا.
في هذه الصفحة
اضبط الموقع والطلب
أولًا، أنشئ ملفًا تعريفيًا نشطًا لاسم المضيف المستهدف في وحدة تحكم مساحة عملك. إرسال اسم مضيف وإجراء لا يُنشئ ملفًا تعريفيًا. راجع إعداد الملف التعريفي للموقع إذا لم يكن الهدف مُعدًّا بعد.
اضبط متغيرات البيئة التالية على الخادم الذي سيعمل عليه عميل Rust:
SOLVETHREE_API_KEY: مفتاح API لمساحة عملك. احتفظ به على جانب الخادم.SOLVETHREE_PROXY: عنوان URL للوكيل المستخدم للحل وللطلب اللاحق الموجّه إلى الهدف.REQUEST_ID: معرّف اختياري وثابت لهذه المهمة المنطقية. احفظه قبل إرسال الطلب لتتمكن من الاسترداد بعد انقطاع الاتصال.
| الحقل | ما يجب إرساله |
|---|---|
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.
[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"] }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.
إذا كان لديك معرّف مهمة، فاسترجع تلك المهمة
استأنف باستخدام GET /v1/tasks/:id بدلًا من إنشاء مهمة أخرى. تنشئ نقطة الدخول العادية في المثال مهمة دائمًا؛ لذا ينبغي أن يستخدم مسار إعادة التشغيل المعرّف المحفوظ.
- 2.
إذا لم يكن لديك سوى معرّف الطلب، فاستعد عملية الإنشاء
أرسل طلب المهمة نفسه باستخدام requestId نفسه. سيُرجع المهمة الموجودة. يؤدي تغيير المدخلات مع إعادة استخدام المعرّف إلى تعارض.
- 3.
تعامل مع الحالة المستردة
واصل الاستعلام الدوري عن المهام في حالتي pending أو solving. استخدم الحل في حالة ready، أو توقف عند failed وافحص errorCode وerror. انتهاء المهلة المحلية وحده لا يثبت أن المهمة البعيدة فشلت.
تحتاج إخفاقات HTTP وإخفاقات المهام إلى معالجة مختلفة. يتحقّق العميل من استجابات HTTP غير الناجحة قبل تحليل حمولة النجاح. أما الفشل النهائي للمهمة فيصل على هيئة حالة المهمة failed؛ وتُردّ تكلفة عمليات الحل الفاشلة. لا تتضمن رسالة الفشل في المثال إلا errorCode، لذا افحص أيضًا error في استجابة المهمة عند التشخيص. راجع معالجة أخطاء API قبل إعادة المحاولة بمدخلات لم تتغير.
أرسل الرمز مع هوية المتصفح الخاصة به
يتوقف المثال عند جاهزية المهمة؛ ولا يُرسل طلب التطبيق إلى الهدف. يحتاج ذلك الطلب إلى solution.token، لا إلى معرّف المهمة. استخدم الهوية المُرجعة في userAgent وsecChUa عبر الوكيل الأصلي، وأرسل إلى الهدف المقصود بدلًا من خلط قيم من جلسة متصفح أخرى.
تعني المهمة الجاهزة أنه تم توليد حل، لا أن الهدف قبل طلب تطبيقك. أضف الإرسال الخاص بالهدف عند التعليق المحدد في مسار النجاح؛ وراجع دليل تكامل reCAPTCHA Enterprise للحصول على إرشادات تمرير الرمز.
المصادر ومرجع API
شغّل مهمتك الأولى.
ابدأ برصيد قدره $5 في مساحة العمل.