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

چگونه با استفاده از پروکسی محدودیت نرخ را دور بزنیم: معماری، چرخش IP و نمونه کد برای توسعه‌دهندگان

محدودیت نرخ، پارسر یا کلاینت API شما را مسدود می‌کند؟ بررسی می‌کنیم که چگونه می‌توان محدودیت‌های تعداد درخواست‌ها را با استفاده از پروکسی دور زد - با مثال‌های کد و راه‌حل‌های معماری.

📅۲۲ اردیبهشت ۱۴۰۵
```html

محدودیت نرخ — یکی از رایج‌ترین دلایلی است که باعث سقوط پارسرها، قطع شدن ادغام‌های API و دریافت وضعیت 429 Too Many Requests توسط اسکریپت‌های خودکار می‌شود. سرور تعداد زیادی درخواست از یک IP را مشاهده می‌کند — و به سادگی دیگر پاسخ نمی‌دهد. در این مقاله بررسی می‌کنیم که چگونه زیرساخت مناسب را با استفاده از پروکسی بسازیم تا محدودیت‌های درخواست را بدون مسدود شدن و اختلال دور بزنیم — با مثال‌های واقعی کد در Python و Node.js.

محدودیت نرخ چیست و چرا تأخیرهای معمولی کمک نمی‌کنند

محدودیت نرخ (Rate limiting) — یک مکانیزم حفاظتی برای سرور است که تعداد درخواست‌ها از یک منبع را در یک بازه زمانی مشخص محدود می‌کند. منبع معمولاً آدرس IP است، اما سیستم‌های پیشرفته همچنین توکن‌های احراز هویت، User-Agent، کوکی‌ها و حتی الگوهای رفتاری را در نظر می‌گیرند.

زمانی که اسکریپت شما از حد مجاز فراتر می‌رود، سرور یکی از پاسخ‌های زیر را برمی‌گرداند:

  • 429 Too Many Requests — وضعیت استاندارد HTTP برای محدودیت نرخ
  • 503 Service Unavailable — گاهی به جای 429 استفاده می‌شود
  • 403 Forbidden — اگر IP قبلاً در لیست سیاه قرار گرفته باشد
  • پاسخ خالی یا تایم‌اوت — در صورت مسدودسازی تهاجمی

اولین فکر اکثر توسعه‌دهندگان — اضافه کردن time.sleep(1) بین درخواست‌ها است. این فقط در شرایط بسیار ملایم (مثلاً 60 درخواست در دقیقه) کار می‌کند. اما سناریوهای واقعی پیچیده‌تر هستند:

محدودیت‌های واقعی پلتفرم‌های محبوب:

  • Twitter/X API (رایگان): 500,000 توییت در ماه، اما نه بیشتر از 15 درخواست هر 15 دقیقه
  • جستجوی گوگل: ~100 درخواست در روز از یک IP بدون احراز هویت
  • Wildberries، Ozon: محدودیت نرخ تهاجمی — مسدود شدن بعد از 30–50 درخواست در دقیقه
  • GitHub API: 60 درخواست در ساعت بدون توکن، 5000 در ساعت با توکن
  • سایت‌های محافظت‌شده توسط Cloudflare: ممکن است بعد از 10–20 درخواست در دقیقه مسدود شوند

اگر شما نیاز دارید 100,000 کارت محصول را از یک بازار جمع‌آوری کنید یا قیمت‌ها را در زمان واقعی نظارت کنید — تأخیرها به سادگی کمک نمی‌کنند. به معماری دیگری نیاز است. و در اینجا پروکسی‌ها تبدیل به یک ضرورت می‌شوند، نه یک گزینه.

مهم است که درک کنید: محدودیت نرخ به آدرس IP وابسته است. اگر شما 100 IP مختلف داشته باشید — در واقع 100 «سهم» مستقل دارید. این اصل کلیدی دور زدن محدودیت‌ها از طریق پروکسی است.

چگونه پروکسی‌ها مشکل محدودیت درخواست‌ها را حل می‌کنند

مکانیزم ساده است: هر درخواست به سرور هدف از یک آدرس IP متفاوت ارسال می‌شود. از نظر سرور — این کاربران مختلفی هستند. سهم هر یک از آنها تقریباً مصرف نمی‌شود، بنابراین مسدودسازی اتفاق نمی‌افتد.

بیایید تفاوت بین کار بدون پروکسی و با استخر پروکسی را با یک مثال خاص بررسی کنیم. فرض کنید سرور 10 درخواست در دقیقه از یک IP را مجاز می‌دارد:

سناریو درخواست‌ها در دقیقه مسدودسازی زمان برای 10,000 درخواست
یک IP، بدون پروکسی 10 بله، بعد از 10 درخواست ~16 ساعت
10 پروکسی، چرخش 100 خیر ~1.7 ساعت
100 پروکسی، چرخش 1000 خیر ~10 دقیقه

علاوه بر مقیاس‌پذیری پهنای باند، پروکسی‌ها چندین مزیت دیگر در کار با محدودیت نرخ ارائه می‌دهند:

  • جداسازی جلسات — اگر یک IP مسدود شود، بقیه به کار خود ادامه می‌دهند
  • توزیع جغرافیایی — درخواست‌ها از مناطق مختلف ارسال می‌شوند، که مشکوک بودن را کاهش می‌دهد
  • جلسات چسبنده — امکان «چسبیدن» به یک IP برای سناریوهای چند مرحله‌ای (احراز هویت + عمل)
  • کنترل بار — می‌توان درخواست‌ها را به طور دقیق برای هر IP دوزیابی کرد، بدون اینکه از حد مجاز فراتر رود

کدام نوع پروکسی را برای کار خود انتخاب کنیم

همه پروکسی‌ها به یک اندازه در برابر محدودیت نرخ مؤثر نیستند. انتخاب نوع به وب‌سایت هدف، حجم درخواست‌ها و بودجه بستگی دارد. سه نوع اصلی را بررسی می‌کنیم:

پروکسی‌های مسکونی

این آدرس‌های IP کاربران واقعی خانگی هستند. آنها به عنوان ترافیک اینترنتی معمولی به نظر می‌رسند و به ندرت تحت مسدودسازی قرار می‌گیرند. پروکسی‌های مسکونی — انتخاب بهینه برای وب‌سایت‌های با حفاظت تهاجمی: بازارها (Wildberries، Ozon)، شبکه‌های اجتماعی، منابع محافظت‌شده توسط Cloudflare. نقطه ضعف اصلی — قیمت بالاتر نسبت به پروکسی‌های مرکز داده.

پروکسی‌های موبایل

آدرس‌های IP اپراتورهای موبایل (3G/4G/5G). ویژگی آنها این است که یک IP می‌تواند به طور همزمان توسط هزاران مشترک واقعی استفاده شود، بنابراین مسدود کردن چنین آدرسی برای وب‌سایت‌ها بسیار نامطلوب است. پروکسی‌های موبایل بهترین نتایج را در جاهایی که پروکسی‌های مسکونی شروع به مسدود شدن می‌کنند نشان می‌دهند — مثلاً در پارس کردن با فرکانس بالا در Instagram یا کار با API پلتفرم‌هایی که نوع اتصال را تجزیه و تحلیل می‌کنند.

پروکسی‌های مرکز داده

IPهای سریع و ارزان از مرکز داده‌های سرور. آنها برای پارس کردن وب‌سایت‌ها بدون حفاظت جدی ایده‌آل هستند: APIهای باز، تجمیع‌کننده‌های خبری، پایگاه‌های داده عمومی. برای کارهایی با محدودیت نرخ به تعداد بیشتری از آنها نیاز است (زیرا آنها بیشتر در لیست‌های سیاه قرار می‌گیرند)، اما با چرخش مناسب، آنها به خوبی با حجم‌های بزرگ درخواست‌ها کنار می‌آیند. اطلاعات بیشتر — در صفحه پروکسی‌های مرکز داده.

نوع پروکسی ناشناس بودن سرعت قیمت بهترین سناریو
مسکونی بسیار بالا متوسط $$ بازارها، شبکه‌های اجتماعی، Cloudflare
موبایل حداکثر متوسط $$$ API اینستاگرام، پارس کردن با فرکانس بالا
مرکز داده متوسط بالا $ APIهای باز، داده‌های عمومی

استراتژی‌های چرخش IP: per-request، sticky sessions، round-robin

خود وجود پروکسی هنوز مشکل را حل نمی‌کند — مهم است که به درستی آنها را مدیریت کنید. چندین استراتژی چرخش وجود دارد که هر کدام برای سناریوهای خاص خود مناسب است.

چرخش per-request (IP جدید برای هر درخواست)

هر درخواست HTTP از طریق یک آدرس IP جدید ارسال می‌شود. این تهاجمی‌ترین استراتژی برای دور زدن محدودیت نرخ است — سرور به طور فیزیکی نمی‌تواند شمارنده را برای یک IP جمع‌آوری کند. مناسب برای:

  • پارس کردن کارت‌های محصول (هر کارت — یک درخواست جداگانه)
  • جمع‌آوری داده‌ها از موتورهای جستجو
  • هر نوع درخواست stateless که نیاز به جلسه ندارد

جلسات چسبنده (IP ثابت برای جلسه)

یک IP در طول کل جلسه (معمولاً 1–30 دقیقه) استفاده می‌شود. این برای سناریوهایی که نیاز به احراز هویت دارند حیاتی است: وارد شدن به حساب، انجام عمل، خارج شدن. اگر IP بین مراحل تغییر کند — سرور ممکن است جلسه را به عنوان مشکوک مسدود کند.

Round-robin با محدودیت درخواست‌ها بر روی IP

دقیق‌ترین استراتژی. شما محدودیت سرور را می‌دانید (مثلاً 10 درخواست در دقیقه) و درخواست‌ها را بین استخر پروکسی توزیع می‌کنید به طوری که هر IP هرگز از این آستانه فراتر نرود. نیاز به پیاده‌سازی صف با در نظر گرفتن زمان آخرین درخواست برای هر IP دارد.

فرمول محاسبه تعداد پروکسی‌های مورد نیاز:

N پروکسی = (سرعت هدف درخواست‌ها در دقیقه) ÷ (محدودیت سرور در دقیقه بر روی IP)
مثال: نیاز به 500 درخواست در دقیقه، محدودیت سرور — 10 در دقیقه → حداقل 50 پروکسی نیاز است. 20% اضافی برای موارد مسدودسازی اضافه کنید: در مجموع 60 پروکسی.

نمونه‌های کد در Python: requests، aiohttp، Scrapy

به عمل می‌رویم. در زیر — الگوهای آماده برای سه ابزار محبوب Python.

1. requests + چرخش پروکسی به صورت دستی

ساده‌ترین گزینه — لیست پروکسی و انتخاب تصادفی برای هر درخواست:

import requests
import random
import time

PROXIES = [
    "http://user:[email protected]:8080",
    "http://user:[email protected]:8080",
    "http://user:[email protected]:8080",
    # ... تعداد مورد نیاز را اضافه کنید
]

def get_random_proxy():
    proxy = random.choice(PROXIES)
    return {"http": proxy, "https": proxy}

def fetch_with_retry(url, max_retries=3):
    for attempt in range(max_retries):
        proxy = get_random_proxy()
        try:
            response = requests.get(
                url,
                proxies=proxy,
                timeout=10,
                headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"}
            )
            if response.status_code == 429:
                print(f"محدودیت نرخ در {proxy}، تغییر پروکسی...")
                time.sleep(1)
                continue
            return response
        except requests.RequestException as e:
            print(f"تلاش {attempt+1} ناموفق: {e}")
            time.sleep(2)
    return None

# استفاده
urls = ["https://example.com/item/1", "https://example.com/item/2"]
for url in urls:
    result = fetch_with_retry(url)
    if result:
        print(f"OK: {url} — {len(result.text)} بایت")

2. استخر پروکسی هوشمند با در نظر گرفتن محدودیت نرخ

گزینه پیشرفته‌تر — کلاس ProxyPool که زمان آخرین استفاده از هر IP را ردیابی می‌کند و از محدودیت تعیین‌شده فراتر نمی‌رود:

import requests
import time
from collections import defaultdict
from threading import Lock

class ProxyPool:
    def __init__(self, proxies, rate_limit=10, window=60):
        """
        proxies: لیست رشته‌هایی از نوع 'http://user:pass@host:port'
        rate_limit: حداکثر درخواست‌ها از یک IP در هر window ثانیه
        window: بازه زمانی به ثانیه
        """
        self.proxies = proxies
        self.rate_limit = rate_limit
        self.window = window
        self.usage = defaultdict(list)  # پروکسی -> [timestamps]
        self.lock = Lock()

    def get_available_proxy(self):
        now = time.time()
        with self.lock:
            for proxy in self.proxies:
                # برچسب‌های قدیمی را پاک می‌کنیم
                self.usage[proxy] = [
                    t for t in self.usage[proxy]
                    if now - t < self.window
                ]
                if len(self.usage[proxy]) < self.rate_limit:
                    self.usage[proxy].append(now)
                    return {"http": proxy, "https": proxy}
        return None  # همه پروکسی‌ها محدودیت را تمام کرده‌اند

    def fetch(self, url, **kwargs):
        proxy = self.get_available_proxy()
        if proxy is None:
            print("همه پروکسی‌ها محدود شده‌اند، منتظر می‌مانیم...")
            time.sleep(5)
            return self.fetch(url, **kwargs)
        
        try:
            response = requests.get(url, proxies=proxy, timeout=10, **kwargs)
            return response
        except requests.RequestException as e:
            print(f"درخواست ناموفق: {e}")
            return None

# استفاده
pool = ProxyPool(
    proxies=[
        "http://user:[email protected]:8080",
        "http://user:[email protected]:8080",
    ],
    rate_limit=10,  # 10 درخواست در دقیقه بر روی IP
    window=60
)

for i in range(100):
    r = pool.fetch(f"https://example.com/page/{i}")
    if r:
        print(f"صفحه {i}: {r.status_code}")

3. aiohttp برای پارس کردن غیرهمزمان

رویکرد غیرهمزمان اجازه می‌دهد که به طور موازی از ده‌ها پروکسی بدون مسدودسازی رشته‌ها استفاده کنید:

import asyncio
import aiohttp
import itertools

PROXIES = [
    "http://user:[email protected]:8080",
    "http://user:[email protected]:8080",
    "http://user:[email protected]:8080",
]

proxy_cycle = itertools.cycle(PROXIES)

async def fetch(session, url, proxy):
    try:
        async with session.get(
            url,
            proxy=proxy,
            timeout=aiohttp.ClientTimeout(total=10)
        ) as response:
            if response.status == 429:
                await asyncio.sleep(2)
                return None
            return await response.text()
    except Exception as e:
        print(f"خطا: {e}")
        return None

async def main(urls):
    connector = aiohttp.TCPConnector(limit=50)
    async with aiohttp.ClientSession(connector=connector) as session:
        tasks = [
            fetch(session, url, next(proxy_cycle))
            for url in urls
        ]
        results = await asyncio.gather(*tasks)
        return results

urls = [f"https://example.com/item/{i}" for i in range(200)]
results = asyncio.run(main(urls))
print(f"جمع‌آوری شده: {sum(1 for r in results if r is not None)} صفحه")

4. Scrapy با چرخش از طریق middleware

برای Scrapy یک راه‌حل آماده وجود دارد — scrapy-rotating-proxies. اما می‌توانید middleware خود را بنویسید:

# middlewares.py
import random

class RotatingProxyMiddleware:
    def __init__(self, proxies):
        self.proxies = proxies

    @classmethod
    def from_crawler(cls, crawler):
        return cls(proxies=crawler.settings.getlist("PROXY_LIST"))

    def process_request(self, request, spider):
        proxy = random.choice(self.proxies)
        request.meta["proxy"] = proxy

    def process_response(self, request, response, spider):
        if response.status == 429:
            spider.logger.warning(f"محدودیت نرخ، پروکسی: {request.meta.get('proxy')}")
            # می‌توانید منطق استثنای پروکسی مشکل‌دار را اضافه کنید
        return response

# settings.py
PROXY_LIST = [
    "http://user:[email protected]:8080",
    "http://user:[email protected]:8080",
]
DOWNLOADER_MIDDLEWARES = {
    "myproject.middlewares.RotatingProxyMiddleware": 350,
}
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_TARGET_CONCURRENCY = 10

نمونه‌های کد در Node.js: axios، got، Puppeteer

Node.js — انتخاب محبوب برای اتوماسیون مرورگرها و کار با API. در اینجا الگوهای آماده برای کار با پروکسی آورده شده است.

1. axios با چرخش پروکسی

const axios = require('axios');
const { HttpsProxyAgent } = require('https-proxy-agent');

const proxies = [
  'http://user:[email protected]:8080',
  'http://user:[email protected]:8080',
  'http://user:[email protected]:8080',
];

let proxyIndex = 0;

function getNextProxy() {
  const proxy = proxies[proxyIndex % proxies.length];
  proxyIndex++;
  return proxy;
}

async function fetchWithProxy(url, retries = 3) {
  for (let i = 0; i < retries; i++) {
    const proxyUrl = getNextProxy();
    const agent = new HttpsProxyAgent(proxyUrl);
    
    try {
      const response = await axios.get(url, {
        httpsAgent: agent,
        httpAgent: agent,
        timeout: 10000,
        headers: {
          'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
        },
      });
      return response.data;
    } catch (error) {
      if (error.response?.status === 429) {
        console.log(`محدودیت نرخ، تغییر پروکسی...`);
        await new Promise(r => setTimeout(r, 1000));
        continue;
      }
      console.error(`تلاش ${i + 1} ناموفق:`, error.message);
    }
  }
  return null;
}

// استفاده
(async () => {
  const urls = Array.from({length: 50}, (_, i) => `https://example.com/item/${i}`);
  
  const results = await Promise.allSettled(
    urls.map(url => fetchWithProxy(url))
  );
  
  const successful = results.filter(r => r.status === 'fulfilled' && r.value).length;
  console.log(`موفق: ${successful}/${urls.length}`);
})();

