عندما يرسل المُحلل أو الروبوت مئات الطلبات في الدقيقة من عنوان IP واحد، يقوم الخادم بسرعة بتفعيل حد المعدل، وبعد ذلك - تظهر كابتشا أو حظر. الحل هو قائمة المهام التي توزع الطلبات بين العمال والبروكسي. في هذه المقالة، سنستعرض كيف يتم تنظيم هذه البنية في Celery و RQ و BullMQ، وكيفية إدارة حد الطلبات لكل عنوان IP بشكل صحيح.
لماذا قائمة المهام عند العمل مع البروكسي
إذا كانت المهمة هي استخراج 50,000 بطاقة منتج من Wildberries أو التحقق من حالة 10,000 حساب على Instagram، فإن تشغيل الطلبات بشكل متسلسل سيستغرق ساعات، بينما التشغيل المتوازي بدون رقابة سيؤدي بسرعة إلى الحظر على IP. تحل قائمة المهام (task queue) كلا المشكلتين: فهي تقسم حجم العمل الكبير إلى مهام صغيرة، وتوزعها بين عدة عمال، وتسمح بربط بروكسي منفصل بكل عامل.
عادةً ما تبدو البنية كما يلي: يحتفظ وسيط الرسائل (Redis أو RabbitMQ) بقائمة المهام، ويأخذ العمال المهام من القائمة وينفذونها، حيث يستخدم كل عامل بروكسي خاص به أو يقوم بتدوير البروكسي من المجموعة. وهذا يسمح بالحفاظ على الحمل على كل IP ضمن الحدود الآمنة، حتى لو كان إجمالي حجم الطلبات هائلًا.
لا يستخدم هذا النهج فقط مطورو المحللات، بل أيضًا الفرق التي تقوم بأتمتة التحقق من حسابات إعلانات Facebook، ومراقبة الأسعار على Ozon، أو جمع البيانات من عشرات المواقع في نفس الوقت. بدون قائمة وموزع جيد حسب IP، فإن أي نظام من هذا القبيل محكوم عليه بالتحجيم في اليوم الأول من العمل.
مشكلة حد الطلبات على عنوان IP واحد
تمتلك معظم المواقع وواجهات برمجة التطبيقات حماية من الحمل الزائد. يمكن أن تتجلى هذه الحماية بطرق مختلفة: يعيد الخادم HTTP 429 (طلبات كثيرة جدًا)، يظهر كابتشا، يحظر IP مؤقتًا لمدة 10-60 دقيقة، أو يضيفه إلى القائمة السوداء بشكل دائم. تختلف الحدود بشكل كبير بين المنصات - على سبيل المثال، يسمح واجهة برمجة التطبيقات الخاصة بـ Wildberries بعدد محدود من الطلبات في الثانية من عنوان واحد، بينما يستجيب Instagram للنشاط المشبوه بعد 15-20 إجراءً متتاليًا.
الخطأ الرئيسي عند بناء قائمة المهام هو تشغيل جميع العمال من خلال نفس IP. حتى إذا كانت المهام موزعة بين 20 عاملاً، ولكن جميعهم يستخدمون نفس البروكسي أو لا يستخدمونه على الإطلاق، فإن الحد على هذا IP سيحدث بنفس السرعة كما هو الحال عند التنفيذ المتسلسل. لذلك يجب أن يتناسب عدد العمال مع عدد عناوين IP المتاحة في مجموعة البروكسي.
بالنسبة للمهام التي تتطلب تكرارًا عاليًا للطلبات على نطاق واحد، من الأفضل استخدام بروكسي مراكز البيانات - فهي توفر زمن انتقال منخفض وتناسب عمليات الاستخراج المتوازية الكبيرة، حيث تكون السرعة هي الأهم وليس التمويه كمستخدم عادي. إذا كانت الهدف هو تجاوز حماية أكثر صرامة مثل أنظمة مكافحة الروبوتات في الأسواق أو الشبكات الاجتماعية، فإن الحاجة تكون إلى IP تشبه المستخدمين العاديين، أي بروكسي سكنية أو موبايلية.
Celery: البنية وتدوير البروكسي في الكود
Celery هي قائمة المهام الأكثر نضجًا وشعبية في نظام Python البيئي. تعمل فوق وسيط (غالبًا Redis أو RabbitMQ) وتدعم إعدادات مرنة للتوازي، وإعادة المحاولة، وأولويات المهام. لتدوير البروكسي، من السهل تخزين مجموعة IP في Redis وتعيين بروكسي لكل مهمة عند وضعها في القائمة.
from celery import Celery
import requests
import redis
import random
app = Celery('tasks', broker='redis://localhost:6379/0')
redis_client = redis.Redis(host='localhost', port=6379, db=1)
PROXY_POOL = [
"http://user:[email protected]:8000",
"http://user:[email protected]:8000",
"http://user:[email protected]:8000",
]
def get_proxy():
# Round-robin عبر Redis، لتجنب الضغط على IP واحد
idx = redis_client.incr("proxy_cursor") % len(PROXY_POOL)
return PROXY_POOL[idx]
@app.task(bind=True, max_retries=3, default_retry_delay=30)
def fetch_url(self, url):
proxy = get_proxy()
try:
response = requests.get(
url,
proxies={"http": proxy, "https": proxy},
timeout=15
)
if response.status_code == 429:
raise self.retry(exc=Exception("تم تحديد الحد"))
return response.status_code
except requests.exceptions.RequestException as exc:
raise self.retry(exc=exc)
النقطة الرئيسية هي وظيفة get_proxy()، التي من خلال عداد Redis تعطي البروكسي التالي من المجموعة بشكل دائري. وهذا يضمن توزيع الحمل بالتساوي بين جميع IP، وليس التركيز على واحد. يسمح المعامل max_retries ومعالجة الكود 429 للمهمة بإعادة الإرسال تلقائيًا مع بروكسي آخر، إذا حصل IP الحالي على حد.
للتحكم في التوازي على مستوى العمال، يسمح Celery بتحديد عدد المهام المتزامنة من خلال المعامل --concurrency عند تشغيل العامل، وكذلك من خلال قوائم ذات أولويات مختلفة (على سبيل المثال، قائمة منفصلة للمهام البطيئة ذات حد صارم).
RQ (Redis Queue): بديل بسيط
RQ هي مكتبة أخف من Celery، بدون وسيط RabbitMQ وتكوين بسيط. إنها مناسبة إذا كان المشروع صغيرًا ولا يتطلب توجيهًا معقدًا للمهام، بل تحتاج فقط إلى قائمة مع العمال وإمكانية ربط البروكسي بالمهمة.
from redis import Redis
from rq import Queue
import requests
import itertools
redis_conn = Redis()
q = Queue(connection=redis_conn)
PROXY_POOL = [
"http://user:[email protected]:8000",
"http://user:[email protected]:8000",
]
proxy_cycle = itertools.cycle(PROXY_POOL)
def fetch_with_proxy(url):
proxy = next(proxy_cycle)
response = requests.get(
url,
proxies={"http": proxy, "https": proxy},
timeout=15
)
if response.status_code == 429:
# إعادة وضع في القائمة مع تأخير
q.enqueue_in(60, fetch_with_proxy, url)
return None
return response.status_code
# وضع المهام في القائمة
urls = ["https://example.com/page1", "https://example.com/page2"]
for url in urls:
q.enqueue(fetch_with_proxy, url)
في RQ، يعمل التدوير عبر itertools.cycle داخل عملية العامل - وهذا أسهل من عداد Redis، ولكنه أسوأ في التوسع مع العديد من العمال المتوازيين، لأن كل عامل يبدأ الدورة من البداية. لمشاريع صغيرة مع 1-5 عمال، هذا كافٍ، ولكن للمشاريع الأكبر، من الأفضل العودة إلى عداد مركزي في Redis، كما في المثال مع Celery.
RQ مناسبة جيدًا للمطورين الذين يستخدمون بالفعل Redis في المشروع ولا يرغبون في إعداد وسيط رسائل منفصل. العيب هو وجود آليات أقل مدمجة للتصنيف والمراقبة مقارنة بـ Celery، لذلك غالبًا ما يتم اختيار Celery للأنظمة الإنتاجية التي تحتوي على مئات الآلاف من المهام يوميًا.
BullMQ: قائمة على Node.js مع حد على IP
BullMQ هو المعيار لقوائم المهام في نظام Node.js البيئي، مبني على Redis ويحتوي على محدد معدل مدمج، مما يجعله مناسبًا بشكل خاص للتحكم في حد الطلبات على IP. بدلاً من حساب الطلبات يدويًا، يمكنك تحديد المحدد مباشرة على مستوى القائمة.
const { Queue, Worker } = require('bullmq');
const axios = require('axios');
const connection = { host: 'localhost', port: 6379 };
const proxyPool = [
'http://user:[email protected]:8000',
'http://user:[email protected]:8000',
'http://user:[email protected]:8000',
];
const queue = new Queue('fetchQueue', { connection });
// المحدد: لا أكثر من 5 مهام في الثانية على القائمة
const worker = new Worker('fetchQueue', async job => {
const proxy = proxyPool[job.id % proxyPool.length];
try {
const response = await axios.get(job.data.url, {
proxy: false,
httpsAgent: new (require('https-proxy-agent'))(proxy),
timeout: 15000,
});
return response.status;
} catch (err) {
if (err.response && err.response.status === 429) {
throw new Error('تم تحديد الحد، أعيد المحاولة مع بروكسي آخر');
}
throw err;
}
}, {
connection,
limiter: { max: 5, duration: 1000 },
concurrency: proxyPool.length,
});
for (let i = 0; i < 100; i++) {
queue.add('fetch', { url: `https://example.com/item/${i}` }, {
attempts: 3,
backoff: { type: 'exponential', delay: 5000 },
});
}
المعامل limiter في BullMQ هو آلية مدمجة تحد من العدد الإجمالي للمهام المعالجة خلال فترة زمنية، مما يسهل الالتزام بحد الطلبات على مستوى النظام بالكامل. والمعامل concurrency، الذي يساوي حجم مجموعة البروكسي، يضمن أن عدد العمال الذين يعملون في نفس الوقت هو بالضبط عدد IP المتاحة.
يستخدم هذا النهج غالبًا من قبل الفرق التي تكتب روبوتات خاصة بها لأتمتة الإجراءات في TikTok أو Telegram - BullMQ يتكامل جيدًا مع بقية مجموعة Node.js ولا يتطلب الانتقال إلى Python من أجل مهمة واحدة.
مقارنة بين Celery و RQ و BullMQ
| المعلمة | Celery | RQ | BullMQ |
|---|---|---|---|
| اللغة | Python | Python | Node.js |
| الوسيط | Redis / RabbitMQ | فقط Redis | فقط Redis |
| محدد معدل مدمج | لا (يتم كتابته يدويًا) | لا | نعم، من الصندوق |
| حد الدخول | متوسط | منخفض | متوسط |
| مناسب لـ | أنظمة الإنتاج الكبيرة | مشاريع صغيرة، نماذج أولية | مجموعة Node.js، روبوتات، أتمتة |
استراتيجيات توزيع الحمل حسب IP
Round-robin، الذي يستخدم في الأمثلة أعلاه، هو أبسط استراتيجية، لكنه ليس دائمًا الأمثل. إليك بعض الخيارات التي يجب مراعاتها حسب المهمة:
- Round-robin - كل طلب تالي يمر عبر البروكسي التالي بشكل دائري. مناسب عندما تكون جميع IP متساوية والحدود عليها متشابهة.
- جلسة ثابتة - يتم ربط بروكسي واحد بحساب أو جلسة معينة طوال فترة العمل معه. هذا حاسم للشبكات الاجتماعية والأسواق، حيث تبدو تغيير IP في منتصف الجلسة مشبوهة.
- توزيع وزني - يتم توجيه جزء من الطلبات إلى IP الأكثر تكلفة وموثوقية (مثل الموبايل)، وجزء إلى IP مراكز البيانات الرخيصة، حسب النطاق المعالج.
- تخفيض تلقائي - تنخفض سرعة إرسال المهام تلقائيًا إذا ارتفع معدل الأخطاء 429 أو الكابتشا، وتعود إلى طبيعتها عندما تختفي الأخطاء.
بالنسبة للمهام التي تتطلب التمويه كمستخدم حي - مثل تسخين والتحقق من الحسابات على Instagram أو TikTok عبر متصفحات مكافحة الكشف مثل Dolphin Anty أو AdsPower - من الأفضل استخدام جلسة ثابتة مع بروكسي موبايل: فهي أقل عرضة للاشتباه من أنظمة مكافحة الروبوتات وتتصرف مثل المستخدمين العاديين للإنترنت المحمول.
لجمع البيانات بكميات كبيرة من المواقع، حيث تكون السرعة والحجم مهمين، وليس موثوقية الخوارزمية، فإن round-robin مع بروكسي سكنية يوفر توازنًا بين الاستقرار والسعر: تبدو IP كمستخدمين حقيقيين، ولكن يمكن تغييرها بشكل متكرر دون مخاطر على جلسة معينة.
مراقبة الأخطاء 429 والحظر
قائمة المهام بدون مراقبة هي صندوق أسود: من الصعب فهم سبب فشل بعض المهام وأي IP تحت المراقبة بالفعل. الحد الأدنى من المقاييس التي يجب تتبعها:
- نسبة الردود برمز 429 و 403 لكل بروكسي على حدة
- متوسط وقت الاستجابة - زيادة التأخير غالبًا ما تسبق الحظر
- عدد المحاولات المتكررة (retries) على المهمة
- طول قائمة المهام في الوقت الحقيقي - إذا كانت تنمو، فإن العمال لا يتعاملون معها
في Celery، من السهل استخدام Flower - واجهة ويب لمراقبة المهام والعمال في الوقت الحقيقي. في BullMQ، يوجد Bull Board مع وظائف مماثلة. بالنسبة لـ RQ، يوجد RQ Dashboard. جميع الأدوات الثلاثة تعرض حالة المهام، وعدد المحاولات الفاشلة، وتسمح بسرعة فهم أي بروكسي من المجموعة يجب استبعاده مؤقتًا من التدوير.
من الممارسات الجيدة أن يتم وضع علامة على البروكسي كـ "في الحجر الصحي" لمدة 15-30 دقيقة بعد عدة أخطاء متتالية 429 من هذا IP، وإعادته إلى المجموعة فقط بعد إجراء اختبار ناجح. هذا يمنع الحالة التي يستمر فيها IP المحظور في تلقي المهام وإضاعة وقت العمال.
الخاتمة
تحل قائمة المهام مشكلة توسيع الطلبات، لكنها لا تحمي نفسها من الحد على IP - لذلك تحتاج إلى تدوير بروكسي مدروس داخل Celery و RQ أو BullMQ. يعتمد اختيار الأداة على النظام: غالبًا ما تستخدم مشاريع Python Celery للإنتاج و RQ للمهام البسيطة، بينما يفضل مطورو Node.js BullMQ مع محدد المعدل المدمج.
بغض النظر عن قائمة المهام المختارة، فإن النتيجة تعتمد بشكل كبير على جودة مجموعة البروكسي نفسها. إذا كانت المهمة هي استخراج سريع بكميات كبيرة، فاحرص على النظر إلى بروكسي مراكز البيانات للسرعة، أما بالنسبة للعمل مع الشبكات الاجتماعية والأسواق، حيث تكون ولاء أنظمة مكافحة الروبوتات مهمًا، فإن IP السكنية أو الموبايلية ستكون مناسبة - فهي تقلل من نسبة الأخطاء 429 والحظر، مما يقلل مباشرة من عدد المحاولات المتكررة ويسرع تنفيذ جميع المهام.