Was ist Scrapy und wie nutzt man es mit Proxy?

Veröffentlicht:

17 Min. Lesezeit

Acar Diveroli
Autor: Acar Diveroli
Fünf Stationen der Scrapy-Middleware-Kette: Spider, robots.txt, Retry, blauer Proxy-Würfel und der Kasten der Zielseite

Ein Skript mit Requests und BeautifulSoup läuft bei hundert Seiten ohne Probleme. Bei zehntausend Seiten ändert sich die Aufgabe selbst: Sie müssen verwalten, welche Adresse schon gecrawlt wurde, welche einen Fehler geliefert hat und wiederholt wird, wie viele Anfragen gleichzeitig an dieselbe Website gehen und wohin die Daten geschrieben werden. Scrapy ist ein Python-Framework, das all das fertig mitbringt. Auch die Proxy-Einstellung sitzt innerhalb dieser Struktur, in einer der Schichten, die jede Anfrage durchläuft.

In diesem Beitrag zeigen wir zuerst kurz, was Scrapy ist und wie es installiert wird. Danach geht es um das eigentliche Thema: die drei Wege, einen Proxy festzulegen, die Authentifizierung, den Unterschied zwischen einem rotierenden Gateway mit einem einzigen Endpunkt und einer eigenen Proxy-Liste, die Geschwindigkeitssteuerung mit DOWNLOAD_DELAY und AutoThrottle sowie das Verhalten von Scrapy bei den Antworten 429 und 503. Alle Beispiele haben wir mit Scrapy 2.19 und Python 3.13 ausgeführt, gegen die Übungsseiten von toscrape.com und einen lokalen Test-Proxy.

Was ist Scrapy und wofür wird es genutzt?

Scrapy ist ein Python-Framework, mit dem Websites gecrawlt und strukturierte Daten aus ihren Seiten gewonnen werden. Der Unterschied zu einer Bibliothek: Requests gibt Ihnen ein Werkzeug, um eine einzelne Anfrage zu senden, die Schleife bauen Sie selbst. In Scrapy ist die Schleife das Framework. Sie schreiben nur eine Klasse, die festlegt, bei welcher Adresse begonnen wird und was aus der gelieferten Seite entnommen wird. Diese Klasse heißt Spider.

Alles außerhalb des Spiders ist vorhanden: ein Scheduler, der Anfragen einreiht, ein Duplikatfilter, der dieselbe Adresse kein zweites Mal anfordert, ein asynchroner Downloader, Wiederholungsversuche, die robots.txt-Prüfung, die Begrenzung des Tempos und der Export nach JSON oder CSV. Für Selektoren werden CSS und XPath nebeneinander verwendet; den Unterschied erklären wir in CSS-Selektor oder XPath. Den Überblick über die Werkzeuge auf der Python- und der JavaScript-Seite finden Sie in Web Scraping: JavaScript oder Python?.

Wie wird Scrapy installiert?

Installieren Sie Scrapy nicht in das System-Python, sondern in eine virtuelle Umgebung, die zum Projekt gehört. Unter Windows lauten die Befehle:

bash
python -m venv venv
venv\Scripts\activate
pip install scrapy
scrapy startproject kitaplik
cd kitaplik
scrapy genspider kitaplar books.toscrape.com

Unter macOS und Linux lautet die zweite Zeile source venv/bin/activate. Der Befehl startproject erzeugt ein Gerüst mit settings.py, middlewares.py und dem Ordner spiders/. Die Datei settings.py der aktuellen Vorlage bringt drei Einstellungen bereits mit: ROBOTSTXT_OBEY = True, CONCURRENT_REQUESTS_PER_DOMAIN = 1 und DOWNLOAD_DELAY = 1. Ein neues Projekt hält sich also standardmäßig an die robots.txt und sendet ungefähr eine Anfrage pro Sekunde an dieselbe Website. Löschen Sie diese Zeilen nicht, sondern bauen Sie Ihre Einstellungen darauf auf.

Füllen Sie die von genspider angelegte Datei wie unten. Der Spider liest die Buchkarten und folgt dem Link „nächste Seite“:

python
import scrapy