2. Puppeteer با پروکسی و دور زدن محدودیت نرخ

برای وب‌سایت‌هایی که رندرینگ JavaScript و حفاظت Cloudflare دارند، به یک مرورگر headless نیاز است:

const puppeteer = require('puppeteer');

const proxies = [
  'proxy1.example.com:8080',
  'proxy2.example.com:8080',
];

async function scrapeWithProxy(url, proxyHost) {
  const browser = await puppeteer.launch({
    args: [
      `--proxy-server=${proxyHost}`,
      '--no-sandbox',
      '--disable-setuid-sandbox',
    ],
    headless: true,
  });

  const page = await browser.newPage();
  
  // احراز هویت پروکسی
  await page.authenticate({
    username: 'user',
    password: 'pass',
  });

  // تنظیم User-Agent واقعی
  await page.setUserAgent(
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
  );

  try {
    await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
    
    // بررسی محدودیت نرخ
    const status = await page.evaluate(() => document.title);
    if (status.includes('429') || status.includes('Too Many')) {
      console.log('محدودیت نرخ، نیاز به تغییر پروکسی');
      return null;
    }
    
    const data = await page.evaluate(() => {
      return document.querySelector('.price')?.textContent || null;
    });
    
    return data;
  } finally {
    await browser.close();
  }
}

