هل يتعطل المحلل في Scrapy بسبب انتهاء المهلة، وتستهلك مجموعة الوكلاء بشكل أسرع بكثير مما هو متوقع، والسجلات مليئة بالاستجابات 407 و403 - هل هذه صورة مألوفة؟ في 90% من الحالات، المشكلة ليست في الوكلاء أنفسهم، بل في كيفية كتابة DownloaderMiddleware. نناقش خمس من أكثر الأخطاء شيوعًا في واجهة برمجة التطبيقات الوكيلة، التي تحول حركة المرور المكلفة إلى طلبات غير مجدية، ونوضح كيفية إصلاحها مع الشيفرة.
الخطأ 1: تدوير بدائي دون مراعاة حالة الوكيل
أكثر البنى شيوعًا التي يمكن العثور عليها في الدروس التعليمية هي random.choice(PROXY_LIST) داخل process_request. المشكلة هي أن هذا التدوير لا يعرف أي وكيل تم حظره للتو وأيهم لا يزال يعمل. في النهاية، يواصل المحلل إرسال الطلبات عبر عنوان IP المحظور بالفعل، ويتلقى 403/429، ويقوم بإعادة المحاولة - ويختار نفس العنوان مرة أخرى، لأن الاختيار عشوائي ولا يستبعد "العقد السيئة".
النهج الصحيح هو تتبع حالة كل وكيل: عدد الطلبات الناجحة، عدد الأخطاء، وقت الاستخدام الأخير. إليك الحد الأدنى من الخيار العملي:
import random
import time
class ProxyPool:
def __init__(self, proxies):
self.proxies = {p: {"fails": 0, "last_used": 0, "banned_until": 0} for p in proxies}
def get_proxy(self):
now = time.time()
available = [
p for p, state in self.proxies.items()
if state["banned_until"] < now
]
if not available:
# إذا كانت جميعها محظورة، نقوم بإعادة تعيين الحظر "الأقدم"
available = list(self.proxies.keys())
return random.choice(available)
def mark_fail(self, proxy, cooldown=300):
self.proxies[proxy]["fails"] += 1
self.proxies[proxy]["banned_until"] = time.time() + cooldown
def mark_success(self, proxy):
self.proxies[proxy]["fails"] = 0
self.proxies[proxy]["last_used"] = time.time()
تستبعد هذه المجموعة عناوين IP المحظورة خلال فترة "التهدئة" (cooldown) وتعيدها إلى التداول لاحقًا. هذا بالفعل يقلل من استهلاك الحركة بشكل كبير، لأنك لا تضغط على نفس العقدة المحظورة باستمرار.
الخطأ 2: معالجة غير صحيحة لإعادة المحاولة ورموز الحالة
الخطأ الثاني النموذجي هو استخدام RetryMiddleware القياسي من Scrapy دون تعديل. بشكل افتراضي، يقوم بإعادة المحاولة على نفس الوكيل الذي فشل، ما لم تقم بدمج تغيير الوكيل بشكل صريح في process_exception. ونتيجة لذلك، نحصل على الصورة الكلاسيكية: 3 إعادة محاولة، 3 حظر، الطلب لا يزال يسقط، وقد تم استهلاك الحركة بالفعل.
النقطة الثانية هي أنه ليس كل رموز الحالة تحتاج إلى إعادة المحاولة بنفس الطريقة. 429 (طلبات كثيرة جدًا) تتطلب فترة توقف وتغيير IP، بينما 403 عادة ما تعني حظر وكيل معين (تحتاج إلى استبدال فوري)، و5xx - غالبًا ما تكون مشكلة مؤقتة على جانب الخادم، يمكن إعادة المحاولة على نفس الوكيل. إذا قمت بدمج كل شيء في مجموعة واحدة، فإن واجهة برمجة التطبيقات إما تحرق الوكلاء بشكل مفرط، أو تنتظر طويلاً في الأماكن التي كان يجب فيها تغيير IP على الفور.
class SmartRetryMiddleware:
def __init__(self, pool):
self.pool = pool
def process_response(self, request, response, spider):
proxy = request.meta.get("proxy")
if response.status in (403, 407):
if proxy:
self.pool.mark_fail(proxy, cooldown=600)
new_request = request.copy()
new_request.meta["proxy"] = self.pool.get_proxy()
new_request.dont_filter = True
return new_request
if response.status == 429:
if proxy:
self.pool.mark_fail(proxy, cooldown=120)
new_request = request.copy()
new_request.meta["proxy"] = self.pool.get_proxy()
new_request.dont_filter = True
return new_request
if proxy:
self.pool.mark_success(proxy)
return response
هنا من المهم فصل فترة التهدئة حسب نوع الخطأ: الحظر الصارم (403/407) - فترة توقف طويلة، وحدود التردد (429) - فترة قصيرة. هذا يوفر عشرات النسب المئوية من الحركة في الجولات الطويلة.
الخطأ 3: عدم وجود جلسات ثابتة للمواقع التي تتطلب المصادقة
إذا كان المحلل يعمل مع موقع يتطلب تسجيل دخول، سلة تسوق، ترقيم الصفحات مع الحفاظ على الحالة أو كابتشا مع التحقق من IP - فإن تغيير الوكيل في كل طلب يكسر الجلسة. يرى الموقع أن الطلب رقم 1 جاء من IP واحد، بينما جاء الطلب رقم 2 (في إطار نفس جلسة الكوكي) من IP آخر، وهذا ينبه الحماية من الروبوتات على الفور، حتى لو كانت كلا IP "نظيفين".
الحل هو ربط وكيل واحد بجلسة منطقية واحدة (على سبيل المثال، بحساب معين أو بسلسلة من الطلبات داخل نطاق واحد) لفترة زمنية ثابتة، بدلاً من تغييره في كل طلب. تُسمى هذه الجلسة "ثابتة".
class StickySessionMiddleware:
def __init__(self, pool, ttl=600):
self.pool = pool
self.ttl = ttl
self.sessions = {} # session_id -> (proxy, expires_at)
def process_request(self, request, spider):
session_id = request.meta.get("session_id")
if not session_id:
return
now = time.time()
session = self.sessions.get(session_id)
if session and session[1] > now:
request.meta["proxy"] = session[0]
else:
proxy = self.pool.get_proxy()
self.sessions[session_id] = (proxy, now + self.ttl)
request.meta["proxy"] = proxy
بالنسبة للمهام التي تحتاج إلى استقرار IP طوال فترة الجلسة - المصادقة، العمل مع الحسابات الشخصية، النماذج متعددة الخطوات - فإن أفضل الخيارات هي الوكلاء السكنيين الذين يدعمون الجلسات: حيث يسمحون بالحفاظ على نفس IP الصادر لبضع دقائق أو ساعات، ثم تغييره بشكل منظم، وليس عشوائيًا في كل طلب.
الخطأ 4: نقل غير صحيح لمصادقة الوكيل
الخطأ الرابع - تقني، ولكنه يظهر في كل مشروع تقريبًا. يقوم المطورون بنقل اسم المستخدم وكلمة المرور للوكيل مباشرة في عنوان URL على شكل http://user:pass@ip:port عبر request.meta["proxy"]. يعمل هذا في معظم الحالات، ولكن عند استخدام الوكيل عبر نفق HTTPS أو عند العمل مع بعض المزودين، يتم معالجة هذه الطريقة للمصادقة بشكل غير صحيح بواسطة HttpProxyMiddleware القياسي، وتفشل الطلبات مع 407 Proxy Authentication Required، على الرغم من أن بيانات الاعتماد صحيحة.
الطريقة الأكثر موثوقية هي نقل عنوان Proxy-Authorization بشكل صريح، مشفرًا في base64:
import base64
class ProxyAuthMiddleware:
def process_request(self, request, spider):
proxy = request.meta.get("proxy")
if not proxy:
return
# الوكيل بدون بيانات الاعتماد في URL
request.meta["proxy"] = proxy
user = request.meta.get("proxy_user")
password = request.meta.get("proxy_pass")
if user and password:
credentials = f"{user}:{password}"
encoded = base64.b64encode(credentials.encode()).decode()
request.headers["Proxy-Authorization"] = f"Basic {encoded}"
يعمل هذا النهج بشكل أكثر استقرارًا عند التوسع إلى مئات الطلبات المتوازية ولا يعتمد على كيفية تحليل الإصدار المحدد من المكتبة لعنوان URL مع بيانات الاعتماد المدمجة. هذا مهم بشكل خاص عند العمل مع الوكلاء المحمولين، حيث غالبًا ما تكون المصادقة مرتبطة بقائمة بيضاء من IP أو تحقق صارم من العناوين.
الخطأ 5: عدم وجود مراقبة وتسجيل للحظر
الخطأ الأخير، وربما الأكثر تكلفة من حيث العواقب - هو عدم وجود تسجيل لما هي الوكلاء التي يتم حظرها، ومدى تكرار ذلك وعلى أي نطاقات. بدون هذه البيانات، من المستحيل فهم ما الذي يستهلك الحركة: سواء كانت مجموعة الوكلاء قد استنفدت على موقع معين، أو كانت المشكلة في المحلل نفسه (طلبات متكررة جدًا، عدم وجود تأخيرات، عناوين مشبوهة).
الحد الأدنى من مجموعة المقاييس التي يجب تسجيلها في واجهة برمجة التطبيقات:
- عدد الطلبات لكل وكيل خلال جلسة التحليل
- عدد ورموز الأخطاء (403، 407، 429، 5xx) لكل وكيل
- مدة حياة الوكيل حتى أول حظر
- النطاقات التي تحدث فيها الحظورات بشكل متكرر
import logging
import json
logger = logging.getLogger("proxy_stats")
class ProxyStatsMiddleware:
def __init__(self):
self.stats = {}
def process_response(self, request, response, spider):
proxy = request.meta.get("proxy", "unknown")
domain = request.url.split("/")[2]
key = f"{proxy}|{domain}"
entry = self.stats.setdefault(key, {"requests": 0, "errors": 0})
entry["requests"] += 1
if response.status in (403, 407, 429):
entry["errors"] += 1
if entry["requests"] % 50 == 0:
logger.info(json.dumps(self.stats))
return response
بدون هذه الإحصائيات، فإن أي محاولات "لتحسين" واجهة برمجة التطبيقات ستتحول إلى تخمين. مع هذه البيانات، يمكنك رؤية بوضوح: إذا كان، على سبيل المثال، نطاق معين يحظر 80% من مجموعة الوكلاء خلال أول 10 طلبات - فإن المشكلة ليست في الوكلاء، بل في نمط الطلبات (عدم وجود تدوير لـ User-Agent، تردد مرتفع جدًا، عدم وجود تأخيرات بين الطلبات).
مثال عملي على واجهة برمجة التطبيقات الوكيلة بالكامل
نجمع كل شيء معًا في settings.py - ترتيب واجهة برمجة التطبيقات حاسم، لأنه يعتمد على الترتيب الذي يتم فيه تطبيق الفحوصات:
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.ProxyAuthMiddleware": 350,
"myproject.middlewares.StickySessionMiddleware": 400,
"myproject.middlewares.SmartRetryMiddleware": 550,
"myproject.middlewares.ProxyStatsMiddleware": 900,
}
RETRY_ENABLED = False # تعطيل إعادة المحاولة القياسية، نستخدم خاصتنا
DOWNLOAD_TIMEOUT = 15
CONCURRENT_REQUESTS_PER_DOMAIN = 8
لاحظ RETRY_ENABLED = False - هذا أمر أساسي، وإلا فإن آلية إعادة المحاولة المدمجة في Scrapy ستتعارض مع منطق تغيير الوكيل الخاص بك، وستبدأ الطلبات في التكرار أو إعادة المحاولة مرتين. من المهم أيضًا تقييد CONCURRENT_REQUESTS_PER_DOMAIN - فالتوازي العالي جدًا لنطاق واحد حتى مع تدوير الوكلاء يبدو مشبوهًا لأنظمة مكافحة الروبوتات.
ما هو نوع الوكيل الذي يجب اختياره لـ Scrapy
تحل واجهة برمجة التطبيقات نصف المشكلة، والنصف الآخر هو الاختيار الصحيح لمجموعة الوكلاء المناسبة للمهمة. فيما يلي مقارنة بالسيناريوهات النموذجية للتحليل.
| نوع الوكيل | متى تستخدمه | الإيجابيات | السلبيات |
|---|---|---|---|
| وكلاء مراكز البيانات | تحليل جماعي للصفحات المفتوحة بدون حماية صارمة ضد الروبوتات | سرعة عالية، تكلفة منخفضة للحركة | تكتشف بسهولة، وغالبًا ما تكون في القائمة السوداء |
| وكلاء سكنيين | تحليل الأسواق، المواقع ذات الحماية من JavaScript، المصادقة | IP حقيقية، نسبة منخفضة من الحظر، دعم للجلسات الثابتة | سرعة أقل مقارنة بمراكز البيانات |
| وكلاء محمولين | تحليل النسخ المحمولة من المواقع وواجهات برمجة التطبيقات ذات الحماية الصارمة ضد الروبوتات | أقصى ثقة من المواقع المستهدفة | أعلى تكلفة للحركة |
القاعدة العملية: لتحليل الصفحات الثابتة بدون حماية قوية، فإن الوكلاء الرخيصين من مراكز البيانات مع واجهة برمجة التطبيقات الذكية من هذه المقالة هي الأنسب. إذا كان الموقع يستخدم Cloudflare أو PerimeterX أو DataDome أو أنظمة مشابهة - فإن عناوين IP من مراكز البيانات ستتعرض للحظر تقريبًا على الفور، وفي هذه الحالة، من الأفضل الانتقال مباشرة إلى مجموعة سكنية، مما يوفر الوقت في تصحيح واجهة برمجة التطبيقات.
قائمة التحقق قبل تشغيل المحلل في الإنتاج
- تستبعد واجهة برمجة التطبيقات الوكلاء المحظورين خلال فترة التهدئة، وليس تختارهم عشوائيًا
- تفرق منطق إعادة المحاولة بين أنواع الأخطاء (403/407 مقابل 429 مقابل 5xx) مع فترات تهدئة مختلفة
- للمهام الجلسات، يتم استخدام ربط ثابت للوكيل، وليس تغييره في كل طلب
- يتم نقل مصادقة الوكيل عبر عنوان Proxy-Authorization، وليس فقط عبر URL
- يتم تسجيل إحصائيات الحظر حسب النطاقات والوكلاء لتشخيص المشاكل
- تم تعطيل واجهة برمجة التطبيقات القياسية RetryMiddleware من Scrapy، لتجنب التعارض مع المنطق المخصص
- تم تقييد التوازي إلى قيم معقولة، وليس تعيينه على الحد الأقصى
الخاتمة
يتم حل معظم المشاكل المتعلقة باستهلاك حركة مرور الوكلاء في Scrapy ليس من خلال شراء مجموعة أكبر من عناوين IP، بل من خلال تصحيح منطق واجهة برمجة التطبيقات: فصل الأخطاء حسب الأنواع، والجلسات الثابتة للسيناريوهات المعقدة، والمصادقة الصحيحة والمراقبة المستمرة للحظر. يمكن أخذ الشيفرة من هذه المقالة كأساس وتكييفها مع المشروع المحدد - تظل الهيكلية عملية سواء للمحللات الصغيرة أو لتثبيتات Scrapy-Cluster الموزعة.
إذا كانت واجهة برمجة التطبيقات قد تم إعدادها بشكل صحيح بالفعل، ولكن الحظر لا يزال يحدث بشكل متكرر للغاية - فمن المحتمل أن تكون المشكلة في جودة مجموعة IP نفسها. لتحليل المواقع ذات الحماية المتقدمة ضد الروبوتات، يجب تجربة الوكلاء السكنيين الذين يدعمون الجلسات - حيث يقللون بشكل ملحوظ من نسبة تفعيل الحماية مقارنة بالعناوين من مراكز البيانات مع نفس منطق واجهة برمجة التطبيقات.