← العودة إلى المدونة

استبعاد البروكسيات السيئة من مجموعة المحلل: مقاييس الأخطاء والتدوير باستخدام بايثون

نستعرض المقاييس التي تشير إلى تدهور البروكسي في مجموعة المحلل، وكيفية بناء نظام تلقائي لاستبعاد وتدوير عناوين IP.

📅٨ جمادى الأولى ١٤٤٨ هـ

إذا بدأ محلل Wildberries أو Ozon فجأة في إظهار صفحات فارغة، أو تأخيرات، أو أخطاء 403 - فالمشكلة ليست دائماً في الموقع نفسه. في كثير من الأحيان، تكون بعض عناوين IP في مجموعة البروكسي قد "تجاوزت صلاحيتها": تم حظرها، أو تم إبطاؤها، أو تم وضعها تحت CAPTCHA. نتناول المقاييس التي تميز البروكسي العامل عن الميت وكيفية أتمتة عملية التصفية دون مراقبة يدوية.

لماذا تتدهور مجموعة البروكسي مع مرور الوقت

أي مجموعة بروكسي - حتى لو تم شراؤها من مزود موثوق - تفقد مع مرور الوقت جزءًا من العناوين العاملة. الأسباب متنوعة: أنظمة مكافحة الاحتيال في Wildberries وOzon وAvito تجمع علامات سمعة على IP، وبعض العناوين تدخل القوائم السوداء بعد طلبات جماعية من عملاء آخرين للمزود (إذا كانت البروكسي مشتركة)، نطاقات مراكز البيانات تُحظر بكميات كبيرة حسب الشبكات الفرعية، وأحيانًا تخرج IP السكنية من المجموعة ببساطة لأن المستخدم الحقيقي أوقف الجهاز.

إذا لم يتم تنظيف المجموعة، فإن نسبة "البروكسيات الميتة" تنمو بشكل غير خطي: في البداية 5%، وبعد أسبوع من التحليل النشط - 20-30%. يبدأ المحلل في استهلاك الموارد في محاولات متكررة عبر IP غير عاملة، ويزداد متوسط الوقت لجمع صفحة واحدة، وتتراكم الأخطاء في السجلات التي تخفي المشاكل الحقيقية (مثل تغيير تصميم الموقع). بدون نظام تصفية، إما أن تحتفظ بالمجموعة متضخمة "للاحتياط"، أو تحصل بانتظام على فشل في جمع البيانات.

المهمة ليست فقط تسجيل خطأ، بل تصنيفه: تحميل مؤقت على الخادم، حظر IP معين، حظر إقليمي أو مشكلة على جانب المحلل (رؤوس غير صحيحة، ملفات تعريف ارتباط قديمة). يعتمد نوع الخطأ على ما إذا كان يجب التخلص من البروكسي من المجموعة إلى الأبد، أو وضعه في "الحجر الصحي" لفترة، أو ببساطة إعادة المحاولة.

المقاييس الرئيسية للأخطاء للتصفية

لتمييز الفشل المؤقت عن البروكسي الميت حقًا، يجب جمع عدة مقاييس لكل IP، وليس الاعتماد على حالة HTTP واحدة. إليك مجموعة أساسية يجب تسجيلها مع كل طلب:

المقياس ماذا تشير الإجراء
HTTP 403 / 429 حظر أو حد معدل على IP حجر صحي لمدة 30-60 دقيقة، عند التكرار - حذف
مهلة الاتصال البروكسي محمّل أو ميت 3 مهلات متتالية - حذف من المجموعة
CAPTCHA في الاستجابة IP تحت الشك من قبل مكافحة الروبوتات تقليل تكرار الطلبات أو استبدال IP
وقت الاستجابة > 5 ثوانٍ تدهور السرعة على جانب البروكسي خفض الأولوية في المجموعة
حجم الاستجابة أقل من المتوسط تم إرجاع صفحة حجب/صفحة فارغة تحقق إضافي من المحتوى قبل القبول
نسبة الطلبات الناجحة (نسبة النجاح) تقييم شامل لجودة IP أقل من 70% بعد 50 طلبًا - حذف