class KitaplarSpider(scrapy.Spider):
    name = "kitaplar"
    allowed_domains = ["books.toscrape.com"]
    start_urls = ["https://books.toscrape.com/"]

    def parse(self, response):
        for kart in response.css("article.product_pod"):
            yield {
                "baslik": kart.css("h3 a::attr(title)").get(),
                "fiyat": kart.css("p.price_color::text").get(),
                "adres": response.urljoin(kart.css("h3 a::attr(href)").get()),
            }

        sonraki = response.css("li.next a::attr(href)").get()
        if sonraki:
            yield response.follow(sonraki, callback=self.parse)

Der Befehl scrapy crawl kitaplar -O kitaplar.json startet den Spider und schreibt das Ergebnis in eine Datei. In unserem Lauf kamen von den ersten beiden Seiten 40 Datensätze. Behalten Sie die Zeile FEED_EXPORT_ENCODING = "utf-8" aus der Vorlage bei, damit Umlaute und Währungssymbole in der Datei nicht beschädigt werden.

Wie verarbeitet Scrapy eine Anfrage?

Um zu verstehen, wohin die Proxy-Einstellung gehört, muss man den Weg einer Anfrage kennen:

  1. Der Spider erzeugt ein Request-Objekt (aus start_urls oder response.follow).
  2. Die Engine übergibt die Anfrage an den Scheduler; der Duplikatfilter sortiert bereits angeforderte Adressen aus.
  3. Ist die Anfrage an der Reihe, durchläuft sie die Kette der Downloader-Middlewares. Jede Middleware trägt eine Ordnungszahl, und die Anfrage wandert von der kleineren zur größeren Zahl: die robots.txt-Prüfung hat 100, der Wiederholungsversuch 550, HttpProxyMiddleware 750.
  4. Der Downloader schickt die Anfrage ins Netz. Enthält sie meta["proxy"], wird die Verbindung nicht zum Ziel, sondern zum Proxy geöffnet; bei HTTPS-Adressen wird über den Proxy ein CONNECT-Tunnel aufgebaut.
  5. Die Antwort läuft in umgekehrter Reihenfolge durch dieselbe Kette zurück und erreicht die Callback-Funktion des Spiders (parse).
  6. Die von der Funktion erzeugten Datensätze gehen an die Item-Pipeline, neue Anfragen wieder an den Scheduler.

Daraus folgt: Der Proxy gehört zum dritten Schritt, nicht zum Parsing-Code des Spiders. Wer auch immer meta["proxy"] füllt, die Arbeit erledigt HttpProxyMiddleware. Diese Middleware ist standardmäßig aktiv; der Abschnitt HttpProxyMiddleware in der Scrapy-Dokumentation beschreibt ihr Verhalten.

Wie wird in Scrapy ein Proxy festgelegt?

Es gibt drei Wege, und alle drei füllen am Ende dasselbe Feld meta["proxy"].

MethodeWo sie stehtGeltungsbereichWann sie passt
meta["proxy"]Im Spider, in jedem RequestNur diese AnfrageEinige Anfragen über einen anderen Ausgang senden
UmgebungsvariableIn der Shell, http_proxy / https_proxyAlle Anfragen, auch robots.txtSchneller Versuch, Ausführung ohne Codeänderung
Downloader-Middlewaremiddlewares.py + settings.pyAlle Anfragen, auch robots.txtDauerhafte Projekteinrichtung, Rotation

Pro Anfrage: das meta-Feld

python
import scrapy

PROXY = "http://user:pass@pr.proxynet.io:8000"


class KitaplarMetaSpider(scrapy.Spider):
    name = "kitaplar_meta"
    allowed_domains = ["books.toscrape.com"]

    async def start(self):
        yield scrapy.Request("https://books.toscrape.com/", meta={"proxy": PROXY})

    def parse(self, response):
        for kart in response.css("article.product_pod"):
            yield {"baslik": kart.css("h3 a::attr(title)").get()}

        sonraki = response.css("li.next a::attr(href)").get()
        if sonraki:
            # meta geht nicht von selbst auf die neue Anfrage über, es wird von Hand mitgegeben
            yield response.follow(sonraki, callback=self.parse, meta={"proxy": PROXY})

Diese Methode hat zwei Folgen, die leicht übersehen werden, und beide haben wir im Test gesehen. Erstens wird meta nicht von selbst an die nächste Anfrage weitergegeben. In dem Lauf, in dem wir meta beim Aufruf von response.follow weggelassen hatten, ging die erste Seite über den Proxy und die zweite direkt von unserer eigenen IP-Adresse aus. Zweitens ist die robots.txt-Anfrage von Scrapy keine Anfrage, die Sie geschrieben haben; sie trägt kein meta und geht ebenfalls ohne Proxy hinaus. Soll der gesamte Verkehr denselben Ausgang nehmen, verwenden Sie einen der beiden folgenden Wege.

