أي بارس يعمل لأكثر من خمس دقائق على مواقع حقيقية، سيواجه عاجلاً أم آجلاً حظرًا بناءً على IP.
يوفر Rust مع مكتبة reqwest
أداءً ممتازًا، لكنه لا يحل مشكلة تدوير البروكسي بشكل افتراضي - يجب تنفيذ ذلك بنفسك.
في هذه المقالة، سنناقش كيفية توصيل بروكسي SOCKS5 وHTTP(S)، وتجميع مجموعة عملاء وتنظيم تدوير IP بحيث
لا يتعطل البارس بعد أول ردود 429 و403.
لماذا يحتاج البارس إلى بروكسي وما هي مخاطر عدم وجوده
المواقع مثل الأسواق، ومجمعات الأسعار، ووسائل التواصل الاجتماعي تتعقب النشاط غير الطبيعي من IP واحد: عشرات الطلبات في الثانية، User-Agent متشابه، عدم وجود ملفات تعريف الارتباط بين الطلبات. بمجرد أن يتجاوز عداد الأنشطة المشبوهة العتبة، يتم إدراج IP في قائمة الحظر - مؤقتًا أو دائمًا. بالنسبة للبارس، يعني هذا توقفًا كاملًا لجمع البيانات حتى يتم تغيير IP يدويًا.
تحل البروكسي هذه المشكلة عن طريق توزيع الطلبات بين عشرات أو مئات عناوين IP. ومن المهم ليس فقط "تبديل البروكسي"، ولكن تنظيم مجموعة الاتصالات والتدوير بشكل صحيح، وإلا ستواجه إما حدودًا في reqwest نفسها (عميل واحد = مجموعة واحدة من الاتصالات)، أو ستبدأ في إرسال الطلبات من نفس البروكسي بشكل متكرر جدًا. سنناقش كلا المشكلتين بالتفصيل: أولاً الاتصال الأساسي، ثم هيكل المجموعة.
بالنسبة للبارس في الإنتاج، ليس فقط تدوير IP هو المهم، ولكن أيضًا جودة العناوين نفسها. إذا كنت تقوم ببارس Wildberries، Ozon أو Avito، يتم حظر عناوين IP الخاصة بمراكز البيانات بشكل أسرع من عناوين IP الخاصة بالمستخدمين الحقيقيين - سنتحدث عن ذلك بمزيد من التفصيل في قسم اختيار نوع البروكسي.
الإعداد الأساسي لبروكسي HTTP/HTTPS في reqwest
يدعم reqwest البروكسي من خلال هيكل Proxy,
الذي يجب تمريره إلى ClientBuilder.
أضف التبعيات في Cargo.toml:
[dependencies]
reqwest = { version = "0.12", features = ["socks", "json"] }
tokio = { version = "1", features = ["full"] }
مثال بسيط مع بروكسي HTTP يتطلب مصادقة باستخدام اسم المستخدم وكلمة المرور:
use reqwest::{Client, Proxy};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let proxy = Proxy::all("http://gate.proxycove.com:8000")?
.basic_auth("user_login", "user_password");
let client = Client::builder()
.proxy(proxy)
.timeout(std::time::Duration::from_secs(15))
.build()?;
let resp = client
.get("https://httpbin.org/ip")
.send()
.await?
.text()
.await?;
println!("الرد عبر البروكسي: {resp}");
Ok(())
}
توجه Proxy::all
كل من حركة مرور HTTP وHTTPS عبر البروكسي. إذا كنت بحاجة إلى فصل البروتوكولات، استخدم
Proxy::http() و
Proxy::https()
بشكل منفصل - وهذا مفيد إذا كان موفر البروكسي يستمع إلى منافذ مختلفة للبروتوكولات المختلفة.
توصيل بروكسي SOCKS5
يعمل SOCKS5 على مستوى أدنى من بروكسي HTTP ويدعم اتصالات TCP دون تحليل محتوى الطلب - وهذا أسرع وأكثر استقرارًا للبارس،
خاصة إذا كنت تتصفح ليس فقط عبر HTTP(S)، ولكن أيضًا تتصل بنقاط نهاية WebSocket. لتوصيل SOCKS5، تأكد من تمكين الميزة socks
في Cargo.toml (كما هو مذكور أعلاه). مثال على الاتصال:
use reqwest::{Client, Proxy};
async fn build_socks5_client(
host: &str,
login: &str,
password: &str,
) -> reqwest::Result<Client> {
let proxy_url = format!("socks5h://{host}");
let proxy = Proxy::all(&proxy_url)?.basic_auth(login, password);
Client::builder()
.proxy(proxy)
.timeout(std::time::Duration::from_secs(20))
.build()
}
لاحظ استخدام المخطط socks5h
بدلاً من socks5.
الحرف "h" في النهاية يعني أن حل DNS للاسم يحدث على جانب خادم البروكسي، وليس محليًا.
هذا أمر حاسم للخصوصية: بدون ذلك، يمكن لمحلل DNS الحقيقي الخاص بك "تسريب" الأسماء المطلوبة بعيدًا عن البروكسي،
حتى لو كانت حركة المرور نفسها تمر عبر نفق SOCKS5.
إذا كان موفر البروكسي لا يتطلب مصادقة باستخدام اسم المستخدم/كلمة المرور (قائمة بيضاء IP)، فقط لا تستدعي
basic_auth -
حدد في لوحة التحكم الخاصة بالموفر عنوان IP الخاص بالخادم الخاص بك، الذي يتم إرسال الطلبات منه.
مجموعة عملاء reqwest لبروكسيات مختلفة
خطأ شائع هو إنشاء Client
جديد لكل طلب. هذا مكلف: يحتفظ reqwest داخل مجموعة من اتصالات TCP وجلسات TLS، وإعادة إنشاء العميل
تقتل جميع مزايا keep-alive. النهج الصحيح هو إنشاء عميل واحد لكل بروكسي مسبقًا
وإعادة استخدامهم من مجموعة مشتركة.
use reqwest::{Client, Proxy};
use std::sync::Arc;
#[derive(Clone)]
struct ProxyEndpoint {
url: String,
login: String,
password: String,
}
struct ClientPool {
clients: Vec<Client>,
}
impl ClientPool {
fn build(endpoints: &[ProxyEndpoint]) -> reqwest::Result<Self> {
let mut clients = Vec::with_capacity(endpoints.len());
for ep in endpoints {
let proxy = Proxy::all(&ep.url)?
.basic_auth(&ep.login, &ep.password);
let client = Client::builder()
.proxy(proxy)
.pool_max_idle_per_host(4)
.timeout(std::time::Duration::from_secs(15))
.build()?;
clients.push(client);
}
Ok(Self { clients })
}
}
يتم إنشاء هذه المجموعة مرة واحدة عند بدء تشغيل البارس. عادةً ما يكون عدد العملاء مساويًا لعدد عناوين البروكسي الفريدة في مجموعتك - إذا كان الموفر يوفر الوصول إلى التدوير على مستوى البوابة (gateway)، يكفي عميل واحد أو اثنان، حيث يحدث تدوير IP تلقائيًا على جانب الموفر مع كل اتصال TCP جديد.
بالنسبة للمهام ذات التوازي العالي، قم بتغليف المجموعة في Arc
وشاركها بين مهام tokio - عملاء reqwest آمنون من حيث التزامن ومصممون للاستخدام المتزامن.
استراتيجيات تدوير IP
هناك نهجان مختلفان تمامًا لتدوير IP. الأول - تدوير على جانب الموفر: تتصل بعنوان بوابة واحد، ويقوم الموفر بتغيير IP لكل طلب أو مرة كل N دقيقة. الثاني - تدوير على جانبك: تحتفظ بقائمة من البروكسيات الثابتة وتختار بنفسك أيها تستخدمه للطلب التالي.
إذا كنت تستخدم بروكسيات سكنية مع تدوير تلقائي عبر البوابة، يصبح الكود بسيطًا للغاية - فقط أرسل الطلبات عبر نفس العميل، ويقوم الموفر بتغيير IP الصادر بنفسه. ولكن إذا كنت بحاجة إلى التحكم (على سبيل المثال، لتثبيت IP لجلسة لمدة 10 دقائق لمحاكاة سلوك مستخدم حقيقي)، نفذ التدوير يدويًا:
use rand::seq::SliceRandom;
use std::sync::atomic::{AtomicUsize, Ordering};
struct RotatingPool {
clients: Vec<Client>,
counter: AtomicUsize,
}
impl RotatingPool {
// Round-robin: نقوم بالتناوب بين العملاء
fn next_round_robin(&self) -> &Client {
let idx = self.counter.fetch_add(1, Ordering::SeqCst) % self.clients.len();
&self.clients[idx]
}
// اختيار عشوائي - مفيد ضد تحليل الأنماط
fn next_random(&self) -> &Client {
self.clients.choose(&mut rand::thread_rng()).unwrap()
}
}
Round-robin مناسب عندما تكون الحمل موزعة بالتساوي على جميع IP. الاختيار العشوائي يخفي نمط الطلبات بشكل أفضل، إذا كان الموقع المستهدف يحلل الفترات الزمنية بين الطلبات من عنوان واحد. بالنسبة لمهام مثل مراقبة الأسعار على Ozon أو جمع الإعلانات من Avito، يكون الجمع بينهما أكثر فعالية في الممارسة العملية: اختيار عشوائي للبروكسي بالإضافة إلى تأخير عشوائي من 1 إلى 3 ثوانٍ بين الطلبات من نفس العميل.
من المهم أيضًا التحدث عن الجلسات الثابتة. إذا كان البارس يمر بسيناريو متعدد الخطوات (تسجيل الدخول → الانتقال بين الصفحات → إجراء)، فلا يمكن تغيير IP في منتصف الجلسة - سيلاحظ الموقع تغيير IP أثناء جلسة ملفات تعريف الارتباط النشطة إما عن طريق تسجيل الخروج أو حظر الحساب. بالنسبة لمثل هذه السيناريوهات، قم بتثبيت عميل واحد (وبالتالي IP واحد) لجلسة البارس المحددة طوال فترة حياتها.
معالجة الأخطاء ومنطق إعادة المحاولة
تتعطل البروكسي من حين لآخر: مهلات، 407 (مطلوب مصادقة البروكسي)، انقطاع اتصال TCP. يجب أن يكون البارس قادرًا على التمييز بين "البروكسي معطل - استبعاده من المجموعة" و"الموقع أعاد 429 - فقط انتظر وقم بتغيير IP". إليك نمط عمل مع إعادة المحاولة واستبعاد البروكسيات الميتة:
use reqwest::StatusCode;
use std::time::Duration;
async fn fetch_with_retry(
pool: &RotatingPool,
url: &str,
max_attempts: u32,
) -> Result<String, String> {
let mut last_error = String::new();
for attempt in 0..max_attempts {
let client = pool.next_random();
match client.get(url).send().await {
Ok(resp) if resp.status() == StatusCode::TOO_MANY_REQUESTS => {
last_error = "429: حد المعدل، نقوم بتغيير IP".into();
tokio::time::sleep(Duration::from_millis(500 * (attempt as u64 + 1))).await;
continue;
}
Ok(resp) if resp.status().is_success() => {
return resp.text().await.map_err(|e| e.to_string());
}
Ok(resp) => {
last_error = format!("حالة غير متوقعة: {}", resp.status());
}
Err(e) if e.is_timeout() || e.is_connect() => {
last_error = format!("مشكلة مع البروكسي: {e}");
continue;
}
Err(e) => {
last_error = e.to_string();
}
}
}
Err(format!("تم استنفاد جميع المحاولات: {last_error}"))
}
نقطة مهمة: التأخير الأسي (500 * attempt)
يقلل الحمل على الموقع المستهدف عند وجود ردود 429 بشكل جماعي ويقلل في نفس الوقت من خطر الحظر الكامل
لجميع البروكسيات مرة واحدة. بالنسبة للبارس في الإنتاج، أضف عداد أخطاء لكل بروكسي في المجموعة واستبعد تلقائيًا
العنوان إذا أعطى 5+ أخطاء متتالية - سيحميك ذلك من المحاولات غير المجدية عبر IP محظور بالفعل.
ما هو نوع البروكسي الذي يجب اختياره للبارس
يؤثر اختيار نوع البروكسي بشكل مباشر على عدد الطلبات الناجحة التي ستحصل عليها قبل الحظر. تعتبر IP الخاصة بمراكز البيانات هي الأرخص والأسرع، لكنها تنتمي إلى نطاقات معروفة لمزودي الاستضافة - أنظمة مكافحة الروبوتات في الأسواق ووسائل التواصل الاجتماعي تحسبها تقريبًا على الفور من قواعد بيانات ASN. بينما تبدو IP السكنية والمحمولة كأنها مستخدمون عاديون، لذا تعيش لفترة أطول بكثير.
| نوع البروكسي | السرعة | خطر الحظر | متى تستخدم |
|---|---|---|---|
| مركز البيانات | عالية | عالية | APIs مفتوحة، مواقع بدون أنظمة مكافحة الروبوتات |
| سكنية | متوسطة | منخفضة | Wildberries، Ozon، Avito، وسائل التواصل الاجتماعي |
| محمولة | متوسطة | أدنى | Instagram، TikTok، أنظمة مكافحة الروبوتات الصارمة |
بالنسبة لبارس الأسواق بكميات كبيرة، فإن البروكسيات السكنية تمثل توازنًا معقولًا - فهي ليست باهظة الثمن بالمقارنة مع حركة المرور مثل المحمولة، لكنها تمر بفحوصات على مستوى "مستخدم حقيقي"، التي تقطع نطاقات مراكز البيانات. إذا كان البارس يعمل عبر API لوسائل التواصل الاجتماعي أو يحاكي تطبيقًا محمولًا، فإن البروكسيات المحمولة تعطي أقل نسبة من CAPTCHA والحظر المؤقت بفضل IP المرتبطة بمزودي الخدمة.
الأداء ونصائح حول الحمل
يسمح Rust بالحفاظ على آلاف الطلبات المتزامنة على مثيل واحد، لكن موفر البروكسي والموقع المستهدف لا يزالان يحدان من القدرة الحقيقية. بعض القواعد العملية للبارس في الإنتاج:
- حدد التوازي عبر
tokio::sync::Semaphore- لا تزيد عن 20-50 طلبًا متزامنًا على مجموعة بروكسي واحدة، وإلا ستبدأ بعض الاتصالات في الانقطاع بسبب المهلة. - استخدم
pool_idle_timeoutفي ClientBuilder، حتى لا يحتفظ reqwest باتصالات ميتة عبر بروكسيات غير مستقرة. - سجل حالات HTTP وأوقات الاستجابة لكل بروكسي على حدة - بهذه الطريقة ستتمكن بسرعة من رؤية أي العناوين بدأت "تتعفن" وتحتاج إلى الاستبدال.
- تدوير User-Agent مع تدوير IP يقلل بشكل كبير من احتمال الكشف: قم بتغييرها بشكل متزامن، وليس في دورات منفصلة.
- للبارس طويل الأمد، أضف فحص صحة: كل بضع دقائق، أرسل طلب اختبار إلى https://httpbin.org/ip عبر كل بروكسي واستبعد غير المستجيبين.
أعط اهتمامًا خاصًا لبصمات TLS (JA3/JA3S). يستخدم reqwest بشكل افتراضي rustls أو nativetls
اعتمادًا على ميزات البناء، ولكل منهما بصمات مختلفة لعملية المصافحة TLS. بعض أنظمة مكافحة الروبوتات (مثل
تلك الموجودة في الأسواق الكبيرة) تحلل بصمة TLS، وليس فقط IP - لذلك حتى البروكسي المثالي
لن ينقذك إذا كان العميل نفسه يظهر نفسه بعملية مصافحة TLS غير قياسية. إذا واجهت حظرًا عند استخدام
بروكسيات "نظيفة"، تحقق من ميزات reqwest وجرب ميزة native-tls بدلاً من rustls، أو العكس.
الخاتمة
يتم إعداد البروكسي في Rust reqwest بسرعة - سطرين مع Proxy::all
أو مخطط SOCKS5 socks5h://.
تبدأ العمل الرئيسية بعد ذلك: بناء مجموعة عملاء، اختيار استراتيجية التدوير (round-robin، عشوائية
أو جلسات ثابتة) ومعالجة الأخطاء بشكل صحيح مع استبعاد العناوين غير العاملة من المجموعة. بدون ذلك، حتى
أسرع بارس في Rust سيواجه حظراً بعد بضع دقائق من العمل.
إذا كان بارسك يعمل مع الأسواق أو وسائل التواصل الاجتماعي، حيث تحلل أنظمة مكافحة الروبوتات ليس فقط تكرار الطلبات، ولكن أيضًا أصل IP، ابدأ مع بروكسيات سكنية - فهي توفر أفضل توازن بين السعر ودوام الجلسات. لأهداف أكثر صرامة مثل Instagram و TikTok، يجب اختبار IP المحمولة، وللمهام البسيطة التي لا تحتوي على أنظمة مكافحة الروبوتات، يكفي استخدام بروكسيات مراكز البيانات السريعة والرخيصة.