إذا كانت فاتورة البروكسي ترتفع أسرع من حجم البيانات المجمعة، فإن المشكلة تكمن تقريبًا دائمًا في منطق إعادة المحاولة. يقوم البارس بإعادة الطلبات الفاشلة عدة مرات دون أن يتحدث، ويستهلك البيانات على المهلات والتقاطعات، ولا يرى المطور حتى هذه النفقات في السجلات. دعونا نحلل كيفية حساب الخسائر الحقيقية وتقليلها دون فقدان جودة البيانات.
لماذا يستهلك منطق إعادة المحاولة البيانات
تم كتابة معظم البارسات بمنطق إعادة المحاولة الساذج: إذا لم ينجح الطلب - أعد المحاولة، وهكذا حتى 3-5 مرات. المشكلة هي أن كل طلب متكرر ليس مجرد طلب HTTP جديد، بل هو دورة كاملة: TCP-h handshake، TLS-negotiation، تحميل الصفحة بالكامل (حتى لو كان مطلوبًا فقط كتلة بيانات واحدة)، وأحيانًا إعادة تحميل الصور أو ملفات JS، إذا كان البارس يستخدم متصفح headless بدلاً من عميل HTTP بسيط.
تكون التكرارات مكلفة بشكل خاص عند العمل عبر بروكسي سكني, حيث يتم احتساب البيانات حسب الحجم، وليس حسب عدد الطلبات. يمكن أن يكلف طلب فاشل واحد لصفحة منتج Wildberries مع الصور والسكريبتات 300-500 كيلوبايت. إذا قام البارس بإجراء 3 تكرارات عند المهلة، فإنك تدفع مقابل نفس الطلب الفاشل أربع مرات متتالية - وهذا دون ضمان أن المحاولة الرابعة ستكون ناجحة.
السبب الثاني هو إعادة المحاولة بسبب الأخطاء التي لا يمكن تصحيحها بإعادة المحاولة. إذا أعاد الموقع 403 بسبب اكتشاف الروبوت، فإن الطلب المتكرر بنفس بصمة الإصبع ومن نفس جلسة الكوكيز سيحصل تقريبًا على نفس الرد. يستهلك البارس البيانات في محاولات لا يمكن أن تنتهي بالنجاح رياضيًا، حتى يتغير عنوان IP أو بصمة المتصفح.
كم من البيانات تذهب فعليًا إلى التكرارات
لفهم حجم المشكلة، دعونا نأخذ مثالًا بسيطًا. يقوم البارس بجمع بطاقات المنتجات من السوق، ومتوسط حجم الاستجابة هو 250 كيلوبايت (HTML + JSON API + جزء من الستاتيك). عند العمل بشكل مستقر دون حظر تبقى نسبة الطلبات الفاشلة عند مستوى 5-8%. ولكن عند القيام بالبارسينغ العدواني عبر بروكسيات مراكز البيانات الرخيصة يمكن أن ترتفع هذه النسبة إلى 25-35%، لأن مزود الهدف يتعرف بسرعة على النمط ويبدأ في إعادة الطلبات أو فرض حظر مؤقت على IP.
دعونا نحسب بالأرقام. لنفترض أننا بحاجة إلى جمع 100,000 بطاقة منتج:
| نسبة الطلبات الفاشلة | التكرارات لكل طلب (في المتوسط) | إجمالي البيانات | الإفراط في الاستهلاك |
|---|---|---|---|
| 5% | 0.15 | 28.75 غيغابايت | +15% |
| 15% | 0.45 | 36.25 غيغابايت | +45% |
| 30% | 0.90 | 47.5 غيغابايت | +90% |
كما هو واضح، عند نسبة فشل 30% واستراتيجية إعادة المحاولة "إعادة المحاولة حتى 3 مرات" فإن البيانات الفعلية تقريبًا تتضاعف مقارنة بالحد الأدنى النظري البالغ 25 غيغابايت. هذه الـ 20+ غيغابايت الزائدة هي خسائر مباشرة في ميزانية البروكسي، والتي يمكن تقليلها إذا تمت مراجعة منطق التكرارات.
الأخطاء الشائعة في منطق إعادة المحاولة للبارس
قبل إصلاح منطق إعادة المحاولة، من المهم التعرف على الأنماط المضادة الشائعة التي تحدث في 90% من البارسات المكتوبة يدويًا:
- إعادة المحاولة دون تحليل كود الخطأ. يتم بدء إعادة المحاولة في أي حالة غير طبيعية - 403، 429، 500، مهلة، انقطاع الاتصال - على الرغم من أن استراتيجية المعالجة لها يجب أن تختلف.
- تأخير ثابت بين التكرارات. على سبيل المثال، 2 ثانية بين المحاولات بغض النظر عما إذا كانت هذه هي المحاولة الأولى أو الخامسة - إما أنها عدوانية جدًا للموقع، أو بطيئة جدًا لحجم البيانات الكبير.
- إعادة المحاولة بنفس IP ونفس الجلسة. إذا قام الموقع بحظر الطلب بناءً على بصمة الإصبع، فإن إعادة المحاولة بنفس المعلمات لا تغير النتيجة، لكنها تستهلك البيانات.
- لا يوجد حد أعلى للمحاولات. بعض البارسات تتكرر على "روابط ميتة" وتقوم بعشرات التكرارات قبل الاستسلام.
- عدم وجود تمييز بين الأخطاء المؤقتة والدائمة. 404 (الصفحة غير موجودة) و 503 (الخادم غير متاح مؤقتًا) تتطلب منطقًا مختلفًا - ولكن غالبًا ما يتم معالجتها بنفس الطريقة.
تأخير أسي مع كود بلغة بايثون
حل بسيط ولكنه فعال هو التأخير الأسي مع التشتت (الانتشار العشوائي)، الذي يقلل من عدد التكرارات غير المجدية ويوزع الحمل على الوقت. بدلاً من فترة ثابتة بين المحاولات، يزداد التأخير بشكل أسي، مما يمنح الموقع الوقت "للتهدئة" بعد الحظر، ويمنع البارس من استهلاك البيانات في الطلبات التي من المحتمل أن تفشل.
import time
import random
import requests
def fetch_with_backoff(url, proxies, max_retries=4, base_delay=1.0):
retryable_codes = {429, 500, 502, 503, 504}
non_retryable_codes = {404, 410}
for attempt in range(max_retries + 1):
try:
response = requests.get(url, proxies=proxies, timeout=10)
if response.status_code == 200:
return response
if response.status_code in non_retryable_codes:
# لا جدوى من إعادة المحاولة - الصفحة غير موجودة فعليًا
return None
if response.status_code not in retryable_codes:
return None
except (requests.exceptions.Timeout,
requests.exceptions.ConnectionError):
pass # خطأ مؤقت في الشبكة - يمكن إعادة المحاولة
if attempt == max_retries:
return None
# تأخير أسي مع تشتت
delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
time.sleep(delay)
return None
الفكرة الرئيسية في هذا الكود هي تقسيم الأخطاء إلى ثلاث فئات: تلك التي لا يمكن تصحيحها بإعادة المحاولة (404، 410)، وتلك التي يمكن إعادة المحاولة بها مع تأخير (429، 500-504، المهلات)، وكل ما تبقى يعتبر فشلًا دون استهلاك محاولات إضافية. هذا التقسيم بحد ذاته يقلل من البيانات الزائدة بنسبة 20-30% مقارنةً بـ "إعادة المحاولة على كل شيء".
نصيحة: أضف Retry-After في المعالجة -
العديد من المواقع تشير بنفسها إلى عدد الثواني التي يجب الانتظار قبل إعادة المحاولة. تجاهل هذا العنوان -
سبب شائع للحظر الزائد واستهلاك البيانات.
Circuit breaker: متى يجب التوقف
يساعد التأخير الأسي على مستوى الطلب الواحد، ولكنه لا يحمي من الحالة التي يكون فيها النطاق بالكامل أو نقطة بروكسي معينة غير متاحة مؤقتًا لمئات الروابط المتتالية. هنا تحتاج إلى نمط circuit breaker - "قاطع الدائرة"، الذي يتتبع نسبة الأخطاء خلال فترة زمنية معينة ويتوقف مؤقتًا عن المحاولات إذا تجاوزت النسبة الحد، بدلاً من الاستمرار في الطرق على باب مغلق.
class CircuitBreaker:
def __init__(self, failure_threshold=0.5, window_size=50, cooldown=60):
self.failure_threshold = failure_threshold
self.window_size = window_size
self.cooldown = cooldown
self.results = []
self.open_until = 0
def is_open(self):
return time.time() < self.open_until
def record(self, success: bool):
self.results.append(success)
if len(self.results) > self.window_size:
self.results.pop(0)
if len(self.results) == self.window_size:
failure_rate = 1 - sum(self.results) / self.window_size
if failure_rate > self.failure_threshold:
self.open_until = time.time() + self.cooldown
self.results.clear()
المنطق بسيط: إذا فشلت أكثر من نصف الطلبات الأخيرة البالغ عددها 50، فإن البارس يوقف المحاولات لهذا النطاق أو البروكسي لمدة 60 ثانية. خلال هذه الفترة، يمكن تغيير IP، تقليل سرعة الطلبات أو التبديل إلى مجموعة بروكسي أخرى. هذا مهم بشكل خاص عند العمل مع المواقع المستهدفة التي تحظر نطاق IP مؤقتًا بعد تجاوز معدل الطلبات - الاستمرار في الطرق على باب مغلق يعني ببساطة حرق البيانات دون جدوى.
تدوير البروكسي الذكي عند التكرارات
واحدة من أكثر التدابير فعالية ضد إعادة المحاولات الزائدة هي عدم إعادة الطلب من نفس IP الذي حصل على رفض بالفعل. المنطق بسيط: إذا كانت الخطأ مرتبطة بحظر IP (403، 429، إعادة توجيه إلى CAPTCHA)، فإن تغيير البروكسي قبل إعادة المحاولة يزيد بشكل كبير من فرصة النجاح ويقلل من عدد المحاولات.
لجمع البيانات من أسواق مثل Wildberries أو Ozon أو Avito، يعمل الربط بشكل جيد: الطلبات العادية تتم عبر بروكسي مراكز البيانات - فهي أسرع وأرخص، وعندما يتم تنبيه كاشف الحظر عدة مرات متتالية، يتحول البارس إلى بروكسي سكني، التي نادرًا ما تقع تحت فلاتر أنظمة مكافحة الروبوتات. هذا النهج الهجين يقلل من إجمالي استهلاك البيانات، لأن IP السكنية المكلفة تُستخدم فقط حيثما يكون ذلك ضروريًا، وليس لكل الطلبات المتتالية.
| نوع الخطأ | استراتيجية إعادة المحاولة | هل تحتاج لتغيير IP؟ |
|---|---|---|
| مهلة الاتصال | Backoff، 1-2 تكرارات | لا |
| 403 / CAPTCHA | تدوير فوري | نعم، بالتأكيد |
| 429 (معدل الحد) | Backoff حسب Retry-After | يفضل |
| 500-503 | Backoff، 2-3 تكرارات | لا |
| 404 / 410 | بدون تكرارات | — |
بالنسبة للبارسات التي تحاكي حركة المرور المحمولة (مثل جمع البيانات من النسخ المحمولة لتطبيقات الأسواق أو الشبكات الاجتماعية)، من المنطقي استخدام بروكسي محمول - فهي نادرًا ما تثير الشكوك لدى أنظمة الحماية، وذلك لأن مزودي الخدمة يمنحون نفس IP لآلاف المستخدمين الحقيقيين في وقت واحد، مما يجعل الحظر النقطي غير مجدي بالنسبة للموقع المستهدف.
مراقبة مقاييس إعادة المحاولة
بدون مقاييس، تصبح تحسينات منطق إعادة المحاولة مجرد تخمين. الحد الأدنى من المؤشرات التي يجب تسجيلها مع كل طلب:
- معدل إعادة المحاولة - نسبة الطلبات التي تطلبت على الأقل إعادة واحدة.
- النجاح بعد إعادة المحاولة - ما هي النسبة المئوية للتكرارات التي انتهت في النهاية بنجاح (إذا كانت هذه النسبة منخفضة، فإن التكرارات تحرق البيانات فقط).
- البيانات على النتيجة الناجحة - الحجم الإجمالي للبيانات المرسلة مقسومًا على عدد السجلات التي تم جمعها بنجاح. هذه هي المقياس الرئيسي للفعالية.
- توزيع الأخطاء حسب الأكواد - يساعد على فهم أين يحدث تسرب البيانات الرئيسي: المهلات، 403، 429 أو شيء آخر.
- معدل إعادة المحاولة حسب نقاط البروكسي المحددة - إذا كان أحد IP يعطي معدل إعادة محاولة 80% بينما البقية 10%، فإن المشكلة محلية ويمكن حلها بتغيير نقطة معينة، وليس كل المنطق.
حتى جدول بسيط في Google Sheets أو سجل في CSV مع هذه المقاييس الخمسة، يتم تحديثه كل ساعة، يوفر بيانات كافية لرؤية الشذوذ وضبط الاستراتيجية في الوقت المناسب - على سبيل المثال، تقليل معدل الطلبات إلى قسم معين من الموقع أو زيادة نسبة IP السكنية في المجموعة.
قائمة التحقق لتحسين البيانات على إعادة المحاولة
- قسم أكواد الأخطاء إلى قابل لإعادة المحاولة وغير قابل لإعادة المحاولة - لا تعيد 404/410.
- قم بتنفيذ تأخير أسي مع تشتت بدلاً من تأخير ثابت.
- احترم عنوان
Retry-Afterإذا أرسله الموقع. - قم بتغيير IP قبل إعادة المحاولة عند 403 والشك في اكتشاف الروبوت.
- حدد حدًا صارمًا لعدد المحاولات (عادةً 3-4 كافٍ).
- قم بتنفيذ circuit breaker للنطاقات ونقاط البروكسي ذات معدل الفشل العالي.
- سجل معدل إعادة المحاولة والبيانات على النتيجة الناجحة - بدون مقاييس، لا يمكن تحسين الأداء.
- قسم مجموعة البروكسي: مراكز البيانات الرخيصة للمناطق المستقرة، وبروكسيات سكنية أو محمولة - للمناطق المشكلة.
الخاتمة
منطق إعادة المحاولة ليس مجرد تفصيل صغير في البارس، بل هو أحد العوامل الرئيسية التي تؤثر على تكلفة جمع البيانات. يمكن أن تزيد الاستراتيجية الساذجة "إعادة المحاولة على كل شيء" من البيانات الفعلية بنسبة 40-90% مقارنةً بالحد الأدنى النظري، بينما تنتهي معظم التكرارات بنفس الفشل الذي حدث في المحاولة الأولى. يسمح تقسيم الأخطاء حسب الأنواع، والتأخير الأسي، وcircuit breaker، وتدوير IP الذكي بتقليل هذه الخسائر بشكل كبير دون تقليل شمولية البيانات المجمعة.
إذا كان بارسك يعمل مع مواقع تكتشف الروبوتات بشكل عدواني - أسواق، شبكات اجتماعية، منصات إعلانات - من المفيد دمج عدة أنواع من البروكسي حسب المهمة. للعمليات الأساسية، يمكن استخدام بروكسي مراكز البيانات, وأينما كانت الحاجة إلى أقصى مقاومة للحظر، - بروكسي سكني مع عناوين IP حقيقية لمستخدمين عاديين. يوفر هذا النهج الهجين مع منطق إعادة المحاولة المدروس انخفاضًا ملحوظًا في استهلاك البيانات مع نفس حجم البيانات المجمعة.