Python ile Web Scraping ve Rotating Proxy Kullanımı

Yayın tarihi:

12 dk okuma

Acar Diveroli
Yazar: Acar Diveroli
Aynı REQ kartları banttan gateway makinesine girer, her biri üstünde farklı bir çıkış IP'si yazılı olarak çıkar.

Betiğiniz her gece 2.000 ürün sayfası okuyor. İlk 300 sayfa sorunsuz geliyor, sonra yanıtlar 429 Too Many Requests'e dönüyor, birkaç dakika sonra da her istek 403 alıyor. Kodda hiçbir şey değişmedi; site adresinizden kaç istek geldiğini saydı ve hiçbir ziyaretçinin böyle davranmadığına karar verdi. Rotating proxy bunun standart cevabıdır, ama kötü bağlanırsa yeni sorunlar çıkarır: IP beklediğiniz anda değişmez, giriş yarı yolda kopar ya da yirmi işçi nazik bir scraper'ı sele çevirir.

Bu rehber kurulumu Python'da baştan sona kuruyor: tek IP neden hız sınırına takılır, rotating gateway çıkışları nasıl atar, requests'te proxies sözlüğü, oturum yeniden kullanımı ve rotasyonu neden sessizce durdurduğu, Retry-After'a uyan yeniden deneme, iş parçacığı havuzu ve httpx/asyncio sürümü, istek başına rotasyon ile sticky arasındaki seçim, çıkış IP'si kontrolü ve uymanız gereken sınırlar. Bütün örnekler 29 Eylül 2026'da Python 3.13.9, requests 2.34.2, httpx 0.28.1 ve tenacity 9.1.4 ile çalıştırıldı. Kavram tarafı IP Rotasyonu Nedir ve Nasıl Çalışır? ve Web Scraping'de Engellenmeden Veri Toplama Yöntemleri yazılarında; bu yazı kodun kendisi.

Tek IP neden 429 ya da 403 alır?

Siteler istekleri istemci başına sayar, en kolay istemci kimliği de kaynak IP adresidir. Hız sınırlayıcı (rate limiter) her adres için bir zaman penceresinde sayaç tutar; sayaç eşiği geçince 429 Too Many Requests döner. RFC 6585'in 4. bölümü bu kodu tam bu durum için tanımlar ve yanıtın ne kadar bekleneceğini söyleyen bir Retry-After başlığı taşıyabileceğini belirtir. Sayma algoritmaları 429 Too Many Requests ve Rate Limit Hatası Nedir? yazısında.

Art arda gelen 429'lardan sonraki 403 başka bir katmandır: sınırlayıcı yavaşlamanızı istedi, yavaşlamadınız ve bir kural adresinizi betiğinizden daha uzun yaşayan bir engel listesine taşıdı. Çıkışları döndürmek istekleri birçok adrese yayar, böylece hiçbir adres tek başına eşiği geçmez; ama rotasyon yavaşlamanın yerini tutmaz: dakikada 60 isteğe izin veren siteye on adresten 600 istek gönderirseniz bir yerine on engellenmiş adresiniz olur.

Rotating proxy gateway'i nasıl çalışır?

Ağ geçidi (gateway, backconnect) modelinde kodunuz tek bir adres bilir, örneklerde pr.proxynet.io:8000; havuzu sağlayıcı yönetir. Bir HTTPS isteği şu yolu izler:

  1. İstemciniz gateway'e TCP bağlantısı açar ve user:pass çiftinden üretilmiş Proxy-Authorization: Basic ... başlığıyla CONNECT hedef:443 gönderir.
  2. Gateway kullanıcı adını okur. Hesap kısmından sonra gelen her şey parametredir: -country-tr havuzu tek ülkeye daraltır, -session-<id>-ttl-<saniye> sticky oturum ister.
  3. Gateway daraltılmış havuzdan bir çıkış seçer: istek başına modda her yeni tünel için yeni bir çıkış, sticky modda oturum kimliğinize zaten bağlı olan çıkış.
  4. Tünel kurulur, TLS istemcinizle hedef arasında çalışır ve hedef çıkışın IP'sini görür.
  5. Tünel kapanınca bağ çözülür. Bir oturum kimliği tutmuyorsa sonraki bağlantı yeni bir çıkış alır.

