Zum Inhalt

Web-Scraping-Geschwindigkeit: Prozesse, Threads und Async

Als Web-Scraping-Experte mit über 5 Jahren Erfahrung habe ich aus erster Hand gesehen, wie langsame und ineffiziente Scraper Projekte gravierend beeinträchtigen können. Aber mit den richtigen Optimierungen können Sie Ihre Python-Web-Scraper um Größenordnungen beschleunigen.

In diesem umfassenden Leitfaden teile ich die Techniken, die ich gelernt habe, um Ihnen dabei zu helfen, die Scraping-Geschwindigkeit mithilfe von Multiprocessing, Multithreading und Asyncio zu steigern.

Diagnose von Leistungsengpässen

Meiner Erfahrung nach gibt es zwei Hauptverursacher, die die Leistung des Web Scrapers beeinträchtigen:

E/A-gebundene Aufgaben: Vorgänge, die das Warten auf externe Ressourcen erfordern, z. B. das Senden von HTTP-Anfragen oder das Abrufen von Daten aus einer Datenbank. Diese Aufgaben blockieren die Codeausführung, während sie auf eine Antwort warten.

CPU-gebundene Aufgaben: Vorgänge, die umfangreiche Rechenleistung erfordern, wie das Parsen und Extrahieren von Informationen aus HTML, das Konvertieren von Dateien, die Bildverarbeitung usw. Diese Aufgaben maximieren die CPU-Auslastung.

Von den beiden verursachen I/O-gebundene Aufgaben tendenziell mehr Verlangsamungen, da Scraper ständig Anfragen stellen und auf Antworten warten. Aber auch CPU-Aufgaben wie das Parsen dürfen nicht ignoriert werden.

Um zu beurteilen, wo Ihr Scraper fehlt, verwenden Sie die integrierte Funktion von Python timeit Modul zum Isolieren der langsamen Teile:

import timeit

# Time a request

timeit.timeit(lambda: requests.get("http://example.com"), number=50)
# 31.23 seconds

# Time parsing
timeit.timeit(lambda: parse_html(content), number=50)  
# 22.12 seconds

Dadurch lässt sich erkennen, ob E/A-Vorgänge wie Anfragen oder CPU-Aufgaben wie Parsing die meiste Zeit in Anspruch nehmen.

Strategien zur Skalierung von Python-Scrapern

Sobald Sie die Engpässe identifiziert haben, sind hier die besten Strategien, die ich gefunden habe, um sie zu optimieren:

Für E/A-gebundene Aufgaben:

  • Verwenden Sie Asyncio, um I/O gleichzeitig ohne Blockierung auszuführen

Für CPU-gebundene Aufgaben:

  • Nutzen Sie Multiprocessing, um die Arbeit über mehrere CPU-Kerne hinweg zu parallelisieren

Python bietet fantastische native Tools zur Implementierung dieser Ansätze. Lassen Sie uns sie im Detail besprechen:

Asyncio: Parallelität für E/A-gebundene Aufgaben

Wenn Ihr Scraper ständig auf E/A-Vorgänge wie den Abschluss von Anforderungen wartet, können Sie mit Asyncio diese Zeitverschwendung vermeiden, indem Sie E/A gleichzeitig ausführen.

Betrachten Sie diesen Synchronschaber:

# Synchronous Scraper

import requests
import time

start = time.time()

for _ in range(50):
  requests.get("http://example.com")

end = time.time()  
print(f"Time taken: {end - start:.2f} secs")

# Time taken: 31.14 secs

Die Bearbeitung von 30 Anfragen dauert über 50 Sekunden. Die meiste Zeit verbringt man nur damit, untätig auf Antworten zu warten.

Jetzt machen wir es mit asyncio asynchron:

# Asyncio Scraper

import asyncio
import httpx
import time

async def asyn_get(url):
  async with httpx.AsyncClient() as client:
    return await client.get(url)

start = time.time()

loop = asyncio.get_event_loop()
tasks = [loop.create_task(asyn_get("http://example.com")) for _ in range(50)]
wait_tasks = asyncio.wait(tasks)
loop.run_until_complete(wait_tasks)

end = time.time()
print(f"Time taken: {end - start:.2f} secs")

# Time taken: 1.14 secs

Durch die Verwendung von Asyncio können wir alle Anfragen gleichzeitig stellen, ohne warten zu müssen. Dies sorgt für eine enorme Beschleunigung bei I/O-intensiven Arbeitslasten.

Meiner Erfahrung nach gibt es hier einige Tipps für den effektiven Einsatz von Asyncio:

  • Warten Sie immer auf asynchrone Anrufe mit await
  • Verwenden Sie die asyncio.gather() um mehrere asynchrone Aufgaben zu kombinieren
  • Erstellen Sie Aufgaben mit loop.create_task() statt nackt async Anrufe
  • Synchronisierungscode mit umschließen asyncio.to_thread()
  • Verwenden Sie asynchrone Bibliotheken wie httpx für asynchrone E/A

