Rozdrapywanie wartości granicznych

Jak ograniczenia stawek działają, jak strony wykrywają scrappers, i praktyczne strategie, aby pozostać w granicach. Obejmuje adaptacyjny kod przepustnicy i rozproszone wzorce ograniczające szybkość.

Rozdrapywanie wartości granicznych

Co to są Granice Stopniowe?

Oceń limity są niewidzialne ściany, które strony internetowe budować, aby kontrolować jak szybko każdy klient może złożyć wnioski. Gdy zbyt agresywnie drapiesz stronę, uderzasz w te ściany, a konsekwencje sięgają od tymczasowych spowolnień po stałe zakazy IP. Zrozumienie, jak działają ograniczenia stóp procentowych, jak cię wykrywają i jak pozostać pod nimi, ma zasadnicze znaczenie dla budowania skrobaków, które dostarczają wiarygodnych danych.

Ten przewodnik wyjaśnia mechanikę za ograniczeniem prędkości, sygnały detekcji stron internetowych używać, i praktyczne strategie adaptive throttling, które utrzymują Twoje scrapers działa sprawnie.

Aby uzyskać szerszy przegląd drapania z proxy, zobacz nasz Kompletny przewodnik do Web Scraping Proxies. Do unikania bloków w ogóle, czytaj Jak scalić strony internetowe bez blokowania.

Jak Rate Limiting Works

Strony internetowe wdrażają limity szybkości na wielu warstwach, z których każda ma inną ziarnistość wykrywania:

Warstwa 1: Limity stawki IP-

Najczęstsze podejście. Serwer śledzi żądania na adres IP w oknie czasowym. Przekrocz próg i otrzymasz odpowiedzi HTTP 429 (Zbyt wiele żądań) lub 503.

# Typical rate limit behavior
Request 1-50:    HTTP 200 (normal)
Request 51:      HTTP 429 (rate limited)
Wait 60 seconds...
Request 52:      HTTP 200 (reset)

Warstwa 2: Limity oparte na sesjach / ciasteczkach

Ślady żądają częstotliwości na sesję lub cookie przeglądarki. Nawet jeśli obrócisz IP, ten sam token sesji uderzając serwer szybko uruchomi ograniczenia.

Warstwa 3: Granice oparte na rachunkach

Dla stron wymagających logowania, limity są powiązane z kontem użytkownika niezależnie od IP. Wspólne dla platform API i SaaS.

Warstwa 4: Analiza behawioralna

Zaawansowane systemy, takie jak Cloudflare, PerimeterX i Akamai analizują wzorce behawioralne: czas oczekiwania, przepływ nawigacji, ruchy myszy (w kontekstach przeglądarki). Warstwa ta jest najtrudniejsza do obejścia, ponieważ nie polega na prostych licznikach.

Wspólne sygnały wykrywania wartości granicznych

Strony internetowe wykorzystują jednocześnie wiele sygnałów do wykrywania zautomatyzowanego skrobania:

SygnałCo wykrywaTrudności z unikaniem
Wnioski na IP na minutęPrędkość surowcaŁatwe (używać proxy)
Wnioski dotyczące OD na godzinę / dzieńTrwała objętośćŚrednia (obrót IP)
Żądanie regularności w czasiePrzedziały maszynoweŚredni (dodać jitter)
Brak / niewłaściwe nagłówkiKlienci niebędący przeglądarkamiŁatwe (ustawić właściwe nagłówki)
Sekwencyjne wzory URLSystematyczne pełzanieŚrednia (kolejność losowa)
Odcisk palca TLSBiblioteka vs przeglądarkaTwarda (użyj prawdziwych przeglądarek)
Wykonanie JavaScriptPrzeglądarka bez głowyTwarda (zaawansowana konfiguracja)
Wydarzenia myszy / klawiaturyZachowanie botBardzo trudne

Dowiedz się więcej o mechanizmach wykrywania w naszym przewodniku Jak systemy Anti- Bot wykrywają efekty.

Kod odpowiedzi HTTP, który ogranicza szybkość sygnału

Wiedząc, które kody HTTP wskazują ograniczenie szybkości pomaga zbudować właściwą logikę ponownego testowania:

KodZnaczenieDziałanie
200 (z CAPTCHA)Miękki blok - strona wyzwanie serwowaneObróć IP, zwolnij
403 ZakazaneIP lub sesja zablokowanaObróć IP natychmiast
429 Zbyt wiele żądańWyrażone ograniczenie prędkościPoczekaj i spróbuj ponownie z cofnięciem
503 Usługi niedostępnePrzeciążenie lub blok serweraBackoff, sprawdź, czy zablokowane
302 / 307 do adresu CAPTCHAWyzwanie przekierowaćObróć IP, zmniejsz prędkość

Strategia 1: Prawidłowe przeciążenie

Najprostsze podejście - utrzymać swój wskaźnik zapotrzebowania znacznie poniżej tego, co pozwala cel. Oznacza to mniej awarii, mniej marnotrawstwa przepustowości i bardziej zrównoważone skrobanie.

import requests
import time
import random
PROXY = "http://USERNAME:PASSWORD@gate.proxyhat.com:8080"
def respectful_scrape(urls: list[str], rpm_limit: int = 10) -> list[str]:
    """Scrape URLs while respecting a requests-per-minute limit."""
    delay = 60.0 / rpm_limit
    results = []
    for url in urls:
        try:
            resp = requests.get(
                url,
                proxies={"http": PROXY, "https": PROXY},
                timeout=30
            )
            results.append(resp.text if resp.status_code == 200 else None)
        except requests.RequestException:
            results.append(None)
        # Add delay with random jitter (±30%) to look less robotic
        jitter = delay * random.uniform(0.7, 1.3)
        time.sleep(jitter)
    return results

Strategia 2: Próg adaptacyjny

Zamiast stałej stawki, dynamicznie dostosowuj prędkość w oparciu o otrzymane odpowiedzi. Przyspiesz, gdy wszystko działa, zwolnij, gdy zobaczysz znaki ostrzegawcze.

Wdrażanie Pythona

import requests
import time
import random
from dataclasses import dataclass, field
PROXY = "http://USERNAME:PASSWORD@gate.proxyhat.com:8080"
@dataclass
class AdaptiveThrottle:
    """Automatically adjusts request rate based on server responses."""
    base_delay: float = 2.0      # seconds between requests
    min_delay: float = 0.5
    max_delay: float = 30.0
    current_delay: float = 2.0
    success_streak: int = 0
    warning_codes: set = field(default_factory=lambda: {429, 403, 503})
    def on_success(self):
        self.success_streak += 1
        # Speed up after 10 consecutive successes
        if self.success_streak >= 10:
            self.current_delay = max(self.current_delay * 0.85, self.min_delay)
            self.success_streak = 0
    def on_rate_limit(self):
        self.success_streak = 0
        # Double the delay on rate limit
        self.current_delay = min(self.current_delay * 2.0, self.max_delay)
    def on_block(self):
        self.success_streak = 0
        # Aggressive backoff on block
        self.current_delay = min(self.current_delay * 3.0, self.max_delay)
    def wait(self):
        jitter = self.current_delay * random.uniform(0.7, 1.3)
        time.sleep(jitter)
def scrape_adaptive(urls: list[str]) -> list[dict]:
    throttle = AdaptiveThrottle()
    results = []
    for url in urls:
        try:
            resp = requests.get(
                url,
                proxies={"http": PROXY, "https": PROXY},
                timeout=30
            )
            if resp.status_code == 200:
                throttle.on_success()
                results.append({"url": url, "status": 200, "body": resp.text})
            elif resp.status_code == 429:
                throttle.on_rate_limit()
                # Check Retry-After header
                retry_after = int(resp.headers.get("Retry-After", 0))
                if retry_after:
                    time.sleep(retry_after)
                results.append({"url": url, "status": 429, "body": None})
            elif resp.status_code == 403:
                throttle.on_block()
                results.append({"url": url, "status": 403, "body": None})
            else:
                results.append({"url": url, "status": resp.status_code, "body": resp.text})
        except requests.RequestException as e:
            throttle.on_block()
            results.append({"url": url, "status": 0, "error": str(e)})
        throttle.wait()
        print(f"Current delay: {throttle.current_delay:.1f}s")
    return results

Wdrażanie Node.js