Bu rehberin bütün hikâyesi 3. adımdır. Rotasyonun birimi HTTP isteği değil tüneldir: bir CONNECT tüneli açık kaldığı sürece içinden geçen her istek aynı çıkıştan çıkar. Her modern istemcinin varsayılan olarak açtığı keep-alive, "istek başına" ifadesinin pratikte ne anlama geldiğini bu yüzden değiştirir. Ürün tarafı Rotating Proxy sayfasında; buradaki çıkışlar bir Residential Proxy havuzundan gelir, yani hedef sıradan bir ev adresi görür. Gerçek kullanıcı adını panel üretir (proxynet-xxxxxxxx-country-tr-session-…-ttl-1800); aşağıda user olarak kısaltıldı.

İstek başına rotasyon, sticky oturum ve sabit IP

İstek başına rotasyonSticky oturumSabit IP
IP'yi değiştiren olayHer yeni bağlantıOturum kimliğinin değişmesi ya da TTL'nin dolması (1-60 dakika)Hiçbir şey; adres sizindir
Kullanıcı adıuseruser-session-<id>-ttl-<saniye>Ayrı ürün, sabit host:port
Çerez ve girişKopar; site aynı çerezi birçok şehirden görürTTL boyunca korunurSüresiz korunur
İstek başına maliyetHer seferinde yeni TCP ve TLS el sıkışmasıBir kez el sıkışma, sonra keep-aliveBir kez el sıkışma, sonra keep-alive
Paralel işçilerHer bağlantı kendi çıkışını alırİşçi başına bir kimlik, kimlik başına bir çıkışKiraladığınız adres kadar çıkış
Tipik işBağımsız ürün ve liste sayfalarıGiriş, sepet, çok sayfalı sayfalamaAPI izin listeleri, uzun ömürlü hesaplar

Sticky, çıkışı tutma isteğidir, garanti değil; bir residential cihaz çevrimdışı olabilir ve gateway oturumu erken taşır. Ürünün davranışı Sticky Proxy sayfasında.

requests kurulumu: proxies sözlüğü

requests'in proxy dokümantasyonu, session.proxies üzerine yazılan değerler HTTP_PROXY ve HTTPS_PROXY ortam değişkenleriyle ezilebildiği için proxies parametresini her istekte açıkça vermeyi önerir. Anahtarlar hedef URL'nin şemasıdır ve HTTPS trafiği tünellendiği için ikisi de aynı http:// gateway adresine bakar:

python
import os
import requests

# Bu dizgiyi panel üretir; git'e değil ortam değişkenine koyun.
GATEWAY = os.environ.get("PROXY_URL", "http://user:pass@pr.proxynet.io:8000")
PROXIES = {"http": GATEWAY, "https": GATEWAY}
HEADERS = {"User-Agent": "price-monitor/1.0 (+mailto:you@example.com)"}

resp = requests.get("https://httpbin.org/ip", proxies=PROXIES, headers=HEADERS, timeout=(5, 30))
print(resp.status_code, resp.json())

timeout demeti 5 saniyelik bağlanma ve 30 saniyelik okuma sınırı koyar; onsuz takılan bir çıkış işçiyi sonsuza kadar bekletebilir. User-Agent betiğinizi adlandırır ve site sahibine size ulaşacak bir yol verir; User Agent Nedir? yazısı tarayıcı taklidi yerine bunu önerir. Kimlik bilgileri ortam değişkeninde durur, çünkü aynı dokümantasyon sürüm kontrolündeki dosyalara karşı uyarır. SOCKS5 için requests[socks] kurun ve socks5h://user:pass@pr.proxynet.io:1080 kullanın; h harfi DNS çözümlemesini proxy'ye bırakır, SOCKS5 portu da aynı şekilde döner.

Oturum yeniden kullanımı: IP neden değişmedi?

requests.Session TCP bağlantısını yeniden kullanır; proxy arkasında bu, tünelin ve dolayısıyla çıkışın yeniden kullanılması demektir. Bunu her CONNECT'i kaydeden yerel bir test proxy'siyle ölçtük:

Aynı sunucuya üç GETAçılan tünelAtanan çıkış (tünel başına bir)
Üç kez requests.get(...), oturum yok33
Tek Session, üç s.get(...)11
Tek Session, headers={"Connection": "close"}33
Tek httpx.AsyncClient, art arda üç await client.get(...)11

Kural buradan çıkar: istek başına rotasyon için requests.get'i oturumsuz çağırın ya da Connection: close gönderin; sticky işlerde Session ile oturum kimliğini birlikte kullanın, çünkü kopan bir bağlantı aksi hâlde çıkışı yeniden atar.

Retry-After'a uyan backoff ile yeniden deneme

Rotating gateway arkasında iki şey bozulur: ağ (bir çıkış düşer, tünel reddedilir, okuma zaman aşımına uğrar) ve hedef (429 ya da 5xx). İkisi de yeniden denenmeyi hak eder, ama aynı beklemeyi değil. 429 sitenin kendi sayısını Retry-After içinde taşıyabilir; o sayı kazanır. Geri kalan her şey jitter'lı üstel backoff alır, böylece birlikte düşen dört işçi birlikte yeniden denemez:

python
import logging
import random
import time

import requests

MAX_ATTEMPTS = 4         # ilk deneme + 3 yeniden deneme
RETRY_STATUS = {429, 500, 502, 503, 504}
TIMEOUT = (5, 30)        # bağlanma, okuma - saniye
log = logging.getLogger("scraper")


def backoff(attempt: int) -> float:
    """1, 2, 4, 8 sn ... artı jitter; işçiler aynı anda yeniden denemesin."""
    return min(2 ** (attempt - 1), 30) + random.uniform(0, 1)


def fetch(url: str) -> requests.Response:
    """Tek URL'yi yeniden denemelerle GET eder. Son başarısız denemeden sonra hata fırlatır."""
    for attempt in range(1, MAX_ATTEMPTS + 1):
        try:
            resp = requests.get(url, proxies=PROXIES, headers=HEADERS, timeout=TIMEOUT)
        except (requests.ConnectionError, requests.Timeout) as exc:
            # ProxyError da buraya girer: gateway tüneli reddetti ya da düşürdü.
            log.warning("deneme %d/%d %s: %s", attempt, MAX_ATTEMPTS, url, exc.__class__.__name__)
            if attempt == MAX_ATTEMPTS:
                raise
            time.sleep(backoff(attempt))
            continue

        if resp.status_code not in RETRY_STATUS:
            return resp  # 200, 404, 301 ... kararı çağıran versin

        # Site yavaşlamamızı istedi. Onun sayısı bizim takvimimizden önce gelir.
        retry_after = resp.headers.get("Retry-After")
        wait = float(retry_after) if retry_after and retry_after.isdigit() else backoff(attempt)
        log.warning("deneme %d/%d %s: HTTP %d, %.1f sn bekleniyor", attempt, MAX_ATTEMPTS, url, resp.status_code, wait)
        if attempt == MAX_ATTEMPTS:
            resp.raise_for_status()
        time.sleep(wait)
    raise RuntimeError("buraya ulaşılmaz")

requests.ProxyError, ConnectionError'ın alt sınıfıdır; bu yüzden CONNECT'e hata dönen bir gateway yeni bir tünelde yeniden denenir, istek başına modda bu yeni bir çıkış demektir. 404 olduğu gibi döndürülür: sayfa yok ve başka bir adres onu geri getirmez.

Aynı politika tenacity dekoratörüyle daha kısadır; bedeli Retry-After'ı bir istisnaya çevirmek zorunda kalmanızdır, yani başlık yerine tenacity'nin kendi takvimi uygulanır:

python
from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_exponential_jitter


class RetryableStatus(Exception):
    """429 ve 5xx için fırlatılır; tenacity onları ağ hatası gibi yeniden dener."""


@retry(
    stop=stop_after_attempt(4),
    wait=wait_exponential_jitter(initial=1, max=20),
    retry=retry_if_exception_type((requests.ConnectionError, requests.Timeout, RetryableStatus)),
    reraise=True,
)
def fetch_with_tenacity(url: str) -> requests.Response:
    resp = requests.get(url, proxies=PROXIES, headers=HEADERS, timeout=TIMEOUT)
    if resp.status_code in RETRY_STATUS:
        raise RetryableStatus(f"HTTP {resp.status_code} for {url}")
    return resp