Umgebungsvariable

HttpProxyMiddleware liest wie die Python-Standardbibliothek die Variablen http_proxy, https_proxy und no_proxy. Ohne eine Änderung am Code:

bash
export https_proxy="http://user:pass@pr.proxynet.io:8000"
export http_proxy="$https_proxy"
scrapy crawl kitaplar

Enthält eine Anfrage zusätzlich meta["proxy"], hat dieser Wert Vorrang vor der Umgebungsvariable und ignoriert die no_proxy-Liste. Wie die Variablen unter Windows, macOS und Linux gesetzt werden und was es mit Groß- und Kleinschreibung auf sich hat, steht in Proxy-Nutzung mit wget; hier wiederholen wir es nicht.

Für das ganze Projekt: eine kleine Middleware

Für eine dauerhafte Einrichtung genügt eine Middleware aus wenigen Zeilen, die die Adresse aus der Umgebung liest, statt sie in den Code zu schreiben:

python
# kitaplik/middlewares.py
from scrapy.exceptions import NotConfigured


class TekProxyMiddleware:
    """Wendet die eine Proxy-Adresse aus den Einstellungen auf alle Anfragen an."""

    def __init__(self, adres):
        self.adres = adres

    @classmethod
    def from_crawler(cls, crawler):
        adres = crawler.settings.get("PROXY_ADRESI")
        if not adres:
            raise NotConfigured
        return cls(adres)

    def process_request(self, request):
        request.meta.setdefault("proxy", self.adres)
        return None
python
# kitaplik/settings.py
import os

PROXY_ADRESI = os.environ.get("PROXY_ADRESI")
DOWNLOADER_MIDDLEWARES = {
    "kitaplik.middlewares.TekProxyMiddleware": 610,
}

Dank setdefault bleibt ein meta["proxy"] erhalten, das Sie von Hand in eine Anfrage geschrieben haben. Ist PROXY_ADRESI nicht gesetzt, schaltet sich die Middleware ab und der Spider läuft ohne Proxy. Die Ordnungszahl muss kleiner als 750 sein; so schreiben zuerst Sie die Adresse, und die Zugangsdaten zerlegt danach HttpProxyMiddleware. In Beispielen für ältere Versionen sehen Sie die Signatur als process_request(self, request, spider); in der aktuellen Dokumentation gibt es den Parameter spider nicht, und das Beispiel läuft in dieser Form unter Scrapy 2.19.

Wie funktioniert die Authentifizierung?

HttpProxyMiddleware trennt den Teil user:pass aus der Adresse heraus, kodiert ihn mit Base64 und fügt ihn der Anfrage als Header Proxy-Authorization: Basic … hinzu. In meta["proxy"] bleibt die Adresse ohne Zugangsdaten zurück; Ihr Passwort sehen Sie im Log nicht. Enthält das Passwort @, : oder /, schreiben Sie es prozentkodiert (%40 statt @); die Middleware dekodiert den Wert vor dem Senden.

In unserem Versuch mit falschem Passwort schrieb Scrapy diese Zeile:

text
TunnelError: Could not open CONNECT tunnel with proxy 127.0.0.1:8120 [{'status': 407, 'reason': b'Proxy Authentication Required'}]

Bei HTTPS-Adressen kommt 407 nicht als Antwort an, sondern als Ausnahme, weil der Tunnel nicht aufgebaut werden konnte. Zu beachten ist: Die Retry-Middleware wertet diese Ausnahme als vorübergehenden Fehler und versucht dieselbe Anfrage noch zweimal. Ein falsches Passwort wird durch Wiederholung nicht richtig; sehen Sie im Log TunnelError und 407, stoppen Sie den Crawl und korrigieren Sie die Zugangsdaten. Nutzen Sie statt Benutzername und Passwort eine IP-Whitelist, wird die Adresse ohne Zugangsdaten geschrieben, in der Form http://pr.proxynet.io:8000. Den Unterschied zwischen beiden Verfahren erklärt Proxy-Authentifizierung: User:Pass oder IP-Whitelist.