// چرخش بر اساس وظایف
(async () => {
  const urls = ['https://example.com/product/1', 'https://example.com/product/2'];
  
  for (let i = 0; i < urls.length; i++) {
    const proxy = proxies[i % proxies.length];
    const result = await scrapeWithProxy(urls[i], proxy);
    console.log(`${urls[i]}: ${result}`);
    await new Promise(r => setTimeout(r, 500)); // تأخیر کوچک
  }
})();

تکنیک‌های پیشرفته: هدرها، fingerprint، دور زدن Cloudflare

تغییر IP — یک شرط لازم است، اما همیشه کافی نیست. سیستم‌های حفاظتی مدرن ده‌ها پارامتر درخواست را تجزیه و تحلیل می‌کنند. بیایید بررسی کنیم که چه چیزهای دیگری باید در نظر گرفته شود.

هدرهای HTTP: حداقل مجموعه الزامی

درخواست بدون هدرهای مناسب حتی با تغییر IP به عنوان ربات به نظر می‌رسد. همیشه اضافه کنید:

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
    "Accept-Language": "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7",
    "Accept-Encoding": "gzip, deflate, br",
    "Connection": "keep-alive",
    "Upgrade-Insecure-Requests": "1",
    "Sec-Fetch-Dest": "document",
    "Sec-Fetch-Mode": "navigate",
    "Sec-Fetch-Site": "none",
    "Cache-Control": "max-age=0",
}