https://httpbin.org/status/503 karşısında 1,5, 2,8 ve 4,8 saniye sonra yeniden denedi, sonra RetryableStatus fırlattı.

Tam scraper: iş parçacığı havuzu, yeniden deneme ve günlük

ThreadPoolExecutor dört fetch çağrısını aynı anda çalıştırır, as_completed sonuçları bittikçe verir ve bozuk bir sayfa çalışmayı durdurmadan günlüğe yazılır. scrape.py olarak kaydedin:

python
"""scrape.py - bir sayfa listesini rotating proxy gateway'i üzerinden çeker."""
import logging
import os
import random
import sys
import time
from concurrent.futures import ThreadPoolExecutor, as_completed

import requests

GATEWAY = os.environ.get("PROXY_URL", "http://user:pass@pr.proxynet.io:8000")
PROXIES = {"http": GATEWAY, "https": GATEWAY}
HEADERS = {"User-Agent": "price-monitor/1.0 (+mailto:you@example.com)"}

MAX_WORKERS = 4          # aynı anda yolda olan istek sayısı
MAX_ATTEMPTS = 4
RETRY_STATUS = {429, 500, 502, 503, 504}
TIMEOUT = (5, 30)

log = logging.getLogger("scraper")


def backoff(attempt: int) -> float:
    return min(2 ** (attempt - 1), 30) + random.uniform(0, 1)


def fetch(url: str) -> requests.Response:
    # önceki bölümdeki yeniden deneme döngüsü, değişmeden
    ...


def scrape(url: str) -> dict:
    started = time.monotonic()
    resp = fetch(url)
    return {
        "url": url,
        "status": resp.status_code,
        "bytes": len(resp.content),
        "seconds": round(time.monotonic() - started, 2),
    }


def main(urls: list[str]) -> None:
    logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
    results, failed = [], []
    with ThreadPoolExecutor(max_workers=MAX_WORKERS) as pool:
        futures = {pool.submit(scrape, url): url for url in urls}
        for future in as_completed(futures):
            url = futures[future]
            try:
                row = future.result()
                results.append(row)
                log.info("ok   %s -> %d, %.1f sn", url, row["status"], row["seconds"])
            except Exception as exc:  # tek bozuk sayfa çalışmayı durdurmamalı
                failed.append((url, repr(exc)))
                log.error("hata %s -> %s", url, exc)
    log.info("bitti: %d ok, %d hata", len(results), len(failed))
    for row in results:
        print(row)


if __name__ == "__main__":
    targets = sys.argv[1:] or [f"https://httpbin.org/ip?n={i}" for i in range(8)]
    main(targets)

Yeniden deneme yolunu çalıştırmak için betiği her üçüncü tüneli 429 ile reddeden yerel bir test proxy'sinden geçirdik. Sekiz URL, dört işçi, sekiz tünel, üç yapay hata, sıfır kayıp sayfa:

text
03:32:27 WARNING attempt 1/4 https://httpbin.org/ip?n=2: ProxyError
03:32:28 INFO ok   https://httpbin.org/ip?n=3 -> 200 in 1.1s
03:32:28 INFO ok   https://httpbin.org/ip?n=1 -> 200 in 1.1s
03:32:28 INFO ok   https://httpbin.org/ip?n=0 -> 200 in 1.1s
03:32:28 WARNING attempt 1/4 https://httpbin.org/ip?n=5: ProxyError
03:32:29 INFO ok   https://httpbin.org/ip?n=4 -> 200 in 0.9s
03:32:29 WARNING attempt 1/4 https://httpbin.org/ip?n=7: ProxyError
03:32:29 INFO ok   https://httpbin.org/ip?n=6 -> 200 in 0.9s
03:32:30 INFO ok   https://httpbin.org/ip?n=2 -> 200 in 2.6s
03:32:31 INFO ok   https://httpbin.org/ip?n=5 -> 200 in 2.4s
03:32:31 INFO ok   https://httpbin.org/ip?n=7 -> 200 in 2.0s
03:32:31 INFO done: 8 ok, 0 failed

