# Scrapy Proxy Middleware für rotierende Residential-Proxys

> Scrapy Middleware für rotierende Residential-Proxys: Rotation und Sticky Residential IPs pro Anfrage, AutoThrottle und Retry-Einstellungen, scrapy-playwright. Syntaxgeprüfter Code.

- URL: https://crawlrelay.com/de/docs/scrapy/
- Zuletzt aktualisiert: 2026-10-13
- Publisher: CrawlRelay (https://crawlrelay.com)

Eine 30-zeilige Downloader-Middleware, die pro Anfrage Land und Session wählt, plus Einstellungen, die einen Crawl höflich halten: robots.txt, pro-Domain-Konkurrenz, AutoThrottle und Retries.

**Kurze Antwort — Wie nutze ich rotierende Residential-Proxys mit Scrapy?**

Setze `request.meta["proxy"]` auf `http://USERNAME:PASSWORD@gw.crawlrelay.com:7777` in einer kleinen Downloader-Middleware, die vor Scrapy's `HttpProxyMiddleware` läuft; Scrapy schickt dann die Credentials für dich. Mit **CrawlRelay** Residential-Proxys kommen Land und sticky Session in den Benutzernamen, so dass eine Middleware beide Aufgaben übernimmt. Datenverkehr kostet ab 50 GB 0,50 $/GB. Bezahlen kannst du mit Krypto, ohne KYC.

## Scrapy Einrichtung auf einen Blick

| Attribut | Wert |
| --- | --- |
| Wo kommt der Proxy hin | `request.meta["proxy"]`, vom eigenen Middleware gesetzt |
| Middleware-Reihenfolge | Die eigene Middleware unter 750, der Priorität der eingebauten `HttpProxyMiddleware` |
| Zugangsdaten | In der Proxy-URL; Scrapy wandelt sie in einen `Proxy-Authorization` Header um |
| SOCKS5 | Nicht mit dem Standard-HTTP/1.1-Handler; Scrapy 2.17+ unterstützt SOCKS über den optionalen HttpxDownloadHandler. Der HTTP-Port 7777 ist der einfachste |
| Rotation | Neuer Exit pro neuer Verbindung; eine eindeutige Session-ID pro Anfrage liefert normalerweise eine andere IP |
| Höflichkeit | `ROBOTSTXT_OBEY`, `CONCURRENT_REQUESTS_PER_DOMAIN`, `AUTOTHROTTLE_ENABLED` |
| Retries | 429 und 5xx sind im Standard-`RETRY_HTTP_CODES` enthalten; Retry-After wird standardmäßig nicht beachtet |

## Wie schreibe ich die Proxy-Middleware?

Lege die Klasse in `middlewares.py` ab. Sie baut den Benutzernamen aus Einstellungen und drei optionalen `meta`-Schlüsseln: `proxy_country`, `proxy_session` und `proxy_ttl`. Eine Anfrage ohne `proxy_session` rotiert; eine mit `proxy_session="new"` erzeugt eine neue Session, also normalerweise eine andere Exit-IP; eine Anfrage mit fester Session-ID teilt sich den Exit mit anderen Anfragen desselben Flows. Die Middleware berechnet ihren Proxy bei jedem Durchlauf neu, auch bei Retries, und lässt einen von anderem Code gesetzten Proxy unangetastet. Wenn `CRAWLRELAY_USER` fehlt, stoppt der Crawl beim Start statt mit deiner eigenen IP zu laufen.

*middlewares.py*

```python
import uuid
from urllib.parse import quote


class CrawlRelayProxyMiddleware:
    """Sets request.meta["proxy"]; Scrapy's HttpProxyMiddleware then sends the credentials."""

    def __init__(self, user, password, country):
        self.user, self.password, self.country = user, password, country

    @classmethod
    def from_crawler(cls, crawler):
        s = crawler.settings
        if not s.get("CRAWLRELAY_USER"):
            # Fail loudly: NotConfigured would only disable the middleware and crawl from your own IP
            raise ValueError("CRAWLRELAY_USER is not set: refusing to crawl without the proxy")
        return cls(s.get("CRAWLRELAY_USER"), s.get("CRAWLRELAY_PASSWORD", ""), s.get("CRAWLRELAY_COUNTRY", "us"))

    def process_request(self, request, spider=None):
        if "proxy" in request.meta and not request.meta.get("_crawlrelay"):
            return None  # a proxy set by other code: leave it alone
        parts = [self.user, "country", request.meta.get("proxy_country", self.country)]
        session = request.meta.get("proxy_session")
        if session == "new":  # a fresh session for this request (and for each of its retries)
            session = uuid.uuid4().hex[:12]
        if session:
            parts += ["session", session, "ttl", str(request.meta.get("proxy_ttl", 10))]
        username = "-".join(parts)
        request.meta["proxy"] = "http://%s:%s@gw.crawlrelay.com:7777" % (
            quote(username, safe=""), quote(self.password, safe=""))
        request.meta["_crawlrelay"] = True
        return None
```

## Welche Scrapy-Einstellungen halten einen Crawling mit Residential-Proxys höflich und schnell?

Residential-Proxys verteilen deine Anfragen auf viele IPs, aber das Ziel sieht deine Gesamt-Rate. Diese Einstellungen halten jede Domain in einem nachhaltigen Tempo und lassen AutoThrottle bei langsameren Antworten selbst verlangsamen. Behalte `ROBOTSTXT_OBEY = True`, das neue Scrapy-Projekte schon setzen.

*settings.py (Auszug)*

```python
import os

BOT_NAME = "myproject"
ROBOTSTXT_OBEY = True
USER_AGENT = "ExampleCrawler/1.0 (+https://example.com/crawler-info)"

CRAWLRELAY_USER = os.environ.get("CRAWLRELAY_USER")  # e.g. the username from your dashboard
CRAWLRELAY_PASSWORD = os.environ.get("CRAWLRELAY_PASSWORD")
CRAWLRELAY_COUNTRY = "us"

DOWNLOADER_MIDDLEWARES = {
    "myproject.middlewares.CrawlRelayProxyMiddleware": 350,  # runs before HttpProxyMiddleware (750)
}

CONCURRENT_REQUESTS = 32
CONCURRENT_REQUESTS_PER_DOMAIN = 4
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 1.0
AUTOTHROTTLE_MAX_DELAY = 30.0
AUTOTHROTTLE_TARGET_CONCURRENCY = 2.0

RETRY_ENABLED = True
RETRY_TIMES = 3
RETRY_HTTP_CODES = [429, 500, 502, 503, 504, 522, 524, 408]
DOWNLOAD_TIMEOUT = 45
```

## Wie kann ich eine IP für die Paginierung beibehalten und für Artikelseiten rotieren?

Übergib eine feste `proxy_session` bei den Requests eines Ablaufs, der von der vorherigen Seite abhängt, und keine bei unabhängigen Seiten. Der folgende Spider läuft auf books.toscrape.com, einer Sandbox-Seite, die zum Üben von Scraping erstellt wurde.

*spiders/catalog.py*

```python
import scrapy


class CatalogSpider(scrapy.Spider):
    name = "catalog"
    start_urls = ["https://books.toscrape.com/"]

    def parse(self, response):
        for href in response.css("article.product_pod h3 a::attr(href)").getall():
            # Product pages are independent: rotating exits
            yield response.follow(href, callback=self.parse_item)
        next_page = response.css("li.next a::attr(href)").get()
        if next_page:
            # The listing flow keeps one sticky exit for 30 minutes
            yield response.follow(next_page, callback=self.parse,
                                  meta={"proxy_session": "listing1", "proxy_ttl": 30})

    def parse_item(self, response):
        yield {
            "title": response.css("div.product_main h1::text").get(),
            "price": response.css("p.price_color::text").get(),
            "url": response.url,
        }
```

## Verwendet Scrapy Verbindungen wieder und verhindert das die Rotation?

Ja, Scrapy hält Verbindungen aktiv und nutzt sie pro Proxy und Zielhost wieder, sodass auf einer Verbindung aufeinanderfolgende Requests denselben Exit teilen. Für die meisten Crawls ist das kein Problem: Scrapy öffnet bis zu `CONCURRENT_REQUESTS_PER_DOMAIN` Verbindungen pro Site, und jede neue Verbindung erhält normalerweise eine andere Residential Proxy-IP.

Wenn ein Request über eine neue IP gehen muss, z. B. ein Retry nach einem [502](https://crawlrelay.com/de/docs/errors/502-bad-gateway/) durch einen fehlerhaften Exit, sende ihn mit `response.request.replace(meta={**response.meta, "proxy_session": "new"}, dont_filter=True)` erneut. Die Middleware erstellt einen neuen Session-Benutzernamen, ein anderer Benutzername bedeutet einen anderen Connection Pool, sodass Scrapy eine neue Verbindung öffnet und das Gateway normalerweise einen anderen Exit zuweist.

## Wie respektiere ich Retry-After bei 429-Antworten in Scrapy?

Scrapy's eingebaute `RetryMiddleware` versucht 429 erneut, wartet aber nicht den `Retry-After`-Verzögerungswert ab. AutoThrottle verlangsamt die Domain mit der Zeit, was meist ausreichend ist. Für APIs, die lange `Retry-After`-Werte senden, füge eine kleine Middleware hinzu, die den Header ausliest und den Retry mit Verzögerung plant, oder setze `CONCURRENT_REQUESTS_PER_DOMAIN` für diese Domain herunter. Die [429-Seite](https://crawlrelay.com/de/docs/errors/429-too-many-requests/) erklärt, was der Header bedeutet.

## Wie nutze ich den Proxy mit scrapy-playwright?

Für Seiten, die JavaScript benötigen, übergibt scrapy-playwright Playwrights Launch-Optionen über eine Einstellung. Gib die Zugangsdaten als separate Felder an: Chromium ignoriert Zugangsdaten, die direkt in der Server-URL stehen. Verwende einen Kontext pro Sticky Session, wenn du ausgeloggte Abläufe über mehrere Seiten renderst.

*settings.py für scrapy-playwright*

```python
import os

DOWNLOAD_HANDLERS = {
    "http": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
    "https": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
}
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"

PLAYWRIGHT_LAUNCH_OPTIONS = {
    "proxy": {
        "server": "http://gw.crawlrelay.com:7777",
        "username": os.environ.get("CRAWLRELAY_USER", "USERNAME") + "-country-us",
        "password": os.environ.get("CRAWLRELAY_PASSWORD", "PASSWORD"),
    },
}
```

## Scrapy und Proxys: Fragen

### Brauche ich scrapy-rotating-proxies oder eine Proxy-Liste?

Nein. Diese Pakete rotieren eine Liste einzelner Proxy-Adressen und sperren fehlerhafte. Mit einem Residential Gateway rotiert die IP hinter einer einzelnen Adresse: Jede neue Verbindung erhält normalerweise eine andere Residential Proxy-IP. Eine einzelne Middleware, die `request.meta["proxy"]` setzt, reicht aus.

### Warum zeigt Scrapy TunnelError mit meinem Proxy?

`TunnelError: Could not open CONNECT tunnel` bedeutet, dass der Proxy die CONNECT-Anfrage für eine HTTPS-Seite ablehnte oder fehlschlug. Der Status im Fehler gibt den Grund an: 407 steht für Zugangsdaten, 502 oder 504 für den Exit oder das Ziel. Siehe die [407](https://crawlrelay.com/de/docs/errors/407-proxy-authentication-required/) und [502](https://crawlrelay.com/de/docs/errors/502-bad-gateway/) Seiten.

### Wie viele gleichzeitige Requests pro Domain sollte ich nutzen?

Starte mit 2 bis 4 pro Domain und lass AutoThrottle anpassen. Erhöhe die Anzahl nur, solange der Anteil von 429, 403 und 5xx-Antworten stabil bleibt, und beobachte die Antwortzeiten der Seite. Große Sites vertragen mehr als kleine; kleine Sites benötigen eventuell Verzögerungen zwischen Requests und mehrere Verbindungen.

### Soll ich ROBOTSTXT_OBEY deaktivieren, um mehr Daten zu bekommen?

Nein. robots.txt gibt an, welche Pfade automatisierte Clients abrufen dürfen, und wurde 2022 durch RFC 9309 standardisiert. Das Ignorieren erhöht dein rechtliches und ethisches Risiko und verstößt gegen unsere [Acceptable Use Policy](https://crawlrelay.com/de/acceptable-use/), wenn damit auf Inhalte zugegriffen wird, die die Seite ausgeschlossen hat.

### Wie viel Traffic verbraucht ein Scrapy-Crawl?

Scrapy lädt nur HTML, Seiten kosten daher meist einige zehn Kilobyte, komprimiert. Prüfe den `downloader/response_bytes`-Wert in den von Scrapy am Ende eines Laufs ausgegebenen Statistiken und teile ihn durch die Anzahl der Antworten. Der [Bandbreiten-Guide](https://crawlrelay.com/de/guides/bandwidth-calculator/) wandelt das in Kosten um.

**Residential-Proxys in dein Scrapy-Projekt einfügen** 5,94 $ für 10 GB zum Testen, 0,50 $/GB von 50 GB. [Preise ansehen](https://crawlrelay.com/de/pricing/) · [Guide zu gleichzeitigen Verbindungen](https://crawlrelay.com/de/docs/concurrency/)

---

CrawlRelay: CrawlRelay ist ein entwicklerfreundlicher rotierender Residential-Proxy- und 4G/5G mobile Proxy-Service für Scraper und KI-Datenpipelines: nicht verfallende Pay-as-you-go-Traffic ab 0,50 $/GB, mit Krypto bezahlt, ohne ID-Upload. Kein KYC — keine ID, kein Selfie, keine Dokumente. Mit Krypto bezahlen.