پردازش هدر Retry-After

در پاسخ 429، سرور اغلب مشخص می‌کند که چقدر باید منتظر بمانید. پردازش صحیح این هدر اجازه می‌دهد که درخواست‌ها را بیهوده هدر ندهید:

def handle_rate_limit(response):
    if response.status_code == 429:
        retry_after = response.headers.get("Retry-After")
        if retry_after:
            wait_time = int(retry_after)
            print(f"محدودیت نرخ. منتظر {wait_time} ثانیه...")
            time.sleep(wait_time + 1)  # +1 ثانیه بافر
        else:
            # تأخیر نمایی اگر هدر وجود نداشته باشد
            time.sleep(min(2 ** attempt, 60))
        return True
    return False

TLS fingerprinting و چگونگی دور زدن آن

سیستم‌های پیشرفته (Cloudflare، Akamai، PerimeterX) TLS fingerprint — «اثر انگشت» منحصر به فرد اتصال TLS شما را تجزیه و تحلیل می‌کنند. کتابخانه استاندارد requests دارای اثر انگشت قابل شناسایی آسان است. راه‌حل‌ها:

  • curl_cffi (Python) — اثر انگشت Chrome/Firefox را در سطح TLS شبیه‌سازی می‌کند
  • tls-client (Go/Python) — ابزاری مشابه با پشتیبانی از پروفایل‌های مختلف مرورگر
  • Playwright/Puppeteer — مرورگر واقعی، اثر انگشت ایده‌آل به طور پیش‌فرض