Günlük tasarımın parçasıdır: her satırdaki URL, deneme numarası, durum kodu ya da istisna sınıfı ve bekleme süresi, hiçbir şeyi yeniden çalıştırmadan "site bizi sınırlıyor" ile "gateway tünelleri düşürüyor" durumlarını ayırmaya yeter. Dört işçi bilerek düşük tutuldu; eşzamanlılık istek hızınızı çarpar, hedefin ölçtüğü şey de hızdır. results satırlarını Kazınan Veriyi CSV, JSON ve SQLite'a Kaydetme yazısındaki gibi dosyaya yazın; resp.text ile satır arasındaki ayrıştırma adımı Web Sitesinden Veri Çekme yazısında.

Aynı iş httpx ve asyncio ile

httpx proxy'yi httpx proxy dokümantasyonunda anlatıldığı gibi istemci üzerinde alır. Eşzamanlılık sınırı olarak iş parçacığı havuzunun yerini Semaphore alır; yeniden deneme döngüsü biçimini korur, yalnız beklemeler await olur:

python
import asyncio
import random

import httpx

MAX_IN_FLIGHT = 4


async def fetch(client: httpx.AsyncClient, url: str) -> httpx.Response:
    for attempt in range(1, MAX_ATTEMPTS + 1):
        try:
            resp = await client.get(url)
        except (httpx.TransportError, httpx.ProxyError) as exc:
            log.warning("deneme %d/%d %s: %s", attempt, MAX_ATTEMPTS, url, exc.__class__.__name__)
            if attempt == MAX_ATTEMPTS:
                raise
            await asyncio.sleep(2 ** (attempt - 1) + random.uniform(0, 1))
            continue
        if resp.status_code not in RETRY_STATUS:
            return resp
        retry_after = resp.headers.get("Retry-After")
        wait = float(retry_after) if retry_after and retry_after.isdigit() else 2 ** (attempt - 1) + random.uniform(0, 1)
        log.warning("deneme %d/%d %s: HTTP %d, %.1f sn bekleniyor", attempt, MAX_ATTEMPTS, url, resp.status_code, wait)
        if attempt == MAX_ATTEMPTS:
            resp.raise_for_status()
        await asyncio.sleep(wait)
    raise RuntimeError("buraya ulaşılmaz")


async def main(urls: list[str]) -> None:
    gate = asyncio.Semaphore(MAX_IN_FLIGHT)
    async with httpx.AsyncClient(proxy=GATEWAY, headers=HEADERS, timeout=httpx.Timeout(30, connect=5)) as client:

        async def one(url: str):
            async with gate:
                resp = await fetch(client, url)
                return {"url": url, "status": resp.status_code}

        rows = await asyncio.gather(*(one(u) for u in urls), return_exceptions=True)
    for url, row in zip(urls, rows):
        print("HATA" if isinstance(row, Exception) else "ok  ", url, row)


asyncio.run(main([f"https://httpbin.org/ip?n={i}" for i in range(8)]))

Tek istemci tek bağlantı havuzu demektir ve havuzdaki bağlantılar tünelleri yeniden kullanır: dört istek yolda ve sekiz URL varken çalıştırmamız dört tünel açtı, yani sayfa çiftleri aynı çıkışı paylaştı. Her sayfa kendi adresinden çıkmalıysa headers={"Connection": "close"} gönderin; bir grup sayfa aynı adresi paylaşmalıysa bu bir sticky oturumdur, havuzun rastlantısı değil. Hangi kütüphanenin hangi işe uyduğu HTTPX, Requests ve AIOHTTP Karşılaştırması yazısında.

İstek başına mı sticky mi: seçim ve çıkış IP'sinin doğrulanması

Kararı iş başına verin. Ürün sayfaları, arama listeleri ve herkese açık profiller bağımsızdır: istek başına rotasyon, oturum nesnesi yok. Girişten sonra yirmi sayfalık bir sayfalama tek bir konuşmadır: çerezler için bir Session, çıkış için bir oturum kimliği, bir işçi. IP değişirken çerez kavanozunun aynı kalması, çalışma ortasında oturumdan düşmenin en yaygın yoludur; giriş tarafı Python'da Oturum ve Çerez Yönetimi yazısında kuruluyor.

