Zurück
Tech 11 Min. Lesezeit - 20. Apr. 21 - Nicolas Coulombeau

Eine Website scrapen, die durch IP-Bann geschützt ist

Im Rahmen eines POC (proof of concept), eines persönlichen Projekts oder einer statistischen Studie kann es notwendig werden, dass Sie eine Website scrapen müssen. Scraping ist die Automatisierung der Abfrage bestimmter Daten, die von einer Website bereitgestellt werden, mittels eines Skripts. In diesem Artikel gehen wir davon aus, dass Sie Grundkenntnisse im Web-Scraping haben. 
Da die Gesetzgebung unklar und die Rechtsprechung im Bereich des Scrapings zögerlich ist, können wir Ihnen nur davon abraten, es zu kommerziellen Zwecken zu nutzen, aber wir zeigen Ihnen dennoch, wie Sie ein Scraping-System mit IP-Rotation mithilfe von Python, Scrapy und Scrapoxy.  

Fall des Client Side Rendering: Die API nutzen

Für Websites, die in Client-Side-Rendering, Sie können versuchen, die API direkt zu nutzen, die Rohdaten (oft im JSON- oder XML-Format) an Ihren Browser zurücksendet, welcher diese dann in das HTML einfügen wird. Dafür können Sie (im Entwicklungstool Ihres Browsers im Bereich „Netzwerk“) die XHR-Anfragen ansehen, die gesendet werden, wenn Sie die zu scrapende Webseite aktualisieren. Sobald Sie die Anfrage(n) gefunden haben, welche die Daten laden, die Sie abrufen möchten, müssen Sie nur noch versuchen, dieselbe Anfrage innerhalb eines Python-Skripts zu erstellen (zum Beispiel unter Verwendung der Bibliothek request). So ist kein eigentliches Scraping nötig (kein headless webdriver, parser, xpath usw.).
chrome-dev-tool-request-imdb
Hier zum Beispiel auf der IMDB-Webseite könnten wir versuchen, deren GraphQL-API zu nutzen.
Nur für den Fall, dass die API sich als zu schwierig zu nutzen erweist, setze ich die schwere Artillerie ein, um Daten zu scrapen, die mit Javascript angezeigt werden: Selenium.

Fall des Server-Side-Rendering

Für Webseiten mit Server-Side-Rendering (und die ohne API-Aufruf funktionieren), haben Sie keine andere Wahl, als das HTML der Seite über eine Parsing-Bibliothek wie Beautiful Soup und die Daten über deren xpath. 
Wenn Ihr Scraping-Projekt umfangreich ist, empfehle ich Ihnen, sich an Scrapy, ein ziemlich vollständiges Python-Framework für Crawling und Scraping, das es unter anderem ermöglicht, Ihre xpaths über ein Terminal zu testen oder diverse Plugins wie Splash, was es ermöglicht, Javascript auf Webseiten zu aktivieren.

Unser Problem: die IP-Sperre

Manchmal verwenden Webseiten Methoden zur Prävention gegenüber Scraping-Bots. Eine der gefürchtetsten Methoden, die mir lange Zeit Kopfzerbrechen bereitet hat, ist die IP-Sperre. Die gescrapte Webseite analysiert live die Anfragen, die sie erhält. Wenn sie feststellt, dass eine IP zu viele Anfragen stellt, um ein normaler menschlicher Benutzer zu sein, kann sie diese IP sperren (für eine mehr oder weniger lange Dauer). So wird Ihr Skript bei der nächsten Anfrage einen Fehler aufgrund einer 429-Antwort des Servers auslösen.

Erste Lösung – Pausen einlegen sleep