# pip install curl-cffi
from curl_cffi import requests as cffi_requests

response = cffi_requests.get(
    "https://cloudflare-protected-site.com/api/data",
    impersonate="chrome120",  # شبیه‌سازی Chrome 120
    proxies={"https": "http://user:[email protected]:8080"}
)
print(response.json())

مدیریت کوکی‌ها و جلسات

اگر وب‌سایت از کوکی‌ها برای ردیابی جلسات استفاده کند، تغییر IP بدون تغییر کوکی‌ها بی‌فایده است. هنگام تغییر پروکسی، همیشه یک جلسه جدید ایجاد کنید:

import requests

def create_fresh_session(proxy_url):
    """ایجاد یک جلسه جدید با کوکی‌های تمیز برای هر پروکسی"""
    session = requests.Session()
    session.proxies = {"http": proxy_url, "https": proxy_url}
    session.headers.update({
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
    })
    # کوکی‌ها از جلسه قبلی منتقل نمی‌شوند
    return session

# برای هر IP جدید — یک جلسه جدید
for proxy in proxies:
    session = create_fresh_session(proxy)
    response = session.get("https://example.com/protected-page")
    # پردازش پاسخ...

اشتباهات رایج در کار با پروکسی و محدودیت نرخ

حتی با پروکسی‌های به درستی تنظیم‌شده، توسعه‌دهندگان به طور مرتب بر روی همان مشکلات می‌افتند. در اینجا رایج‌ترین اشتباهات و راه‌های جلوگیری از آنها آورده شده است.