من المهم عدم الخلط بين رموز أخطاء الموقع والأخطاء الشبكية. على سبيل المثال، 404 ليست مشكلة بروكسي، بل منتج غير موجود في Wildberries. ولكن إذا كان نفس IP يحصل بانتظام على 403 حيث يحصل الآخرون على 200 - فهذا علامة واضحة على حظر هذا العنوان بشكل محدد.

خوارزمية تقييم البروكسي

بدلاً من التصنيف الثنائي "عامل / غير عامل"، من الأسهل حساب درجة لكل IP من 0 إلى 100، والتي تنخفض تدريجياً عند حدوث الأخطاء وتستعيد نفسها عند الطلبات الناجحة. هذا يسمح بعدم التخلص من البروكسي بعد فشل عشوائي واحد (قد يكون خادم السوق قد علق لبضع ثوانٍ)، ولكن بسرعة إزالة تلك IP التي تعطي نفايات بشكل منهجي.

المنطق بسيط: الطلب الناجح برمز 200 ومحتوى صالح يضيف +2 إلى الدرجة (لكن ليس أكثر من 100). المهلة - ناقص 15. رمز 403/429 - ناقص 25. CAPTCHA في الاستجابة - ناقص 20. إذا انخفضت الدرجة تحت العتبة (على سبيل المثال، 30)، ينتقل البروكسي إلى الحجر الصحي لفترة محددة، وإذا انخفضت الدرجة مرة أخرى تحت 10 بعد العودة من الحجر الصحي - يتم حذف IP من المجموعة نهائيًا وتسجيله للتحليل.

هذا النهج مشابه لنظام السمعة في متصفحات مكافحة الكشف مثل Dolphin Anty أو AdsPower، حيث تمتلك الحسابات "صحة" تعتمد على النشاط. نفس الآلية تعمل بشكل ممتاز أيضًا لعناوين IP في التحليل.

الكود: ProxyPool مع التصفية التلقائية بلغة بايثون

أدناه - هيكل عمل لفئة لإدارة مجموعة البروكسي مع مراعاة مقاييس الأخطاء. يمكن توصيلها بأي محلل يستخدم requests أو httpx أو Scrapy، عن طريق استبدال منطق الحصول على البروكسي باستدعاء pool.get_proxy().

import time
import random
from dataclasses import dataclass, field
from typing import Optional

@dataclass
class ProxyStats:
    address: str
    score: int = 100
    quarantine_until: float = 0.0
    total_requests: int = 0
    total_success: int = 0
    consecutive_timeouts: int = 0
    is_dead: bool = False

    @property
    def success_rate(self) -> float:
        if self.total_requests == 0:
            return 1.0
        return self.total_success / self.total_requests


