عندما ينتقل المحلل من خادم واحد إلى مجموعة من عدة عمال، تظهر مشكلة جديدة - عدة آلات تبدأ في استخدام نفس مجموعة البروكسي في نفس الوقت. بدون تنسيق، يؤدي ذلك إلى تكرار IP في الطلبات، وتجاوز الحدود على عنوان واحد، وحظر جماعي. في هذه المقالة، سنستعرض كيفية تقسيم البروكسي بشكل صحيح بين الخوادم، وأي مخططات توزيع تعمل بشكل أفضل، وكيفية تنفيذ ذلك عمليًا باستخدام كود بلغة Python وRedis.
لماذا تتعارض البروكسي في المحلل الموزع
لنفترض حالة نموذجية: لديك مجموعة من 500 بروكسي وثلاثة خوادم، كل منها يشغل 50 عاملًا متوازيًا لتحليل Wildberries أو Ozon أو Avito. إذا أخذ كل خادم البروكسي من القائمة العامة بشكل مستقل دون تنسيق، فقد يرسل عاملان على آلات مختلفة طلبًا من نفس IP إلى نفس النطاق في نفس الوقت. بالنسبة لنظام مكافحة الروبوتات، يبدو هذا كأن هناك تكرارًا غير عادي في تردد الطلبات من عنوان واحد - وIP يتم حظره في غضون دقائق معدودة.
المشكلة الثانية هي توزيع الحمل غير المتوازن. إذا تم تقسيم البروكسي ببساطة "كما يحدث"، فقد يأخذ خادم واحد أسرع وأحدث IP، بينما يأخذ الآخر البقية ذات السمعة السيئة. في النهاية، ستختلف سرعة التحليل بين الخوادم بشكل كبير، وكذلك نسبة الأخطاء.
المشكلة الثالثة هي فقدان الحالة. إذا تم حظر البروكسي على خادم واحد، فلن تعرف الخوادم الأخرى بذلك وستستمر في استخدامه حتى تتلقى الحظر أيضًا. بدون سجل مركزي لحالة البروكسي، يعيد كل خادم "اكتشاف أمريكا" من جديد، مما يهدر الطلبات بلا فائدة.
الحل لجميع المشكلات الثلاث هو إدخال آلية تنسيق موحدة: إما من خلال تخزين مركزي لحالة البروكسي (Redis، PostgreSQL، Consul)، أو من خلال تقسيم محدد مسبقًا لمجموعة البروكسي، بدون تداخل النطاقات بين الخوادم.
بنية المحلل الموزع
قبل تقسيم البروكسي، يجب تحديد البنية العامة للمجموعة. هناك نهجان أساسيان.
مخطط ثابت (shared-nothing). يتم تقسيم مجموعة البروكسي بين الخوادم مسبقًا، مرة واحدة عند بدء التشغيل. يحصل كل خادم على قسم غير متداخل ويعمل به بشكل مستقل، دون الرجوع إلى الخوادم الأخرى. الميزة - أقصى بساطة وموثوقية: انهيار خادم واحد لا يؤثر على الآخرين. العيب - إذا تم حظر نصف البروكسي على خادم واحد، فلا يمكنه "استعارة" IP جديدة من الجار.
مخطط ديناميكي (shared-state). جميع الخوادم تتصل بمخزن مشترك (عادةً Redis)، الذي يحتفظ بحالة كل بروكسي ويصدرها عند الطلب مع قفل. الميزة - استخدام متوازن للمجموعة واستجابة فورية للحظر. العيب - يصبح المخزن نقطة فشل واحدة، مما يتطلب منطقًا منفصلًا في حالة عدم توفره.
في الممارسة العملية، غالبًا ما يتم اختيار الهجين لتحليل الأسواق ووسائل التواصل الاجتماعي: يقوم الوسيط على Redis بإصدار البروكسي مع قفل لفترة قصيرة (TTL)، وعند عدم توفر Redis، تتحول الخوادم مؤقتًا إلى مجموعة احتياطية محلية. يجمع هذا النهج بين موثوقية المخطط الثابت ومرونة المخطط الديناميكي.
طرق تقسيم مجموعة البروكسي بين الخوادم
دعونا نستعرض خوارزميات محددة يمكن تطبيقها لتقسيم المجموعة.
التقسيم البسيط حسب المودول (round-robin sharding). قمنا بترقيم البروكسي من 0 إلى N-1، ويأخذ الخادم برقم i جميع البروكسي التي index % عدد_الخوادم == i. بسيط، لكنه لا يتحمل إضافة أو إزالة الخوادم - عند تغيير عدد الآلات، يتم إعادة توزيع معظم المجموعة من جديد.
التجزئة المتسقة. يتم تعيين موقع لكل خادم ولكل بروكسي على حلقة التجزئة (عادةً بناءً على تجزئة عنوان IP أو ID الخادم). يتم ربط البروكسي بأقرب خادم على الحلقة. عند إضافة أو إزالة خادم، يتم إعادة توزيع جزء صغير فقط من البروكسي، وليس المجموعة بأكملها - هذه ميزة رئيسية للمجموعات التي تتوسع ديناميكيًا (مثل Kubernetes مع التوسع التلقائي).
وسيط مركزي مع أقفال. بدلاً من ربط البروكسي بشكل صارم بالخادم، تطلب جميع الخوادم بروكسي "حر" من خدمة مشتركة (Redis، RabbitMQ)، التي تصدره مع قفل مؤقت. بعد الاستخدام، يتم إرجاع البروكسي إلى المجموعة. هذه هي الخيار الأكثر مرونة، خاصة إذا كان الحمل على الخوادم غير متوازن.
التقسيم حسب النطاقات أو المهام. إذا كان المحلل يعمل مع عدة مصادر في نفس الوقت (مثل Wildberries وOzon وAvito بالتوازي)، فمن المنطقي تقسيم البروكسي ليس فقط بين الخوادم، ولكن أيضًا بين المصادر - حتى لا يظهر نفس IP في عدة منصات في نفس الوقت. هذا يقلل من خطر إدراج IP في القائمة السوداء العامة لأنظمة مكافحة الاحتيال، إذا كانت هذه المنصات تتبادل البيانات.
جدول: مقارنة طرق تقسيم البروكسي
| الطريقة | الصعوبة | القدرة على التوسع | متى تستخدم |
|---|---|---|---|
| التقسيم حسب المودول | منخفضة | سيئة | عدد ثابت من الخوادم |
| التجزئة المتسقة | متوسطة | جيدة | التوسع التلقائي للمجموعة |
| وسيط مركزي | متوسطة-مرتفعة | ممتازة | حمل غير متوازن، حركة مرور عالية |
| التقسيم حسب النطاقات | منخفضة | متوسطة | تحليل عدة منصات في نفس الوقت |
تنفيذ وسيط البروكسي على Redis
الخيار الأكثر عملية لمعظم المحللين الموزعين هو الوسيط المركزي على Redis. يناسب Redis لأنه سريع، ويدعم العمليات الذرية وTTL "من الصندوق". الفكرة بسيطة: جميع البروكسي المتاحة موجودة في مجموعة Redis، وعند إصدار بروكسي لعامل معين، يتم قفله لفترة محددة عبر مفتاح مع TTL.
أدناه - مثال عملي لوسيط بلغة Python مع مكتبة redis-py. يقوم بتنفيذ إصدار بروكسي حر مع قفل، وتحريره بعد الاستخدام، ووضع علامة على البروكسي كأنه محظور.
import redis
import time
import random
r = redis.Redis(host="localhost", port=6379, db=0, decode_responses=True)
PROXY_POOL_KEY = "proxy:pool" # مجموعة جميع البروكسي العاملة
LOCK_PREFIX = "proxy:lock:" # بادئة قفل البروكسي المحدد
BANNED_PREFIX = "proxy:banned:" # بادئة علامة الحظر
LOCK_TTL = 30 # ثوانٍ، لمدة قفل البروكسي
BAN_TTL = 3600 # ثوانٍ، لمدة اعتبار البروكسي محظورًا
def load_pool(proxy_list):
"""تحميل قائمة البروكسي إلى Redis مرة واحدة عند بدء المجموعة."""
if proxy_list:
r.sadd(PROXY_POOL_KEY, *proxy_list)
def get_free_proxy(server_id, max_attempts=20):
"""كل خادم يستدعي هذه الوظيفة للحصول على بروكسي حر."""
candidates = list(r.smembers(PROXY_POOL_KEY))
random.shuffle(candidates)
for proxy in candidates[:max_attempts]:
if r.exists(BANNED_PREFIX + proxy):
continue
lock_key = LOCK_PREFIX + proxy
# تعيين القفل الذري، إذا كان البروكسي حرًا
acquired = r.set(lock_key, server_id, nx=True, ex=LOCK_TTL)
if acquired:
return proxy
return None # لم يتم العثور على بروكسيات حرة، نحتاج إلى الاحتياطي
def release_proxy(proxy):
"""تحرير البروكسي بعد الطلب الناجح."""
r.delete(LOCK_PREFIX + proxy)
def mark_banned(proxy):
"""وضع علامة على البروكسي كأنه محظور لمدة BAN_TTL ثانية، وإزالة القفل."""
r.set(BANNED_PREFIX + proxy, 1, ex=BAN_TTL)
r.delete(LOCK_PREFIX + proxy)
r.srem(PROXY_POOL_KEY, proxy) # إزالة مؤقتة من المجموعة
def return_to_pool_if_recovered():
"""مهمة دورية: إرجاع البروكسي التي انتهت مدة حظرها."""
for proxy in r.scan_iter(match=BANNED_PREFIX + "*"):
clean_proxy = proxy.replace(BANNED_PREFIX, "")
if not r.exists(BANNED_PREFIX + clean_proxy):
r.sadd(PROXY_POOL_KEY, clean_proxy)
النقطة الرئيسية هنا هي استخدام SET ... NX EX في Redis. هذه الأمر ذري: إذا حاول خادمان قفل نفس البروكسي في نفس الوقت، سيحقق أحدهما النجاح، بينما سيفشل الآخر ويذهب إلى المرشح التالي في القائمة. هذا هو ما يستبعد التعارضات بين الخوادم عند الوصول المتوازي إلى المجموعة العامة.
مثال على استخدام الوسيط في عامل المحلل:
import requests
def fetch_with_proxy(url, server_id):
proxy = get_free_proxy(server_id)
if not proxy:
raise RuntimeError("لا توجد بروكسيات حرة في المجموعة")
proxies = {"http": f"http://{proxy}", "https": f"http://{proxy}"}
try:
response = requests.get(url, proxies=proxies, timeout=10)
if response.status_code in (403, 429):
mark_banned(proxy)
else:
release_proxy(proxy)
return response
except requests.exceptions.RequestException:
mark_banned(proxy)
raise
هذه المخطط سهل التوسع - يمكنك إضافة 10 أو 50 خادمًا، وسيتصلون بنفس مثيل Redis، ولن تحدث تعارضات على مستوى IP لأن القفل ذري على مستوى التخزين، وليس على مستوى خادم منفصل.
الجلسات الثابتة وربط البروكسي بالمهام
لبعض المهام، فإن إصدار "أي بروكسي حر" ليس كافيًا. إذا كان المحلل يحاكي سلوك المستخدم - على سبيل المثال، يتصفح بطاقات المنتجات بشكل متسلسل، يضيف إلى السلة، ينتقل بين صفحات الكتالوج - من المهم أن تذهب جميع الطلبات من نفس IP. خلاف ذلك، سيلاحظ الموقع تغييرًا حادًا في الجغرافيا أو الجهاز أثناء الجلسة ويقوم بحظرها كأنها مشبوهة.
لهذا، يتم إدخال مفهوم الجلسة الثابتة: يتم ربط البروكسي ليس بطلب واحد، ولكن بمهمة معينة أو جلسة مستخدم طوال فترة حياتها. في Redis، يتم تنفيذ ذلك من خلال مفتاح منفصل على شكل session:{task_id} -> proxy مع TTL، يساوي المدة المتوقعة للجلسة.
SESSION_PREFIX = "proxy:session:"
SESSION_TTL = 600 # 10 دقائق للجلسة
def get_sticky_proxy(task_id, server_id):
existing = r.get(SESSION_PREFIX + task_id)
if existing:
return existing
proxy = get_free_proxy(server_id)
if proxy:
r.set(SESSION_PREFIX + task_id, proxy, ex=SESSION_TTL)
return proxy
بالنسبة للمهام ذات الجلسات الطويلة - مثل تصفح كتالوج Wildberries مع محاكاة سلوك المشتري الحقيقي - من الضروري استخدام البروكسي التي تتصرف بشكل مشابه قدر الإمكان للمستخدم العادي. هنا، تعتبر البروكسي السكنية مناسبة، حيث إنها مرتبطة بعناوين IP منزلية حقيقية وتثير الشك أقل لدى أنظمة مكافحة الروبوتات في الأسواق مقارنة بعناوين مراكز البيانات.
معالجة الحظر والتدوير في المجموعة
في نظام موزع، من المهم أن تصبح معلومات حظر البروكسي متاحة على الفور لجميع الخوادم، وليس فقط لذلك الذي تلقى الحظر. في المثال أعلاه، تتولى وظيفة mark_banned هذه المهمة - بمجرد أن يسجل خادم واحد 403 أو 429 من الموقع المستهدف، يتم إزالة البروكسي من المجموعة العامة في Redis، وتتوقف الخوادم الأخرى تلقائيًا عن الحصول عليه.
لتحديد الحظر، يجب تحليل ليس فقط رمز HTTP للرد، ولكن أيضًا محتوى الصفحة - بعض المواقع تعيد 200 OK، ولكن مع CAPTCHA أو صفحة فارغة بدلاً من المحتوى الحقيقي. عادةً ما تبدو منطق الكشف عن الحظر كما يلي:
- رموز HTTP 403، 429، 503 - علامة واضحة على الحظر أو حد السرعة.
- إعادة توجيه إلى صفحة CAPTCHA أو تسجيل الدخول - تحقق من URL بعد الطلب.
- انخفاض حاد في حجم الرد أو عدم وجود محددات CSS المتوقعة - علامة على صفحة فارغة.
- مهلة الاتصال - غالبًا ما تعني أن البروكسي توقف عن الاستجابة تمامًا.
بعد تسجيل الحظر، من المنطقي عدم حذف البروكسي من المجموعة إلى الأبد، بل وضعه في "حجر صحي" مع TTL (كما في المثال - BAN_TTL)، بعد ذلك يعود البروكسي تلقائيًا إلى المجموعة. العديد من الحظر في الأسواق ووسائل التواصل الاجتماعي مؤقتة وتزال بعد 30-60 دقيقة.
إذا كانت الحظورات تحدث بشكل متكرر جدًا حتى مع توزيع الحمل المتوازن، فهذا عادةً ما يكون إشارة إلى أن مجموعة البروكسي نفسها ذات جودة منخفضة أو حجم غير كافٍ للحجم المطلوب من الطلبات. في مثل هذه الحالات، يساعد توسيع المجموعة من خلال بروكسي مراكز البيانات للمهام الأقل حساسية (مثل التحقق من توفر الصفحات) واستخدام IP سكنية أغلى فقط للجلسات الحرجة على تقليل نسبة الحظر العامة دون زيادة النفقات على جميع حركة المرور.
مراقبة صحة مجموعة البروكسي
في مجموعة من عدة خوادم، من السهل فقدان الصورة العامة: كم عدد البروكسي النشطة حاليًا، وكم عدد المحظورة، وما هي سرعة الاستجابة المتوسطة لكل منها. بدون مراقبة، من الصعب فهم سبب تباطؤ التحليل - هل خادم واحد قد تعطل، أم أن البروكسي الحية قد انتهت.
الحد الأدنى من المقاييس التي يجب جمعها في Redis أو قاعدة بيانات منفصلة:
- عدد البروكسي النشطة في المجموعة في الوقت الحالي.
- عدد البروكسي المحظورة ومتوسط الوقت حتى استعادتها.
- متوسط وقت الاستجابة لكل بروكسي خلال آخر N طلبات.
- نسبة الطلبات الناجحة (نسبة النجاح) حسب الخوادم وحسب المجموعة ككل.
- توزيع الحمل - عدد الطلبات التي عالجها كل خادم خلال فترة معينة.
لتصور هذه المقاييس، من الملائم تجميعها في Prometheus من خلال مصدر تصدير بسيط، يقرأ البيانات من Redis كل دقيقة ويقدمها بتنسيق مفهوم لـ Grafana. يسمح ذلك برؤية سريعة لانخفاض نسبة النجاح حسب الجغرافيا المحددة أو نوع البروكسي، وإضافة IP جديدة إلى المجموعة في الوقت المناسب قبل أن يتوقف المحلل تمامًا.
يجب مراقبة ذروات الحظر عند تشغيل خوادم جديدة في المجموعة بشكل منفصل - إذا زادت نسبة الحظر بشكل حاد عند التوسع من 3 إلى 6 خوادم، فمن المحتمل أن تكون المشكلة في التجزئة: بدأت الخوادم الجديدة تتداخل مع النطاقات البروكسي مع القديمة. هذه هي العلامة الأولى على أنه يجب الانتقال من التقسيم حسب المودول إلى التجزئة المتسقة.
قائمة التحقق لإطلاق المحلل الموزع
قبل نشر المحلل على عدة خوادم، تحقق من النقاط التالية:
- تم اختيار آلية تنسيق المجموعة: تقسيم ثابت، تجزئة متسقة أو وسيط مركزي على Redis.
- تم تنفيذ قفل ذري للبروكسي، مما يستبعد استخدام نفس IP من قبل خادمين في نفس الوقت.
- تم إعداد TTL للقفل وللحظر، حتى لا "تتجمد" البروكسي في حالة محظورة إلى الأبد عند تعطل العامل.
- بالنسبة للمهام التي تتطلب محاكاة جلسة المستخدم، تم إعداد ربط البروكسي الثابت طوال مدة المهمة.
- تم إعداد كشف الحظر ليس فقط حسب رمز HTTP، ولكن أيضًا حسب محتوى الرد.
- تم تفعيل مراقبة نسبة النجاح، والبروكسي النشطة والمحظورة عبر المجموعة بأكملها.
- تم تقسيم مجموعة البروكسي حسب أنواع المهام: IP مراكز البيانات - للطلبات الأقل حساسية، والبروكسي السكنية أو البروكسي المحمولة - للجلسات التي تكون فيها الشبه بالمستخدم الحقيقي مهمة.
- تم التحقق من الحمل على مخزن الحالة (Redis) - عند وجود عدد كبير من الخوادم وتكرار الطلبات العالية، قد تحتاج إلى مجموعة Redis بدلاً من مثيل واحد.
الخاتمة
السبب الرئيسي للتعارضات عند انتقال المحلل إلى عدة خوادم هو عدم وجود مصدر واحد للحقيقة حول حالة مجموعة البروكسي. بمجرد ظهور وسيط مركزي مع أقفال ذرية (على Redis أو مخزن مماثل)، تختفي مشكلة تكرار IP وعدم تزامن الحظر من تلقاء نفسها. بعد ذلك، يتبقى اختيار استراتيجية التجزئة وفقًا لحجم مجموعتك: بالنسبة لعدد ثابت من الخوادم، يكفي التقسيم البسيط، بينما للبنية التحتية المتزايدة ديناميكيًا، من الأفضل وضع التجزئة المتسقة من البداية.
إذا كان المحلل الموزع الخاص بك يعمل مع الأسواق أو وسائل التواصل الاجتماعي، حيث تكون مصداقية الحركة على مستوى جلسة واحدة مهمة، فمن الأفضل تقسيم المجموعة حسب نوع المهام: للبروكسي مراكز البيانات الرخيصة لتحليل الكتالوجات، والبروكسي السكنية مع عناوين IP المنزلية الحقيقية، التي تقلل بشكل ملحوظ من نسبة الحظر في الأنظمة الحساسة لمكافحة الاحتيال.