Asyncio eignet sich hervorragend zur Optimierung von Scrapern, die große Mengen an I/O-Vorgängen ausführen. Lassen Sie uns als Nächstes besprechen, wie CPU-Engpässe beschleunigt werden können.

Multiprocessing: CPU-Workloads parallelisieren

Während Asyncio bei I/O hilft, habe ich festgestellt, dass Multiprocessing die effektivste Möglichkeit ist, die CPU-Leistung für Parsing, Datenverarbeitung und Berechnungen zu optimieren.

Moderne CPUs verfügen über mehrere Kerne, die eine parallele Ausführung ermöglichen. Meine aktuelle Maschine hat 8 Kerne:

import multiprocessing
print(multiprocessing.cpu_count())

# 8

Um alle diese Kerne zu nutzen, können wir Multiprocessing verwenden, um die Arbeit auf mehrere Python-Prozesse zu verteilen.

Hier ist ein Beispiel zum Vergleich der seriellen mit der parallelen Verarbeitung:

# Serial Processing

import time
from slugify import slugify

start = time.time()

articles = ["Article One","Article Two",..."Article One Thousand"]

for title in articles:
  slugify(title)

print(f"Serial time: {time.time() - start:.2f} secs")

# Serial time: 5.14 sec

Dies läuft auf nur 1 Kern. Lassen Sie uns mit Multiprocessing parallelisieren:

# Parallel Processing 

from multiprocessing import Pool
import time
from slugify import slugify

start = time.time()

with Pool(8) as p:
  p.map(slugify, articles)

print(f"Parallel time: {time.time() - start:.2f} secs")

# Parallel time: 1.04 secs

Durch den Einsatz eines Pools von 8 Mitarbeitern konnten wir die Daten durch Nutzung aller verfügbaren CPU-Kerne mehr als fünfmal schneller verarbeiten!

Einige häufige CPU-Engpässe bei Scrapern:

  • Parsen von HTML/XML-Dokumenten
  • Text und Daten mit Regex extrahieren
  • Kodierung/Dekodierung geschabter Medien
  • Crawlen und Verarbeiten von Sitemaps
  • Komprimieren von Scraped-Daten

Durch die Mehrfachverarbeitung können Sie diese Aufgaben einfach parallelisieren, um die Verarbeitungszeit erheblich zu verkürzen.

Kombination von Asyncio und Multiprocessing

Für eine optimale Leistung empfehle ich, in Ihren Scrapern sowohl Asyncio als auch Multiprocessing zu kombinieren.

Hier ist eine Vorlage, die sehr gut funktioniert:

  1. Erstellen Sie ein async_scrape() Funktion, die E/A-gebundene Arbeiten wie das Erstellen von Anforderungen mithilfe von Asyncio verarbeitet.

  2. Telefon async_scrape() aus einem Multiprocessing-Pool, um es parallel auf mehreren Kernen auszuführen.

Dadurch können Sie sowohl die I/O- als auch die CPU-Parallelität maximieren!

Hier ist ein Beispiel:

import asyncio
from multiprocessing import Pool
import httpx
import time

async def async_scrape(urls):

  async with httpx.AsyncClient() as client:

    tasks = [client.get(url) for url in urls]
    results = await asyncio.gather(*tasks)

    # CPU-heavy processing
    for data in results:
      analyze_data(data)

def multiproc_wrapper(urls):
  asyncio.run(async_scrape(urls))

