← بازگشت به وبلاگ

حذف پروکسی‌های نامناسب از مجموعه پارسر: معیارهای خطا و چرخش در پایتون

بررسی می‌کنیم که کدام معیارهای خطا نشان‌دهنده کاهش کیفیت پروکسی در مجموعه پارسر هستند و چگونه یک سیستم خودکار برای حذف و چرخش 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 دریافت کند در حالی که IP‌های دیگر 200 دریافت می‌کنند — این یک نشانه واضح از مسدود شدن نقطه‌ای همین آدرس است.

الگوریتم امتیازدهی پروکسی

به جای «کارآمد / غیرکارآمد» باینری، بهتر است برای هر IP امتیازی از 0 تا 100 محاسبه کنید که به تدریج در هنگام خطاها کاهش می‌یابد و در هنگام درخواست‌های موفق بازمی‌گردد. این امکان را می‌دهد که پروکسی را پس از یک اختلال تصادفی حذف نکنید (سرور بازار ممکن است به سادگی برای یک ثانیه قفل شده باشد)، اما به سرعت IP‌هایی که به طور سیستماتیک زباله ارسال می‌کنند را حذف کنید.

منطق ساده است: درخواست موفق با کد 200 و محتوای معتبر +2 به امتیاز اضافه می‌کند (اما بالاتر از 100 نمی‌رود). تایم‌اوت — منفی 15. کد 403/429 — منفی 25. CAPTCHA در پاسخ — منفی 20. اگر امتیاز زیر آستانه (به عنوان مثال، 30) بیفتد، پروکسی به مدت زمان مشخصی به قرنطینه می‌رود و اگر پس از بازگشت از قرنطینه، امتیاز دوباره زیر 10 بیفتد — IP به طور دائم از استخر حذف می‌شود و برای تحلیل ثبت می‌شود.

این رویکرد شبیه به سیستم شهرت در مرورگرهای ضد شناسایی مانند Dolphin Anty یا AdsPower است، جایی که حساب‌ها «سلامت» دارند که به فعالیت بستگی دارد. همین مکانیک به خوبی برای آدرس‌های IP در پارسینگ کار می‌کند.

کد: ProxyPool با حذف خودکار به زبان Python

در زیر — چارچوب کاری کلاس برای مدیریت استخر پروکسی با در نظر گرفتن معیارهای خطا. می‌توان آن را به هر پارسری که از 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 بیشتر از 1 بار در 20 درخواست — IP در حال حاضر تحت مظنون ضد ربات قرار دارد، بهتر است فرکانس درخواست‌ها را کاهش دهید یا آدرس را جایگزین کنید.
  • زمان متوسط پاسخ بالاتر از 2 برابر میانه استخر — سیگنالی از کاهش کیفیت، بحرانی نیست، اما دلیلی برای کاهش اولویت در انتخاب است.

قرنطینه به مدت 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 در مقایسه با آدرس‌های معمولی دیتاسنتری ارائه می‌دهند.