İki moda da güvenmeden önce bir echo servisine hangi adresi gördüğünü sorun: oturumsuz üç çağrı üç farklı adres, aynı oturum kimliğiyle üç çağrı aynı adresi döndürmelidir:

python
import uuid
import requests

HOST = "pr.proxynet.io:8000"
USER, PASSWORD = "user", "pass"
ECHO = "https://httpbin.org/ip"


def proxies_for(username: str) -> dict:
    url = f"http://{username}:{PASSWORD}@{HOST}"
    return {"http": url, "https": url}


print("istek basina:")
for _ in range(3):  # Session yok, her çağrı yeni bir tünel açar
    print("  ", requests.get(ECHO, proxies=proxies_for(USER), timeout=20).json()["origin"])

sid = uuid.uuid4().hex[:8]
sticky = proxies_for(f"{USER}-session-{sid}-ttl-600")  # en fazla 600 sn aynı çıkış
print(f"sticky {sid}:")
with requests.Session() as s:
    for _ in range(3):
        print("  ", s.get(ECHO, proxies=sticky, timeout=20).json()["origin"])

Bunu işin başında bir kez çalıştırın ve sonucu günlüğe yazın. "istek basina" üç kez aynı adresi basıyorsa önce bağlantı yeniden kullanımına bakın; "sticky" üç farklı adres basıyorsa oturum kimliği gateway'e ulaşmıyordur, genellikle bir URL kodlama adımı kullanıcı adını bozmuştur.

Hız sınırı, robots.txt ve uyduğunuz sınırlar

Rotating proxy sitenin hangi adresi gördüğünü değiştirir, neye izin verdiğini değil:

  • Önce robots.txt'yi okuyun. urllib.robotparser üç satırda can_fetch(user_agent, url) cevabını verir; izin verilmeyen yol URL listesinin dışında kalır. Dosyanın neyi ifade edebildiği robots.txt Nedir? yazısında.
  • Retry-After'ı olduğu gibi uygulayın. Yeniden deneme döngüsü, rotasyon başka bir çıkıştan devam etmenize izin verse bile sitenin sayısı kadar bekler.
  • İşçi sayısını değil hızı sınırlayın. Gecikmesiz dört işçi hızlı bir sitede hâlâ saniyede 40 istek gönderebilir; site bir sınır yayınlıyorsa işçi başına kısa bir time.sleep ekleyin.
  • Resmi API varsa onu tercih edin. İki taraf için de HTML'i proxy üzerinden ayrıştırmaktan ucuzdur.
  • CAPTCHA çözdürme servisi ve tespit atlatma eklentisi yok. CAPTCHA sitenin bir insan istediği anlamına gelir; cevap daha düşük hız, API ya da erişim istemektir.

Bu kurulum nerede kullanılır?

  • Fiyat takibi. Günde binlerce bağımsız ürün sayfası, istek başına rotasyon, dört ila sekiz işçi; havuz boyutlandırması veri kazıma sayfasında.
  • Kendi hesabınızın olduğu paneller. Hesap başına bir sticky oturum, bir işçi, çalıştırmalar arasında saklanan çerezler; Python'da Oturum ve Çerez Yönetimi yazısındaki gibi.
  • Elle tutulan proxy listesinin yerine. Listeyi kodda döndürmek, Python'da Proxy'ler Nasıl Döndürülür? yazısındaki gibi, eski yaklaşımdır; gateway listeyi ve ölü adres takibini ortadan kaldırır.

Sık yapılan hatalar

  • Session'ı yeniden kullanıp sayfa başına yeni IP beklemek. Bir tünel, bir çıkış. Oturumu bırakın ya da Connection: close gönderin.
  • Session olmadan sticky kimlik. Çıkış sabitlenir ama her çağrı yeni el sıkışma öder ve çerezler kaybolur. İkisini birlikte kullanın.
  • 404'ü yeniden denemek. Sayfa yok; başka bir çıkış onu bulmaz. Yalnız 429, 5xx ve ağ hatalarını yeniden deneyin.
  • Zaman aşımı yok. Takılan bir çıkış işçiyi süreç öldürülene kadar bloke eder. Her zaman timeout=(5, 30) verin.
  • Kimlik bilgileri betiğin içinde. Git geçmişine düşer. Ortam değişkeninden okuyun.