چک‌لیست: چه چیزی را قبل از راه‌اندازی پارسر بررسی کنیم

  • ☐ هدرهای HTTP واقعی (User-Agent، Accept، Accept-Language) اضافه شده‌اند
  • ☐ هنگام تغییر پروکسی، یک جلسه جدید ایجاد می‌شود (کوکی‌های جدید)
  • ☐ وضعیت‌های 429، 503، 403 با منطق retry پردازش می‌شوند
  • ☐ تأخیر بین درخواست‌ها پیاده‌سازی شده است (حداقل 100–500 میلی‌ثانیه)
  • ☐ تعداد پروکسی‌ها با سرعت هدف درخواست‌ها مطابقت دارد
  • ☐ عملکرد پروکسی‌ها قبل از شروع بررسی شده است (بررسی سلامت)
  • ☐ خطاها و آمار مربوط به هر پروکسی ثبت می‌شوند
  • ☐ زمان‌سنجی برای درخواست‌ها تنظیم شده است (بیش از 15–30 ثانیه نباشد)

خطا 1: استفاده از پروکسی‌های «مرده»

همیشه پروکسی‌ها را قبل از افزودن به استخر و به طور دوره‌ای در حین کار بررسی کنید. یک پروکسی غیرکاربردی در حلقه — درخواست‌ها و تایم‌اوت‌های از دست رفته است:

def check_proxy(proxy_url, test_url="https://httpbin.org/ip", timeout=5):
    try:
        r = requests.get(
            test_url,
            proxies={"http": proxy_url, "https": proxy_url},
            timeout=timeout
        )
        return r.status_code == 200
    except:
        return False

# فیلتر کردن پروکسی‌های کارآمد قبل از شروع
working_proxies = [p for p in PROXIES if check_proxy(p)]
print(f"پروکسی‌های کارآمد: {len(working_proxies)}/{len(PROXIES)}")

خطا 2: نادیده گرفتن نوع پروتکل

پروکسی‌های HTTP نمی‌توانند ترافیک HTTPS را به طور مستقیم پروکسی کنند (فقط از طریق CONNECT). پروکسی‌های SOCKS5 در سطح حمل و نقل کار می‌کنند و از هر پروتکلی پشتیبانی می‌کنند. برای اکثر وب‌سایت‌های مدرن از پروکسی‌های SOCKS5 یا HTTPS استفاده کنید:

# پروکسی SOCKS5 در requests (نیاز به pip install requests[socks])
proxies = {
    "http": "socks5://user:[email protected]:1080",
    "https": "socks5://user:[email protected]:1080",
}

# پروکسی HTTPS
proxies = {
    "http": "https://user:[email protected]:8080",
    "https": "https://user:[email protected]:8080",
}

خطا 3: عدم وجود backoff نمایی

اگر بعد از 429 بلافاصله درخواست را تکرار کنید — فقط وضعیت را بدتر می‌کنید. استراتژی صحیح — تأخیر نمایی با jitter (انحراف تصادفی):