Braucht man für die Rotation eine Middleware?

Die Antwort hängt von der Art des Proxys ab, den Sie haben.

Mit einem rotierenden Gateway nicht. Bei einem Rotating-Proxy-Dienst verbinden Sie sich mit einem einzigen Endpunkt, und das Gateway wechselt die Ausgangs-IP. Auf der Scrapy-Seite genügt die TekProxyMiddleware von oben oder eine einzige Umgebungsvariable; eine Liste zu führen, die nächste Adresse zu wählen und defekte Adressen auszusortieren, ist nicht Ihre Aufgabe. Bei Crawls mit Residential-Proxy ist das der typische Aufbau. Wie die Rotationsmodi arbeiten, steht in unserem Beitrag zur IP-Rotation.

Haben Sie eine Liste fester Adressen (zum Beispiel einige Adressen vom Typ Datacenter-Proxy oder ISP-Proxy), übernimmt eine Middleware die Verteilung. Das folgende Beispiel verwendet die Adressen der Reihe nach, lässt eine Adresse, die mehrmals hintereinander Fehler liefert, eine Weile ruhen und schreibt die Zähler in die Scrapy-Statistik:

python
# kitaplik/middlewares.py
import time
from urllib.parse import urlsplit

from scrapy.exceptions import IgnoreRequest, NotConfigured


def anahtar(proxy_adresi):
    parca = urlsplit(proxy_adresi)
    return f"{parca.hostname}:{parca.port}"


class ProxyHavuzuMiddleware:
    """Weist jeder Anfrage den nächsten Proxy der Liste zu und lässt einen wiederholt fehlerhaften ruhen."""

    def __init__(self, adresler, hata_siniri, dinlenme, stats):
        self.adresler = adresler
        self.hata_siniri = hata_siniri
        self.dinlenme = dinlenme
        self.stats = stats
        self.sira = 0
        self.hatalar = {anahtar(a): 0 for a in adresler}
        self.kapali = {}  # Schlüssel -> Zeitpunkt der Wiederfreigabe

    @classmethod
    def from_crawler(cls, crawler):
        adresler = crawler.settings.getlist("PROXY_LISTESI")
        if not adresler:
            raise NotConfigured
        return cls(
            adresler,
            crawler.settings.getint("PROXY_HATA_SINIRI", 3),
            crawler.settings.getfloat("PROXY_DINLENME", 60.0),
            crawler.stats,
        )

    def sec(self):
        simdi = time.monotonic()
        for _ in self.adresler:
            adres = self.adresler[self.sira % len(self.adresler)]
            self.sira += 1
            if self.kapali.get(anahtar(adres), 0) <= simdi:
                return adres
        return None

    def process_request(self, request):
        adres = self.sec()
        if adres is None:
            self.stats.inc_value("proxy_havuzu/hepsi_dinleniyor")
            raise IgnoreRequest("Alle Proxys der Liste ruhen gerade")
        request.meta["proxy"] = adres
        request.meta["proxy_anahtari"] = anahtar(adres)
        return None

    def process_response(self, request, response):
        kim = request.meta.get("proxy_anahtari")
        if kim:
            self.hatalar[kim] = 0
            self.stats.inc_value(f"proxy_havuzu/yanit/{kim}")
        return response

    def process_exception(self, request, exception):
        kim = request.meta.get("proxy_anahtari")
        if not kim:
            return None
        self.hatalar[kim] += 1
        self.stats.inc_value(f"proxy_havuzu/hata/{kim}")
        if self.hatalar[kim] >= self.hata_siniri:
            self.kapali[kim] = time.monotonic() + self.dinlenme
            self.hatalar[kim] = 0
        return None
python
# kitaplik/settings.py
PROXY_LISTESI = [
    "http://user:pass@203.0.113.10:8000",
    "http://user:pass@203.0.113.11:8000",
    "http://user:pass@203.0.113.12:8000",
]
DOWNLOADER_MIDDLEWARES = {
    "kitaplik.middlewares.ProxyHavuzuMiddleware": 610,
}

Wir haben das Beispiel mit drei lokalen Proxys ausprobiert, zwei davon in Betrieb, einer abgeschaltet. Alle hundert Datensätze kamen vollständig an; die Anfragen verteilten sich mit 6 und 5 auf die beiden funktionierenden Adressen, die abgeschaltete Adresse wurde nach drei Verbindungsfehlern aus dem Umlauf genommen, und Scrapy wiederholte die fehlgeschlagenen Anfragen über die anderen Adressen.