const HttpsProxyAgent = require('https-proxy-agent');
const fetch = require('node-fetch');
class AdaptiveThrottle {
  constructor() {
    this.currentDelay = 2000; // ms
    this.minDelay = 500;
    this.maxDelay = 30000;
    this.successStreak = 0;
  }
  onSuccess() {
    this.successStreak++;
    if (this.successStreak >= 10) {
      this.currentDelay = Math.max(this.currentDelay * 0.85, this.minDelay);
      this.successStreak = 0;
    }
  }
  onRateLimit() {
    this.successStreak = 0;
    this.currentDelay = Math.min(this.currentDelay * 2, this.maxDelay);
  }
  onBlock() {
    this.successStreak = 0;
    this.currentDelay = Math.min(this.currentDelay * 3, this.maxDelay);
  }
  async wait() {
    const jitter = this.currentDelay * (0.7 + Math.random() * 0.6);
    return new Promise(resolve => setTimeout(resolve, jitter));
  }
}
async function scrapeAdaptive(urls) {
  const throttle = new AdaptiveThrottle();
  const agent = new HttpsProxyAgent('http://USERNAME:PASSWORD@gate.proxyhat.com:8080');
  const results = [];
  for (const url of urls) {
    try {
      const res = await fetch(url, { agent, timeout: 30000 });
      if (res.ok) {
        throttle.onSuccess();
        results.push({ url, status: res.status, body: await res.text() });
      } else if (res.status === 429) {
        throttle.onRateLimit();
        const retryAfter = parseInt(res.headers.get('retry-after') || '0');
        if (retryAfter) await new Promise(r => setTimeout(r, retryAfter * 1000));
        results.push({ url, status: 429, body: null });
      } else if (res.status === 403) {
        throttle.onBlock();
        results.push({ url, status: 403, body: null });
      }
    } catch (err) {
      throttle.onBlock();
      results.push({ url, status: 0, error: err.message });
    }
    await throttle.wait();
    console.log(`Current delay: ${throttle.currentDelay.toFixed(0)}ms`);
  }
  return results;
}

Strategia 3: Ograniczenie rozłożone

Podczas równoległego uruchamiania wielokrotnych przypadków skrobania, należy koordynować limit szybkości we wszystkich pracownikach. Bez koordynacji każdy pracownik przestrzega własnego limitu, ale łączny ruch nadal przeważa nad celem.

import requests
import time
import threading
class DistributedRateLimiter:
    """Thread-safe rate limiter for multiple scraper workers."""
    def __init__(self, max_rpm: int):
        self.min_interval = 60.0 / max_rpm
        self.lock = threading.Lock()
        self.last_request_time = 0.0
    def acquire(self):
        """Block until it is safe to make the next request."""
        with self.lock:
            now = time.time()
            elapsed = now - self.last_request_time
            if elapsed < self.min_interval:
                time.sleep(self.min_interval - elapsed)
            self.last_request_time = time.time()
# Shared limiter across all threads
limiter = DistributedRateLimiter(max_rpm=30)
PROXY = "http://USERNAME:PASSWORD@gate.proxyhat.com:8080"
def worker(urls: list[str], results: list):
    for url in urls:
        limiter.acquire()
        try:
            resp = requests.get(
                url,
                proxies={"http": PROXY, "https": PROXY},
                timeout=30
            )
            results.append({"url": url, "status": resp.status_code})
        except Exception as e:
            results.append({"url": url, "error": str(e)})

Strategia 4: Kolejka wniosków o priorytet

W przypadku złożonych projektów scrating należy stosować kolejkę priorytetową, która zarządza limitami stawek na domenę docelową:

import requests
import time
import heapq
import threading
from collections import defaultdict
PROXY = "http://USERNAME:PASSWORD@gate.proxyhat.com:8080"
class DomainRateLimiter:
    """Per-domain rate limiting with priority queue."""
    def __init__(self, default_rpm: int = 10):
        self.default_rpm = default_rpm
        self.domain_limits = {}          # domain -> max RPM
        self.domain_last = defaultdict(float)  # domain -> last request time
        self.lock = threading.Lock()
    def set_limit(self, domain: str, rpm: int):
        self.domain_limits[domain] = rpm
    def wait_for_domain(self, domain: str):
        rpm = self.domain_limits.get(domain, self.default_rpm)
        min_interval = 60.0 / rpm
        with self.lock:
            now = time.time()
            elapsed = now - self.domain_last[domain]
            if elapsed < min_interval:
                time.sleep(min_interval - elapsed)
            self.domain_last[domain] = time.time()
# Configure per-domain limits
limiter = DomainRateLimiter(default_rpm=10)
limiter.set_limit("amazon.com", 3)        # Very conservative for Amazon
limiter.set_limit("example.com", 30)      # Lenient for simple sites
limiter.set_limit("google.com", 5)        # Moderate for Google

Czytanie Robots.txt dla rate podpowiedzi