class ProxyPool:
    def __init__(self, proxies: list[str],
                 min_score: int = 30,
                 dead_score: int = 10,
                 quarantine_seconds: int = 1800):
        self.stats = {p: ProxyStats(address=p) for p in proxies}
        self.min_score = min_score
        self.dead_score = dead_score
        self.quarantine_seconds = quarantine_seconds

    def _is_available(self, stats: ProxyStats) -> bool:
        if stats.is_dead:
            return False
        if stats.quarantine_until > time.time():
            return False
        return True

    def get_proxy(self) -> Optional[str]:
        available = [s for s in self.stats.values() if self._is_available(s)]
        if not available:
            return None
        # نرتب حسب الدرجة، نأخذ عشوائي من أعلى 30%، لتجنب الضغط على IP واحد
        available.sort(key=lambda s: s.score, reverse=True)
        top_pool = available[:max(1, len(available) // 3)]
        return random.choice(top_pool).address

    def report_success(self, proxy: str):
        s = self.stats[proxy]
        s.total_requests += 1
        s.total_success += 1
        s.consecutive_timeouts = 0
        s.score = min(100, s.score + 2)

    def report_timeout(self, proxy: str):
        s = self.stats[proxy]
        s.total_requests += 1
        s.consecutive_timeouts += 1
        s.score -= 15
        if s.consecutive_timeouts >= 3:
            self._quarantine(s)
        self._check_dead(s)

    def report_blocked(self, proxy: str, captcha: bool = False):
        s = self.stats[proxy]
        s.total_requests += 1
        s.score -= 20 if captcha else 25
        self._quarantine(s)
        self._check_dead(s)

    def _quarantine(self, s: ProxyStats):
        if s.score < self.min_score:
            s.quarantine_until = time.time() + self.quarantine_seconds

    def _check_dead(self, s: ProxyStats):
        if s.score <= self.dead_score:
            s.is_dead = True

    def stats_report(self) -> list[dict]:
        return [
            {
                "proxy": s.address,
                "score": s.score,
                "success_rate": round(s.success_rate, 2),
                "dead": s.is_dead,
            }
            for s in self.stats.values()
        ]

في محلل حقيقي، يتم إدراج الاستدعاءات report_success وreport_timeout وreport_blocked مباشرة في معالج الاستجابة بعد كل طلب HTTP. مثال على التكامل مع requests:

import requests

def fetch(url: str, pool: ProxyPool):
    proxy = pool.get_proxy()
    if not proxy:
        raise RuntimeError("لا توجد بروكسيات متاحة في المجموعة")

    proxies = {"http": proxy, "https": proxy}
    try:
        resp = requests.get(url, proxies=proxies, timeout=8)
    except requests.exceptions.Timeout:
        pool.report_timeout(proxy)
        return None

    if resp.status_code in (403, 429):
        pool.report_blocked(proxy)
        return None

    if "captcha" in resp.text.lower():
        pool.report_blocked(proxy, captcha=True)
        return None

    pool.report_success(proxy)
    return resp

هذا النهج يخفف الحمل عن المراقبة اليدوية: تقوم بإعداد العتبات مرة واحدة، ومن ثم تتعافى المجموعة تلقائيًا، طاردة العناوين الميتة وإعادة العناوين من الحجر الصحي التي كانت معلقة مؤقتًا.

القيم الحدية لحظر IP

لا توجد أرقام عالمية - العتبات تعتمد على الموقع وعدوانية مكافحة الروبوتات الخاصة به. ولكن هناك مؤشرات تعمل بشكل جيد في الممارسة العملية عند تحليل Wildberries وOzon وAvito:

  • نسبة النجاح أقل من 70% على عينة من 30-50 طلبًا - سبب لحذف IP، حتى لو كانت الدرجة الحالية ليست حرجة بعد.
  • 3 مهلات متتالية - تعني تقريبًا أن البروكسي غير متاح فعليًا أو محمّل، لا جدوى من الاحتفاظ به في المجموعة النشطة.
  • CAPTCHA أكثر من مرة واحدة كل 20 طلبًا - IP تحت الشك من قبل مكافحة الروبوتات، من الأفضل تقليل تكرار الطلبات أو استبدال العنوان.
  • متوسط وقت الاستجابة أعلى من 2x من الوسيط في المجموعة - إشارة إلى التدهور، ليست حرجة، ولكنها سبب لتقليل الأولوية عند الاختيار.

الحجر الصحي لمدة 30-60 دقيقة عادة ما يكون كافيًا للبروكسيات السكنية ومراكز البيانات، لكي "ينسى" مكافحة الروبوتات النشاط المشبوه المؤقت. بالنسبة للبروكسيات المحمولة، الوضع مختلف: حيث يتغير IP عند تغيير الخلية من قبل المشغل، لذا يمكن جعل الحجر الصحي أقصر - 10-15 دقيقة، لأنه بعد التدوير يكون هذا عنوانًا مختلفًا فعليًا.

Healthcheck وتدوير في الخلفية

بالإضافة إلى التصفية التفاعلية (بناءً على الأخطاء في التدفق الرئيسي للتحليل)، من المفيد تشغيل healthcheck في الخلفية - عملية منفصلة تقوم كل بضع دقائق بالتحقق من البروكسي عبر طلبات خفيفة على عنوان URL محايد (مثل httpbin.org/ip) وتحديث إحصائيات المجموعة قبل أن يدخل البروكسي في طلب حقيقي إلى Wildberries أو Ozon.

import threading

def healthcheck_loop(pool: ProxyPool, interval: int = 300):
    while True:
        for proxy in list(pool.stats.keys()):
            try:
                r = requests.get(
                    "https://httpbin.org/ip",
                    proxies={"http": proxy, "https": proxy},
                    timeout=5
                )
                if r.status_code == 200:
                    pool.report_success(proxy)
                else:
                    pool.report_blocked(proxy)
            except requests.exceptions.RequestException:
                pool.report_timeout(proxy)
        time.sleep(interval)

threading.Thread(target=healthcheck_loop, args=(pool,), daemon=True).start()

هذه العملية الخلفية مهمة بشكل خاص إذا كانت المجموعة كبيرة (مئات IP) والتحليل لا يتم بشكل مستمر، بل على دفعات - بدون healthcheck، بعض البروكسيات قد "تتجاوز صلاحيتها" بين التشغيلات، وتُهدر الدقائق الأولى من التحليل في التصفية الحية، بدلاً من العمل مباشرةً مع العناوين الموثوقة.

ما هو نوع البروكسي الذي يجب اختياره لتحليل الأسواق

يؤثر اختيار نوع البروكسي بشكل مباشر على مدى تكرار الحاجة إلى تشغيل التصفية ومدى عدوانية العتبات. لتحليل مستقر لـ Wildberries وOzon، حيث تقوم مكافحة الروبوتات بتحليل الأنماط السلوكية وسمعة الشبكة الفرعية، فإن البروكسيات السكنية هي الأفضل - حيث أن IP تنتمي إلى مزودين حقيقيين للإنترنت المنزلي، ومكافحة الروبوتات تحظرها بشكل أقل بكثير على دفعات حسب الشبكة الفرعية.

بالنسبة للمصادر الأكثر بساطة وأقل حماية (مثل جمع البيانات العامة من Avito أو مراقبة الأسعار على واجهات بسيطة) غالبًا ما تكون بروكسيات مراكز البيانات كافية - فهي أسرع وأرخص، وهو أمر حاسم عند وجود كميات كبيرة من الطلبات، لكنها تُحظر قليلاً بشكل أكثر تكرارًا، لذا يجب أن تكون عتبات التصفية لها أكثر صرامة.

نوع البروكسي سرعة التصفية متى تستخدم
مركز البيانات عالية، الشبكات الفرعية تُحظر بكميات كبيرة واجهات بسيطة، كميات كبيرة، حماية منخفضة
سكنية متوسطة، IP تُحظر بشكل محدد Wildberries، Ozon، مواقع مع مكافحة الروبوتات
محمولة منخفضة، IP تتغير تلقائيًا بواسطة المشغل مصادر محمية بشدة، تحليل عالي التردد

في الممارسة العملية، تستخدم العديد من الفرق مجموعة هجينة: يتم توجيه الجزء الأكبر من الطلبات عبر IP السكنية، وعند حدوث حظر جماعي، يتم استخدام البروكسيات المحمولة كاحتياطي "طارئ"، حيث أن تغيير IP يحدث بشكل طبيعي من خلال تدوير الشبكة الخلوية ولا يتطلب تدخلًا يدويًا.

الخاتمة

مجموعة البروكسي بدون نظام تصفية تتدهور حتمًا، وكلما طالت فترة عملها بدون تنظيف، زادت الموارد التي يستهلكها المحلل بلا جدوى على IP الميتة. نموذج التقييم الذي يأخذ في الاعتبار رموز HTTP، المهلات، CAPTCHA ونسبة النجاح يسمح بالكشف التلقائي عن العناوين المشكلة والحفاظ على المجموعة في حالة عمل دون مراقبة يدوية. يمكن تعديل الكود من هذه المقالة لأي مجموعة - requests، httpx، Scrapy أو محلل غير متزامن خاص بك.

إذا كنت تجمع مجموعة لتحليل الأسواق وترغب في تقليل تكرار الحظر منذ البداية، نوصي بالنظر في البروكسيات السكنية - فهي توفر نسبة نجاح أعلى بشكل ملحوظ على المنصات المحمية مثل Wildberries وOzon مقارنة بالعناوين العادية لمراكز البيانات.