Die Ordnungszahl ist hier nicht beliebig. Im ersten Versuch hatten wir die Middleware auf 350 gesetzt, und der Fehlerzähler lief nie: Ausnahmen wandern in umgekehrter Reihenfolge durch die Kette, und wenn die Retry-Middleware auf 550 die Ausnahme abfängt und eine neue Anfrage zurückgibt, erfahren Middlewares mit kleinerer Zahl nichts davon. Ein Wert zwischen 550 und 750 (im Beispiel 610) erfüllt beide Bedingungen: Sie sehen den Fehler vor dem Wiederholungsversuch, und die Zugangsdaten verarbeitet HttpProxyMiddleware nach Ihnen. Wie dieselbe Aufgabe mit Requests von Hand gelöst wird, zeigt Proxys in Python rotieren.

Wie werden DOWNLOAD_DELAY und AutoThrottle eingestellt?

Ein Proxy ändert, von wo die Anfrage ausgeht; die Last auf dem Zielserver ändert er nicht. Die Last bestimmen diese Einstellungen:

python
# kitaplik/settings.py
USER_AGENT = "kitaplik-bot/1.0 (+https://ornek.com/bot)"
ROBOTSTXT_OBEY = True

CONCURRENT_REQUESTS = 8
CONCURRENT_REQUESTS_PER_DOMAIN = 2
DOWNLOAD_DELAY = 1
DOWNLOAD_TIMEOUT = 30

AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 2
AUTOTHROTTLE_MAX_DELAY = 30
AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
AUTOTHROTTLE_DEBUG = False

RETRY_ENABLED = True
RETRY_TIMES = 2
RETRY_HTTP_CODES = [429, 500, 502, 503, 504, 408]
  • CONCURRENT_REQUESTS_PER_DOMAIN ist die Zahl der Anfragen, die gleichzeitig an dieselbe Domain offen sein dürfen. Der Standardwert des Frameworks ist 8, die Projektvorlage senkt ihn auf 1. Was Nebenläufigkeit bedeutet, erklären wir in Concurrency und Parallelism.
  • DOWNLOAD_DELAY ist die Wartezeit zwischen zwei Anfragen an dieselbe Domain in Sekunden. Scrapy versieht diesen Wert standardmäßig mit einer zufälligen Schwankung; die Anfragen gehen nicht im Takt einer Uhr hinaus.
  • AutoThrottle passt die Wartezeit an die Antwortzeit des Servers an. Nach dem Algorithmus in der Dokumentation ist die Zielwartezeit die Antwortlatenz geteilt durch AUTOTHROTTLE_TARGET_CONCURRENCY; die neue Wartezeit ist der Mittelwert aus der vorherigen Wartezeit und diesem Ziel. Antworten außer 200 können die Wartezeit nicht senken. Sie fällt nie unter DOWNLOAD_DELAY und steigt nie über AUTOTHROTTLE_MAX_DELAY.
  • DOWNLOAD_TIMEOUT beträgt standardmäßig 180 Sekunden. Verkürzen Sie den Wert bei der Arbeit über einen Proxy, damit eine Verbindung, die nicht antwortet, nicht drei Minuten lang hängt.

Mit AUTOTHROTTLE_DEBUG = True sehen Sie pro Antwort eine Zeile. In unserem Lauf begann die Wartezeit bei 2.000 ms, sank auf 1.072 ms, als der Server in 145 ms antwortete, dann auf 1.000 ms, die Untergrenze durch DOWNLOAD_DELAY, und blieb dort:

text
slot: books.toscrape.com | conc: 1 | delay: 2000 ms (+0)   | latency: 446 ms
slot: books.toscrape.com | conc: 1 | delay: 1072 ms (-927) | latency: 145 ms
slot: books.toscrape.com | conc: 1 | delay: 1000 ms (-72)  | latency: 144 ms

Ist ROBOTSTXT_OBEY aktiv, lädt Scrapy für jede Domain zuerst die Datei /robots.txt und sortiert nicht erlaubte Adressen aus, ohne sie anzufordern. Wie die Datei gelesen wird, steht in Was ist robots.txt und wie liest man die Datei?, der rechtliche Rahmen des Crawlings in Ist Web Scraping legal?. Eine Adresse im Feld USER_AGENT, unter der Sie erreichbar sind, sorgt dafür, dass der Betreiber der Website Ihnen schreiben kann, wenn er ein Problem sieht, statt Sie zu sperren.