Um diese Methode zu kontern, wäre die erste einfache Lösung, die man in Betracht ziehen könnte, „Sleeps“ zwischen unsere verschiedenen Anfragen einzufügen, um die Ausführung unseres Skripts zu verlangsamen und nicht von der gescrapten Webseite erkannt zu werden. Aber angenommen, wir hätten 30'000 Seiten zu scrapen mit 30 Sekunden Sleep zwischen zwei Anfragen, müsste das Skript über 10 Tage laufen, um das Ende der Seitenliste zu erreichen. Diese Lösung wird sicherlich mit einem ausreichend grossen „Sleep“-Wert funktionieren, würde aber bei der Ausführung endlos werden.

Zweite Lösung – IP-Rotation mit Scrapoxy

Eine andere, wesentlich robustere und schnellere Lösung in der Ausführung (da sie keine Sleeps enthält) ist die Verwendung des Open-Source-Dienstes Scrapoxy. Scrapoxy ermöglicht Scraping, indem es die Anfragen über eine Reihe von Proxys umleitet. Diese Proxys sind Serverinstanzen, die vom AWS EC2-Dienst gehostet werden.
scraping-scrapoxy-gif

Zusammenfassung der in diesem Artikel untersuchten Lösung

  • Ein Spider (Scrapy-Scraping-Instanz) bewegt sich von Link zu Link („crawlt“ eine Reihe von Seiten)
  • Die Scrapy-Anfragen an den Server der Zielseite werden nicht direkt von Ihrer Maschine ausgeführt, sondern dank des Scrapoxy-Dienstes an EC2-Container weitergeleitet.
  • Sobald ein Container seine IP blockiert sieht, startet Scrapoxy eine neue Armee von Containern, um die alten, unbrauchbar gewordenen zu ersetzen.

Voraussetzungen:

Um diesem Tutorial zu folgen, müssen Sie Python und das Dienstprogramm pip auf Ihrer Maschine installiert haben.

Erstellung und Konfiguration des AWS-Kontos

Kontoerstellung

Wenn Sie noch keines haben, erstellen Sie ein Konto bei AWS um auf die verschiedenen in der AWS-Konsole verfügbaren Dienste zugreifen zu können. Insbesondere EC2, der Dienst, der Server auf Abruf bereitstellt und von Scrapoxy verwendet wird, um die Proxys zu erstellen, die unsere Anfragen weiterleiten werden. Um ein Konto zu erstellen, gehen Sie bitte hier.
Hinweis: Sie müssen Ihre Kreditkarteninformationen eingeben. Seien Sie also vorsichtig bei der Nutzung des EC2-Dienstes, der einen guten Free Tier bietet (750 Stunden kumulierte Nutzung pro Monat über alle Ihre Instanzen hinweg), aber nicht unbegrenzt kostenlos ist. Wenn Sie also 10 EC2-Instanzen parallel betreiben, werden Ihnen die Kosten nach 750 / 10 = 75 Stunden in Rechnung gestellt. 

Erstellung von IAM-Credentials

Damit Scrapoxy für Sie Ihre EC2-Instanzen verwalten darf, müssen Sie ein Schlüsselpaar generieren: access key und secret access key. Dafür gehen Sie in der AWS-Konsole zu Ihren Sicherheitsanmeldeinformationen (hier)
Klappen Sie den Bereich Access Key und klicken Sie auf Schlüssel hinzufügen. Behalten Sie den Wert deraccess key id. Klicken Sie danach auf „Show secret access key“ und notieren Sie sich den angegebenen Wert oder laden Sie die key file.
Achtung: Sie können ihn später nicht mehr anzeigen lassen. 

Erstellung einer Sicherheitsgruppe