Wiele stron publikuje swoje preferencje pełzania w robot.txt. W Crawl-delay Dyrektywa mówi o minimalnych sekundach pomiędzy wnioskami:

import requests
from urllib.parse import urlparse
from urllib.robotparser import RobotFileParser
def get_crawl_delay(base_url: str, user_agent: str = "*") -> float | None:
    """Extract Crawl-delay from robots.txt."""
    parsed = urlparse(base_url)
    robots_url = f"{parsed.scheme}://{parsed.netloc}/robots.txt"
    try:
        resp = requests.get(robots_url, timeout=10)
        if resp.status_code != 200:
            return None
        rp = RobotFileParser()
        rp.parse(resp.text.splitlines())
        delay = rp.crawl_delay(user_agent)
        return delay
    except Exception:
        return None
# Check before scraping
delay = get_crawl_delay("https://example.com")
if delay:
    print(f"Site requests {delay}s between requests")
else:
    print("No crawl-delay specified")

Błędy w stawce wspólnej

  • Ignoruję 429 odpowiedzi. Wiele scraperów traktuje wszystkie odpowiedzi nie-200 tak samo. 429 mówi dokładnie, co się stało - użyj nagłówka Retri- After i wycofać.
  • Naprawiono opóźnienia bez jittera. Prośba dokładnie co 2.000 sekund wygląda robotycznie. Dodaj losową zmienność (jitter) do swoich opóźnień.
  • Nie koordynuje równoległych pracowników. Pięciu robotników, każdy robi 10 RPM równa się 50 RPM razem. Użyj ogranicznika wspólnej stopy procentowej.
  • Obrócanie IP bez zwalniania. Rotacja IP daje Ci czas, ale jeśli każdy nowy IP natychmiast młotkuje stronę, zaawansowana detekcja będzie nadal złapać. Połącz rotację z odpowiednią przepustnicą.
  • Drapanie w godzinach szczytu. Miejsca są bardziej agresywne z ograniczeniem stawek w okresach wysokiego ruchu. Harmonogram ciężkich czołgów w godzinach poza szczytem dla strefy czasowej celu.

Aby obliczyć, ile proxy potrzebujesz do wsparcia swojego rate- ograniczone skrobanie, zobacz Ile nagród potrzebujesz do skrobania?Dla strategii rotacji proxy, które uzupełniają ograniczenie stóp, czytaj Strategie rotacji proxy dla rozdrabniania na dużą skalę.

Zacznij od odpowiednio ograniczonego drapania za pomocą ProxyHat Python SDK lub odkrywać plany cenowe dla twojego projektu.

Często zadawane pytania

Co się stanie, gdy przekroczę limit stawki?

Odpowiedź zależy od strony. Większość zwraca HTTP 429 z nagłówkiem Retri- After. Niektórzy podają CAPTCHA. Agresywne strony natychmiast blokują IP za pomocą odpowiedzi 403. W najgorszym wypadku powtarzające się naruszenia prowadzą do stałych zakazów IP.

Jak znaleźć limit stawki witryny?

Zacznij powoli i stopniowo wzrastać, monitorując kody odpowiedzi. Sprawdzić robotys.txt dla dyrektyw Opóźnienia Crawl. Obserwuj nagłówki odpowiedzi dla pól X- RateLimit - Limit i X- RateLimit - Pozostałe pola. Niektóre API publikują swoje ograniczenia w dokumentacji.

Czy użycie proxy jest ograniczone?

Proxies rozprowadzają wnioski w wielu IP, więc każdy IP pozostaje w granicach per- IP. Jednak zaawansowane strony również śledzić sesje, odciski palców i wzorce zachowań. Proxy są niezbędne, ale nie wystarczające - połączyć je z odpowiednim przepustnicy i realistyczne wzory żądania.

Jaka jest najbezpieczniejsza stawka za skrobanie?

Nie ma uniwersalnej odpowiedzi. Dla agresywnych celów, takich jak Google czy Amazon, 1-5 wniosków na minutę na IP jest bezpieczne. W przypadku słabo chronionych stron 20- 60 RPM na IP może działać. Należy zawsze rozpoczynać leczenie zachowawcze i zwiększać dawkę na podstawie obserwowanych wskaźników skuteczności.

Gotowy, aby zacząć?

Dostęp do ponad 50 mln rezydencjalnych IP w ponad 148 krajach z filtrowaniem AI.

Zobacz cenyProxy rezydencjalne
← Powrót do Bloga