Was macht Scrapy bei den Antworten 429 und 503?

Die Retry-Middleware wiederholt standardmäßig die Codes 500, 502, 503, 504, 522, 524, 408 und 429 sowie Verbindungsfehler; RETRY_TIMES = 2 bedeutet zusammen mit der ersten Anfrage insgesamt drei Versuche. Eine wiederholte Anfrage kehrt mit niedrigerer Priorität in die Warteschlange zurück.

Zwei Grenzen sollte man kennen. Der Wiederholungsversuch von Scrapy liest den Header Retry-After nicht und wendet zwischen den Versuchen keine exponentielle Wartezeit an; den Abstand bestimmen weiterhin DOWNLOAD_DELAY und AutoThrottle. Weil AutoThrottle die Wartezeit bei Antworten außer 200 nicht senkt, steigt das Tempo in einer Welle von 429 nicht von selbst, es sinkt aber unter Umständen auch nicht automatisch weit genug. Steigt der Anteil von 429 im Log, ist die richtige Reaktion, CONCURRENT_REQUESTS_PER_DOMAIN zu senken und DOWNLOAD_DELAY zu erhöhen. Der Versuch, ein Ratenlimit mit mehr IPs zu überschreiten, löst das Problem nicht, er erhöht die Last auf der Website. Die Bedeutung der Codes und das richtige Warten mit Retry-After finden Sie in HTTP-Statuscodes beim Web Scraping, die Logik des Ratenlimits auf der Seite der Website in unserem Beitrag zu 429 Too Many Requests.

Lässt sich in Scrapy ein SOCKS5 Proxy verwenden?

Mit dem Standard-Downloader nicht. In dem Versuch, in dem wir eine socks5://-Adresse in meta["proxy"] geschrieben hatten, wartete die Anfrage ohne jede Antwort bis zum Timeout. Der zweite, auf httpx basierende Downloader von Scrapy, HttpxDownloadHandler, unterstützt SOCKS5 dagegen seit Version 2.17. Führen Sie zur Einrichtung pip install "scrapy[httpx]" aus und ergänzen Sie die Einstellungen um:

python
DOWNLOAD_HANDLERS = {
    "http": "scrapy.core.downloader.handlers._httpx.HttpxDownloadHandler",
    "https": "scrapy.core.downloader.handlers._httpx.HttpxDownloadHandler",
}

Mit dieser Einstellung funktionierte eine Adresse der Form socks5://user:pass@… in unserem Test samt Authentifizierung. Die Scrapy-Dokumentation kennzeichnet diesen Downloader als experimentell und empfiehlt ihn noch nicht für den Produktivbetrieb; sie merkt außerdem an, dass er für jede Proxy-Adresse einen eigenen Verbindungspool öffnet. Bleiben Sie bei einem HTTP Proxy, wenn SOCKS5 nicht zwingend ist. Die Unterschiede der Protokolle stehen in SOCKS und HTTP Proxy im Vergleich.

Was tun bei Seiten, die mit JavaScript geladen werden?

Scrapy lädt das HTML der Seite und führt kein JavaScript aus. Werden die Daten erst im Browser nachgeladen, sehen Sie sich zuerst den JSON-Endpunkt an, den die Seite im Hintergrund aufruft; meist lässt sich diese Adresse direkt mit Scrapy anfordern. Ist das nicht möglich, öffnet das Plugin scrapy-playwright ausgewählte Anfragen in einem echten Browser. Achtung: In diesem Plugin wird der Proxy nicht über meta["proxy"] angegeben, sondern über die Optionen beim Browserstart oder im Kontext (context). Die Einzelheiten stehen in Was ist Playwright und wie nutzt man es mit Proxy?, wie sich Seitentypen unterscheiden lassen, in Statische und dynamische Seiten.

Scrapy, BeautifulSoup oder Selenium?

Die drei sind verschiedene Teile derselben Aufgabe; deshalb wird der Vergleich oft falsch aufgebaut.