Sie müssen auch eine Sicherheitsgruppe erstellen. Dafür gehen Sie in der AWS-Konsole zum EC2-Dienst und genauer zu den Sicherheitsgruppen (unter dieser URL, indem Sie den Parameter region durch die von Ihnen verwendete Region ersetzen: https://console.aws.amazon.com/ec2/v2/home?region=us-east-1#SecurityGroups)
Hinweis: Wir empfehlen Ihnen, die Region eu-west-1 
Klicken Sie auf die Schaltfläche „Sicherheitsgruppe erstellen“ und dann:  
  • Geben Sie „forward-proxy” in die Felder Name und Beschreibung ein. 
  • Fügen Sie eine Inbound rule, mit: 
    • Typ = Benutzerdefiniertes TCP
    • Portbereich = 3128
    • Quelle = Überall

Wahl eines AMI

Später, bei der Konfiguration von Scrapoxy, werden Sie einen «AMI» benötigen.
Wenn Sie sich in der Region eu-west-1 befinden, können Sie einen dieser drei AMI wählen, abhängig davon, welchen Instanztyp Sie für Ihr Skript erstellen möchten: 
  • t1.micro ⇒ ami-c74d0db4
  • t2.micro ⇒ ami-485fbba5
  • t2.nano ⇒ ami-06220275
Merken Sie sich den Namen des gewählten AMI für später!
Tipp: Versuchen Sie es mit dem ersten (ami-c74d0db4), der t1.micro-Instanzen generiert (die kleinstmöglichen). Denn wir werden diese Server bitten, einfache HTTP-Anfragen zu stellen; es ist nicht nötig, Hochleistungsrechner zu starten.

Installation und Konfiguration von Scrapoxy

Installation von Scrapoxy

Um Scrapoxy zu installieren, geben Sie bitte die folgenden Befehle in ein Terminal ein: sudo apt-get install build-essential sudo npm install -g scrapoxy

Konfiguration von Scrapoxy

Um Scrapoxy zu verwenden und es mit Ihrem AWS-Konto zu verbinden, müssen Sie eine Konfigurationsdatei generieren und ausfüllen. Beginnen Sie, indem Sie den folgenden Befehl eingeben:  scrapoxy init conf.json
Anschliessend in der Datei conf.json, die gerade generiert wurde: 
  • ändern Sie das password im Abschnitt commander (geben Sie ein, was Sie möchten, dieses Passwort wird für den Zugriff auf die grafische Benutzeroberfläche von Scrapoxy verwendet)
  • im Abschnitt provider, behalten Sie nur das Objekt bei, dessen type : “awsec2”
  • ersetzen Sie dann die accessKeyId und secretAccessKey dieses Objekts durch die zuvor bei AWS abgerufenen Credentials
  • ersetzen Sie ebenfalls den Wert des AMI (Feld ImageId) durch den korrekten Wert
scraping-conf-json
Sie müssen die Parameter bezüglich der Anzahl der EC2-Instanzen nicht ändern; das können Sie direkt über die grafische Benutzeroberfläche einstellen, nachdem Sie Scrapoxy gestartet haben.

Lokaler Start von Scrapoxy

Um eine Instanz von Scrapoxy auf Ihrem Rechner zu starten, geben Sie in einem neuen Terminal den Befehl ein: scrapoxy start conf.json -d
Dieses Terminal muss während der gesamten Ausführung des Skripts geöffnet bleiben, das wir Ihnen gleich vorstellen werden. Die Scrapoxy-Instanz hat die Aufgabe, die Anfragen zwischen den verschiedenen EC2-Containern zu verteilen, die Antworten zu analysieren und die Container neu zu starten, wenn eine Anfrage fehlschlägt.
Hinweis: Wenn Scrapoxy läuft, können Sie über die Adresse: http://localhost:8889/ auf eine grafische Benutzeroberfläche (GUI) zugreifen. Zudem wird der Dienst für Scrapy über die Adresse: http://localhost:8888/ zugänglich sein.

Installation und Nutzung von Scrapy

Scrapy installieren

Um zu installieren Scrapy mit dem Dienstprogramm pip, geben Sie den Befehl ein: pip install scrapy scrapoxy

Ein neues Scrapy-Projekt erstellen

Um ein neues Projekt (eine neue «Spider») zu erstellen, geben Sie in einem Terminal den Befehl ein:  scrapy startproject myscraper
Dieser Befehl erstellt einen Ordner myscraper, der diese Verzeichnisstruktur aufweist: 
scraping-arborescence
Erstellen Sie einen Ordner data im Unterordner myscraper und eine Datei scraper.py im Unterordner spider, um diese Verzeichnisstruktur zu erhalten:
scraping-arborescence-fin

Scrapoxy in die Spider integrieren

Um Scrapoxy in Ihre Spider zu integrieren und damit die Anfragen von Ihren EC2-Instanzen anstatt von Ihrem Rechner gestellt werden, fügen Sie in myscraper/myscraper/settings.py die folgenden Zeilen hinzu: language=python CONCURRENT_REQUESTS_PER_DOMAIN = 1 RETRY_TIMES = 0 # PROXY PROXY = 'http://127.0.0.1:8888/?noconnect' # SCRAPOXY API_SCRAPOXY = 'http://127.0.0.1:8889/api' API_SCRAPOXY_PASSWORD = 'CHANGE_THIS_PASSWORD' # BLACKLISTING BLACKLIST_HTTP_STATUS_CODES = [ 429 ] DOWNLOADER_MIDDLEWARES = {     'scrapoxy.downloadmiddlewares.proxy.ProxyMiddleware': 100,     'scrapoxy.downloadmiddlewares.wait.WaitMiddleware': 101,     'scrapoxy.downloadmiddlewares.scale.ScaleMiddleware': 102,     'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': None,     'scrapoxy.downloadmiddlewares.blacklist.BlacklistDownloaderMiddleware': 950 }
Wenn die von Ihnen gescrapte Website einen Statuscode zurückgibt, der von 429 abweicht, um anzuzeigen, dass Ihre IP gesperrt wurde, können Sie dies angeben, indem Sie den von der Website zurückgegebenen Statuscodewert zur Variable BLACKLIST_HTTP_STATUS_CODES hinzufügen.

Erstellung der Spider

Kommen wir nun zum Kern des Scraping-Skripts: der Spider.
Eine Scrapy-Spider besteht aus mehreren Teilen: 
  • Import der für die Skriptausführung notwendigen Module (wie bei jeder Python-Datei) 
  • Eine Scraper-Klasse: enthält die gesamte Logik des Scrapers
    • Attribute (name, custom_settings, allowed_domains, …)
    • Eine erste Methode start_requests(), die ein Iterable von scrapy.Requests zurückgibt.
    • Eine zweite Methode parse(), die für jede Antwort von scrapy.Request aufgerufen wird und deren Aufgabe es ist, die Antwort zu parsen und die gewünschten Daten daraus zu extrahieren. Sie können beispielsweise ein Iterable von Dictionaries zurückgeben, um Ihre Antworten anschliessend in einer CSV-Datei zu speichern. 
Hier ist ein Beispielcode einer Spider mit der parse()-Methode und Beispielen, wie Daten auf einer geparsten Seite abgerufen werden können. Um mehr zu erfahren, konsultieren Sie direkt die Scrapy-Dokumentation oder informieren Sie sich über den Begriff xpath (hier).
Sie können diesen Code in die Datei scraper.py kopieren, die Sie zuvor erstellt haben. Anschliessend müssen Sie nur den Code anpassen, um ihn an Ihre Zielseite anzupassen. language=python import scrapy import csv class Scraper(scrapy.Spider):   name = u'scraper'   custom_settings = {      'FEED_FORMAT': "csv",      'FEED_URI': './data.csv',      'FEED_EXPORT_ENCODING': 'utf-8'   }   allowed_domains = ['www.site-url.com']   start_urls = [f"www.site-url.com/page?id={id}" for id in range(100)]   def start_requests(self):      total = len(self.start_urls)      for i in range(total):          print(f"##############################         {i}/{total}        #############################")          url = self.start_urls[i]          yield scrapy.Request(url, self.parse, meta={"url": url})      def parse(self, response):          # HTML einer Div vollständig abrufen (HINWEIS: Kopieren/Einfügen des Ergebnisses in einen HTML-Editor,          # um die XPaths der zu sammelnden Daten einfacher zu finden)          data_0 = response.xpath("//div[@id='main-content']").text             # Das src-Attribut eines img-Tags abrufen          data_1 = response.xpath("//div[@id='main-content']/div/div/div/div[2]/div/div/div/div/img/@src").extract_first()             # Kategorietitel abrufen (Titel im title-Attribut des category-div-Tags)          # und die IDs der in der Kategorie vorhandenen Elemente (IDs in der URL des Elements)          data_2_raw = response.xpath("//category-div")          data_2 = ""          for data_2_item in data_2_raw:              title = data_2_item.xpath("./@title").extract_first()              data_2 += "{}:".format(title)              links = data_2_item.xpath("./div/div/div[2]/a/@href").extract()              for link in links:                  data_2 += "{}/".format(link.split("/")[-1].split(".")[0].split("-")[-1])              data_2 = data_2[:-1]          data_2 = data_2[:-1]          yield {              "page_url": response.meta['url'],              "data_0": data_0,              "data_1": data_1,              "data_2": data_2          }

Start der Spider

Um die Spider zu starten, führen Sie im Terminal im Stammverzeichnis des myscraper-Ordners den Befehl aus:   scrapy crawl scraper 

Crawling

Wenn Sie die Spider mit auf Ihrem Rechner aktiviertem Scrapoxy starten, müssen Sie zunächst zwei Minuten warten, bis die EC2-Container gestartet sind. (Sie können diese Wartezeit deaktivieren, um Ihr Skript einfacher über die Datei myscraper/myscraper/settings.py zu testen, indem Sie die Zeile auskommentieren language=python 'scrapoxy.downloadmiddlewares.wait.WaitMiddleware': 101)
Überwachen Sie anschliessend, dass alles wie erwartet abläuft: eine Reihe von Anfragen, die 200er-Antworten zurückgeben, und nach einer gewissen Zeit eine Anfrage, die aufgrund einer 429er-Antwort fehlschlägt, gefolgt von einem Sleep von 2-3 Minuten, um die EC2-Container neu zu starten.
Sie können die gewünschte Anzahl EC2-Instanzen jederzeit über die Scrapoxy-GUI ändern (normalerweise unter http://localhost:8889/ zugänglich). Auf dieser GUI können Sie auch die Statistiken Ihrer Anfragen seit dem Start des Dienstes einsehen.
scraping-stats-scrapoxy

Fazit

Wenn Sie drei Dinge aus diesem Artikel behalten sollten, sind es die folgenden:
  • Wenn Sie Daten von einer Website scrapen müssen, greifen Sie nicht direkt zu fortgeschrittenen Scraping-Tools! Versuchen Sie zunächst, die API der Website zu nutzen. Verwenden Sie dafür eine beliebige HTTP-Anfrage-Bibliothek Ihrer bevorzugten Sprache.
  • Wenn das nicht funktioniert, greifen Sie zum traditionelleren Scraping (Parsing von HTML, Headless Browser, ...).
  • Wenn die gescrapte Website Ihre IP blockiert, weil Sie zu viele Anfragen stellen, stehen Ihnen zwei Möglichkeiten offen:
    • Wenn Sie eine geringe Anzahl von Seiten scrapen müssen und/oder die Ausführungszeit des Skripts kein Problem darstellt, versuchen Sie, "sleep" in Ihren Code zwischen den Anfragen einzufügen.
    • Wenn das nicht funktioniert oder Sie zu viele Seiten scrapen müssen, als dass diese Technik praktikabel wäre (Skript-Ausführungszeit zu lang), dann konfigurieren Sie Scrapoxy, um die von Ihrem Skript verwendeten IPs für seine Anfragen zu wechseln.

Möchten Sie bei der Lancierung Ihres digitalen Projekts begleitet werden?

Reichen Sie Ihr Projekt jetzt ein