if __name__ == "__main__":

  urls = [# List of urls

  start = time.time()  

  with Pool(8) as p:
    p.map(multiproc_wrapper, batched_urls)

  print(f"Total time: {time.time() - start:.2f} secs")

Wir stapeln URLs in Gruppen und crawlen sie gleichzeitig mit asyncio async_scrape()und verarbeiten Sie die Stapel parallel mithilfe eines Multiprocessing-Pools.

Dies bietet enorme Skalierungsmöglichkeiten durch Optimierung sowohl der I/O- als auch der CPU-Leistung.

Vergleich der Skalierungsoptionen

Zusammenfassend finden Sie hier einen Überblick über die verschiedenen Parallelitätsoptionen in Python:

AnsatzSpeedupLuftüberwachungOben
MultiprozessingSehr hochCPU-gebundene AufgabenHigh
MultithreadingModeratE/A-gebundene AufgabenSneaker
AsyncioSehr hochE/A-gebundene AufgabenSneaker

Basierend auf umfangreichen Benchmarking- und Praxiserfahrungen habe ich herausgefunden Mehrfachverarbeitung und Asyncio bieten die beste Leistung beim Web-Scraping.

Multiprocessing bietet hervorragende Parallelität für CPU-gebundene Workloads mit einer 8- bis 10-fachen Beschleunigung auf einem 8-Kern-Computer.

Mittlerweile bietet asyncio eine noch schnellere asynchrone I/O-Verarbeitung und ermöglicht so Tausende von Anfragen pro Sekunde in einem einzelnen Thread.

Beides zu kombinieren funktioniert also unglaublich gut. Asyncio macht das Warten auf E/A überflüssig, während Multiprocessing Sparsing und Datenverarbeitung auf alle Kerne verteilt.

Benchmarking der Asyncio-Leistung

Um die reine Leistung von Asyncio zu demonstrieren, habe ich synchrones und asynchrones Scraping von 1,000 URLs auf meinem Computer verglichen:

Synchrone:

1000 URLs scraped sequentially
Total time: 63.412 seconds

Asyncio:

1000 URLs scraped asynchronously 
Total time: 1.224 seconds

Das ist über 50-mal schneller bei gleichem Arbeitsaufwand!

Tatsächlich zeigen Benchmarks, dass Asyncio Tausende von Anfragen pro Sekunde in einem einzelnen Thread erreichen kann.

Hier ist eine AsyncIO-Benchmark-Tabelle von Excellent httpx-Bibliothek:

Unser AnsatzAnfragen/Sek
Asyncio15,500
gevent14,000
Tornado12,500

Wie Sie sehen, bietet Asyncio einen unglaublichen Durchsatz für E/A-Vorgänge.

Nutzen Sie es also für alle I/O-lastigen Arbeitsabläufe, wie zum Beispiel gleichzeitige Anfragen oder das Lesen von Dateien in Ihren Scrapern.

Nutzung von Scraping-Diensten

Nachdem Sie nun Techniken wie Asyncio und Multiprocessing verstanden haben, fragen Sie sich vielleicht: Lohnt es sich, das alles selbst zu erstellen?

In vielen Fällen würde ich empfehlen, einen Web-Scraping-API-Dienst in Betracht zu ziehen SchaberAPI or Scrapfly.

Diese Dienste übernehmen für Sie die gesamte schwere Arbeit der Skalierung und Optimierung. Hier sind einige Vorteile:

Parallelität und Geschwindigkeit

Dienste wie ScraperAPI und Scrapfly verfügen über eine optimierte Infrastruktur, die auf maximale Parallelität ausgelegt ist. Übergeben Sie einfach eine Liste von URLs, und ihre Systeme verarbeiten die Anfrage mit rasender Geschwindigkeit.

Proxy-Verwaltung

Scraping-Dienste bieten Zugriff auf Tausende von Proxys, um Blockaden und Bot-Erkennung zu vermeiden. Das Konfigurieren und Rotieren von Proxys wird abstrahiert.

Wiederholungsversuche und Failover

Die Dienste wiederholen fehlgeschlagene Anfragen automatisch und wechseln bei Bedarf zu neuen Proxys, um sicherzustellen, dass Sie Daten erhalten.

Cloud-Skalierbarkeit

Scraping-APIs können sofort skaliert werden, um der Nachfrage gerecht zu werden, ohne dass Ihrerseits technische Arbeiten erforderlich sind.

Daher kann es in vielen Fällen vorzuziehen sein, eine speziell entwickelte Scraping-API zu nutzen und Ihre Bemühungen auf andere Bereiche zu konzentrieren.

Key Take Away

Hier sind die Kerntechniken, die ich zur Optimierung der Web-Scraping-Leistung in Python behandelt habe:

  • Identifizieren Sie Engpässe: Profilieren Sie Ihren Scraper, um langsame I/O- und CPU-Aufgaben zu isolieren.

  • Optimieren Sie I/O mit Asyncio: Verwenden Sie Asyncio- und Async-Bibliotheken, um das Warten auf Anfragen zu vermeiden.

  • CPU-Arbeit parallelisieren: Nutzen Sie Multiprocessing, um die Datenverarbeitung auf alle CPU-Kerne zu verteilen.

  • Kombinieren Sie sie: Asyncio für I/O und Multiprocessing für die CPU arbeiten sehr gut zusammen.

  • Erwägen Sie das Scraping von APIs: Dienste wie ScraperAPI und Scrapfly übernehmen die Optimierung für Sie.

Mit diesen Ansätzen können Sie Ihre Scraper um Größenordnungen beschleunigen. Asyncio und Multiprocessing sind Ihre besten Freunde für performantes Python-Scraping.

Lassen Sie mich wissen, wenn Sie weitere Fragen haben! Ich freue mich immer, anderen Entwicklern bei der Implementierung dieser Parallelitätstechniken zu helfen.

Stichworte:

Mitreden

E-Mail-Adresse wird nicht veröffentlicht. Pflichtfelder sind MIT * gekennzeichnet. *