Karar rehberi

İhtiyaçÖneri
Çok sayıda bağımsız sayfa, giriş yokİstek başına rotasyon, oturumsuz requests.get, başlangıç için 4 işçi
Giriş, sonra çok sayfaSticky oturum (-session-<id>-ttl-<saniye>) artı tek requests.Session, hesap başına bir işçi
Binlerce küçük istek, G/Ç ağırlıklıSemaphore ile httpx AsyncClient; her biri kendi çıkışını almalıysa Connection: close
Site bir hız sınırı yayınlıyorÖnce işçi sayısını ve gecikmeyi o sınırın altına çekin, rotasyon ikinci sırada
Ülkeye özel fiyatlarKullanıcı adında -country-xx, o ülke içinde istek başına rotasyon
Adres hiç değişmemeli (API izin listesi)Rotating ürün değil; sabit IP kullanın

Sıkça sorulan sorular

IP neden her istekte değişmiyor?

Çünkü istemciniz bağlantıyı yeniden kullanıyor. requests'in Session'ı ve httpx'in Client'ı TCP bağlantısını, dolayısıyla proxy tünelini istekler arasında açık tutar; çıkış tünel açılırken seçilir. İstek başına yeni çıkış için düz requests.get çağrıları ya da Connection: close başlığı kullanın.

Python'da sticky oturum nasıl kullanılır?

Proxy URL'sindeki kullanıcı adının sonuna -session-<id>-ttl-<saniye> ekleyin, bütün konuşma boyunca aynı kimliği koruyun ve çağrıları tek bir requests.Session üzerinden yapın ki çerezler ve tünel yeniden kullanılsın. TTL 1 ile 60 dakika arasında olabilir; işten uzun bir değer seçin.

Proxy ile scraping için requests mi httpx mi?

Gecede birkaç yüz sayfa için iş parçacığı havuzlu requests yeterlidir ve hata ayıklaması daha kolaydır. On binlerce küçük istek için Semaphore ile httpx'in asenkron istemcisi daha az kaynak kullanır. İkisi de aynı gateway URL'sini ve aynı yeniden deneme mantığını alır.

Kaç eşzamanlı işçi güvenlidir?

Dörtle başlayın ve yanıtları izleyin. Önemli olan hedefin gördüğü dakikadaki istek sayısıdır; cevap havuz boyutuna değil sitenin sınırına bağlıdır. Dört işçide 429 görüyorsanız sayıyı düşürün ya da gecikme ekleyin.

Rotating proxy 429'u aşar mı?

İstekleri daha çok adrese yayar, böylece her biri eşiğin altında kalır; eşiği kaldırmaz. Site bir sınır koyduysa Retry-After'a uyun, hızı düşürün ya da API'yi kullanın. 429'u görüp daha hızlı döndürmek, adreslerin engel listesine düşme yoludur.

Proxy'nin çalıştığını nasıl doğrularım?

Proxy üzerinden https://httpbin.org/ip gibi bir IP echo ucuna istek atın ve cevabı kendi adresinizle karşılaştırın: rotasyonu görmek için oturumsuz üç kez, sticky davranışı görmek için oturum kimliğiyle üç kez.

Özet

Python'da rotating proxy, proxies sözlüğündeki tek bir gateway URL'si, Retry-After'a uyan bir yeniden deneme döngüsü, birkaç işçilik bir eşzamanlılık sınırı ve bir sayfanın öncekiyle ne zaman aynı çıkışı paylaşabileceğine dair net bir kuraldır. Giriş gerektiren işlerde oturum nesnesiyle oturum kimliğini birlikte tutun, bağımsız sayfalarda ikisini de uzak tutun ve ilk gerçek çalıştırmadan önce çıkış IP'sini kontrol edin. Gateway'in arkasındaki havuz proxy sayfasında.

ChatGPT'ye sorClaude'a sor