import random

def exponential_backoff(attempt, base=1, max_wait=60):
    """
    attempt: شماره تلاش (از 0 شروع می‌شود)
    base: تأخیر پایه به ثانیه
    max_wait: حداکثر تأخیر
    """
    wait = min(base * (2 ** attempt), max_wait)
    # jitter ±25% برای جلوگیری از thundering herd
    jitter = wait * 0.25 * random.uniform(-1, 1)
    return wait + jitter

# استفاده در منطق retry
for attempt in range(5):
    response = requests.get(url, proxies=proxy)
    if response.status_code == 429:
        wait = exponential_backoff(attempt)
        print(f"محدودیت نرخ. منتظر {wait:.1f}s (تلاش {attempt+1})")
        time.sleep(wait)
    else:
        break

خطا 4: یک رشته برای همه پروکسی‌ها

اگر شما 50 پروکسی دارید، اما یک رشته اجرایی — شما حداکثر از 1 پروکسی به طور همزمان استفاده می‌کنید. از ThreadPoolExecutor یا رویکرد غیرهمزمان برای استفاده موازی از کل استخر استفاده کنید:

from concurrent.futures import ThreadPoolExecutor, as_completed

def fetch_url(args):
    url, proxy = args
    try:
        r = requests.get(url, proxies={"https": proxy}, timeout=10)
        return url, r.status_code, len(r.text)
    except Exception as e:
        return url, None, str(e)

# به طور موازی از همه پروکسی‌ها استفاده کنید
tasks = [(url, proxies[i % len(proxies)]) for i, url in enumerate(urls)]

with ThreadPoolExecutor(max_workers=len(proxies)) as executor:
    futures = {executor.submit(fetch_url, task): task for task in tasks}
    for future in as_completed(futures):
        url, status, size = future.result()
        print(f"{url}: {status} ({size})")

نتیجه‌گیری و توصیه‌ها

محدودیت نرخ — یک مشکل قابل حل است، اگر به آن به طور سیستماتیک نزدیک شوید. نکات کلیدی از این راهنما:

  • استخر پروکسی، نه یک پروکسی — حداقل واحد برای کار جدی. تعداد پروکسی‌ها بر اساس فرمول تعیین می‌شود: سرعت هدف ÷ محدودیت سرور بر روی IP.
  • استراتژی چرخش مهم است — per-request برای درخواست‌های stateless، sticky sessions برای سناریوهای احراز هویت.
  • IP — تنها پارامتر نیست — هدرها، کوکی‌ها، TLS fingerprint و الگوهای رفتاری نیز توسط سیستم‌های حفاظتی تجزیه و تحلیل می‌شوند.
  • 429 را به درستی پردازش کنید — backoff نمایی، هدر Retry-After، تغییر پروکسی در صورت مسدودسازی.
  • نوع پروکسی به هدف بستگی دارد — پروکسی‌های مرکز داده برای APIهای باز، پروکسی‌های مسکونی برای بازارها، پروکسی‌های موبایل برای حداکثر حفاظت.

اگر شما با پارس کردن بازارها (Wildberries، Ozon)، جمع‌آوری داده‌ها از APIهای محافظت‌شده یا اتوماسیون با سرعت‌های بالا کار می‌کنید — توصیه می‌کنیم با پروکسی‌های مسکونی شروع کنید: آنها تعادل بهینه‌ای بین ناشناس بودن و سرعت فراهم می‌کنند و آدرس‌های IP آنها به ندرت در لیست‌های سیاه قرار می‌گیرند. برای کارهایی که نیاز به حداکثر پایداری در برابر مسدودسازی با فرکانس بالا دارند، باید پروکسی‌های موبایل را در نظر بگیرید — IPهای آنها توسط هزاران کاربر واقعی به اشتراک گذاشته می‌شود، که مسدودسازی را برای هر وب‌سایتی بسیار نامطلوب می‌کند.

```