ScrapyRequests + BeautifulSoupSelenium
Was es istCrawling-FrameworkHTTP-Client + HTML-ParserBrowser-Automatisierung
Anfragewarteschlange und NebenläufigkeitVorhandenSchreiben Sie selbstSchreiben Sie selbst
Wiederholungsversuch, Ratenlimit, robots.txtPer Einstellung aktivierbarSchreiben Sie selbstSchreiben Sie selbst
JavaScript ausführenNein (mit Plugin)NeinJa
Proxy-Angabemeta["proxy"] oder MiddlewareParameter proxies=Option beim Browserstart
LernaufwandMittelNiedrigMittel
Passende AufgabeViele Seiten, wiederkehrendes CrawlingEinmalig, wenige SeitenAnmeldung, Klicks, dynamische Inhalte

BeautifulSoup ist nur ein Parser und lässt sich auch innerhalb von Scrapy verwenden. Selenium öffnet für jede Seite einen vollständigen Browser und verarbeitet deshalb auf derselben Hardware deutlich weniger Seiten; sinnvoll ist es nur für die Schritte, die wirklich einen Browser brauchen. Die Proxy-Einrichtung für Selenium steht in Selenium mit Proxy, der Vergleich der beiden Browser-Werkzeuge in unserem Beitrag Playwright oder Selenium.

Einsatzbereiche

  • Preis- und Bestandsüberwachung: Dieselbe Produktliste wird jeden Tag gecrawlt; der Aufbau von Scrapy eignet sich für zeitgesteuerte Läufe und passt zu dieser Aufgabe. Den Aufbau beschreibt unsere Seite zur Preisüberwachung, ein lauffähiges Beispiel unser Beitrag zur Beobachtung von Wettbewerberpreisen im E-Commerce.
  • Crawling innerhalb einer Website und Indexerstellung: Die Klasse CrawlSpider, die Links folgt und alle Seiten durchläuft, ist ein fertiger Ausgangspunkt für Aufgaben mit einem Web Crawler.
  • Katalogerfassung über mehrere Websites: Ein eigener Spider pro Website, eine gemeinsame Pipeline und eine gemeinsame Proxy-Einstellung. Die allgemeine Architektur steht auf unserer Seite zum Data Scraping.
  • Paginierte Listen: Die Muster „Weiter“-Link, Seitennummer und Cursor behandelt unser Beitrag zur Paginierung beim Web Scraping.

Häufige Fehler

  • Den Proxy nur in die erste Anfrage schreiben. meta wird nicht an spätere Anfragen weitergegeben; ab der zweiten Seite läuft der Crawl von Ihrer eigenen IP-Adresse aus weiter, und im Log fällt das nicht auf.
  • Die Ordnungszahl der Middleware beliebig wählen. Bei einer Zahl über 750 werden die Zugangsdaten nicht zerlegt und die Anfrage scheitert mit dem Fehler invalid hostname, bei einer Zahl unter 550 sehen Sie keine Verbindungsfehler.
  • Die Zeilen ROBOTSTXT_OBEY, DOWNLOAD_DELAY und Nebenläufigkeit aus der Vorlage löschen. Die nackten Standardwerte des Frameworks (robots.txt aus, keine Wartezeit, 8 Anfragen pro Domain) sind schnell genug, um eine kleine Website zu belasten.
  • Den Crawl trotz 407 weiterlaufen lassen. Jede Anfrage wird dreimal versucht, keine gelingt; korrigieren Sie zuerst die Zugangsdaten.
  • Auf ein rotierendes Gateway zusätzlich eine Rotations-Middleware setzen. Das Gateway erledigt diese Arbeit bereits; eine zweite Schicht erschwert nur die Fehlersuche.
  • DOWNLOAD_TIMEOUT auf dem Standardwert lassen. Eine einzige Verbindung, die nicht antwortet, belegt einen Slot drei Minuten lang.
  • Das Passwort in settings.py schreiben und ins Repository übertragen. Lesen Sie die Adresse aus einer Umgebungsvariable.

Entscheidungshilfe

BedarfEmpfehlung
Einmalige Aufgabe mit wenigen SeitenRequests + BeautifulSoup genügt, Scrapy ist nicht nötig
Tausende Seiten, regelmäßig wiederholtScrapy-Projekt, die Einstellungen der Vorlage bleiben erhalten
Der gesamte Verkehr soll über den Proxy laufenTekProxyMiddleware oder die Umgebungsvariable https_proxy
Nur bestimmte Anfragen über einen anderen Ausgangmeta["proxy"] in diesen Anfragen
Last auf viele IPs verteilenRotierendes Gateway, eine Adresse, keine Middleware
Sie haben eine feste Proxy-ListeProxyHavuzuMiddleware, Ordnungszahl zwischen 550 und 750
Dieselbe IP für die ganze SitzungSticky-Proxy und eine Adresse
429 nimmt im Log zuNebenläufigkeit senken, DOWNLOAD_DELAY erhöhen, AutoThrottle einschalten
SOCKS5 ist zwingendHttpxDownloadHandler (experimentell)
Die Daten kommen per JavaScriptZuerst der JSON-Endpunkt, sonst scrapy-playwright

Häufige Fragen

Was ist ein Scrapy-Spider?

Ein Spider ist eine Python-Klasse, die von scrapy.Spider abgeleitet ist und zwei Dinge festlegt: von welchen Adressen der Crawl ausgeht und welche Daten und welche neuen Links aus der Antwort entnommen werden. Warteschlange, Download und Fehlerbehandlung sind Aufgabe des Frameworks, nicht des Spiders.

Muss man für Scrapy Python können?

Ja. Ein Spider ist eine Python-Klasse, und Sie arbeiten mit Selektoren, Schleifen und Dictionarys. Wer die Grundlagen von Python kennt, kann einen ersten Spider schreiben; für Middleware und Pipeline sollte man mit Klassen vertraut sein.

Wie erkenne ich, dass der Proxy wirklich verwendet wird?

Senden Sie mit Scrapy eine Anfrage an einen Dienst, der Ihre IP-Adresse als JSON zurückgibt, und vergleichen Sie die Adresse in der Antwort mit Ihrer eigenen IP. Auch der Wert response.meta.get("proxy"), in parse ins Log geschrieben, zeigt, welche Anfrage über welchen Proxy ging. Allgemeine Verfahren stehen in Funktioniert mein Proxy? So testen Sie einen Proxy.

Was ist schneller, Scrapy oder Selenium?

Auf derselben Hardware verarbeitet Scrapy deutlich mehr Seiten, weil es keinen Browser öffnet und Anfragen asynchron sendet. Das Tempo zählt nur, wenn die Seite kein JavaScript braucht; entstehen die Daten im Browser, sieht Scrapy allein sie nicht.

Wird DOWNLOAD_DELAY überflüssig, wenn AutoThrottle aktiv ist?

Nein, dieser Wert legt die Untergrenze fest. AutoThrottle senkt die Wartezeit nie unter DOWNLOAD_DELAY. Bei einem Server, der schnell antwortet, fällt die Wartezeit auf diese Untergrenze und bleibt dort; wer sich mit DOWNLOAD_DELAY = 0 auf AutoThrottle verlässt, sendet an schnelle Server deshalb Anfragen fast ohne Pause.

Verhindert eine andere IP bei jeder Anfrage eine Sperre?

Für sich genommen nicht. Websites messen das Tempo nicht nur pro IP, sondern auch pro Sitzung, Cookie und Verhalten. Rotation verteilt die Last auf viele Ausgänge; sinnvoll wird sie zusammen mit der Beachtung der robots.txt, niedriger Nebenläufigkeit und einem ehrlichen User-Agent. Die legitimen Verfahren haben wir in Web Scraping ohne Sperren zusammengestellt.

Fazit

In Scrapy ist der Proxy das Feld meta["proxy"] der Anfrage, und Sie können es auf drei Wegen füllen: von Hand pro Anfrage, mit einer Umgebungsvariable oder mit einer Downloader-Middleware. Soll der gesamte Verkehr einschließlich robots.txt denselben Ausgang nehmen, ist einer der beiden letzten Wege nötig. Mit einem rotierenden Gateway genügt eine Adresse; haben Sie eine feste Liste, übernimmt eine Middleware mit einer Ordnungszahl zwischen 550 und 750 die Verteilung und das Zählen der Fehler. Das Tempo bestimmt nicht der Proxy, sondern DOWNLOAD_DELAY, die Nebenläufigkeit pro Domain und AutoThrottle; behalten Sie die vorsichtigen Einstellungen der Vorlage bei. Die Proxy-Typen, die zu Ihren Crawling-Aufgaben passen, finden Sie bei unseren Proxy-Diensten.

ChatGPT fragenClaude fragen