Python
Beschleunigen Sie Ihr Python BeautifulSoup Scraping: Ein Leitfaden für Anfänger

Beschleunigen Sie Ihr Python BeautifulSoup Scraping: Ein Leitfaden für Anfänger

MoeNagy Dev

Optimierung von Beautiful Soup für schnelleres Web Scraping

Grundlagen von Beautiful Soup verstehen

Beautiful Soup ist eine leistungsstarke Python-Bibliothek zum Web Scraping, die eine einfache Möglichkeit bietet, HTML- und XML-Dokumente zu analysieren. Sie ermöglicht es Ihnen, die Struktur von Webseiten zu durchsuchen, zu suchen und zu verändern. Um Beautiful Soup zu verwenden, müssen Sie die Bibliothek installieren und in Ihr Python-Skript importieren:

from bs4 import BeautifulSoup

Nachdem Sie die Bibliothek importiert haben, können Sie ein HTML-Dokument mit dem BeautifulSoup-Konstruktor analysieren:

html_doc = """
<html><head><title>Die Geschichte des Siebenschläfers</title></head>
<body>
<p class="title"><b>Die Geschichte des Siebenschläfers</b></p>
<p class="story>Es war einmal, da waren drei kleine Schwestern; und ihre Namen waren
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> und
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
und sie lebten am Boden eines Brunnens.</p>
<p class="story">...</p>
"""
 
soup = BeautifulSoup(html_doc, 'html.parser')

In diesem Beispiel erstellen wir ein BeautifulSoup-Objekt aus dem html_doc-String und verwenden den Parser 'html.parser'. Dieser Parser ist ein eingebauter Python-HTML-Parser, aber Sie können auch andere Parser wie 'lxml' oder 'lxml-xml' je nach Bedarf verwenden.

Identifizierung von Leistungsengpässen

Obwohl Beautiful Soup ein leistungsfähiges Werkzeug ist, ist es wichtig zu verstehen, dass das Analysieren von HTML eine rechenintensive Aufgabe sein kann, insbesondere beim Umgang mit großen oder komplexen Webseiten. Die Identifizierung von Leistungsengpässen in Ihrem Beautiful-Soup-Code ist der erste Schritt zur Optimierung der Leistung.

Ein häufiges Leistungsproblem mit Beautiful Soup ist die Zeit, die es zum Analysieren des HTML-Dokuments benötigt. Dies kann von Faktoren wie der Größe des HTML, der Komplexität der Dokumentstruktur und dem verwendeten Parsmodus beeinflusst werden.

Ein weiterer möglicher Engpass ist die Zeit, die für die Suche und Navigation im analysierten HTML-Baum benötigt wird. Abhängig von der Komplexität Ihrer Abfragen und der Größe des HTML-Dokuments kann dieser Prozess ebenfalls zeitaufwendig sein.

Um Leistungsengpässe in Ihrem Beautiful Soup-Code zu identifizieren, können Sie das in Python eingebaute timeit-Modul oder ein Profiling-Tool wie cProfile verwenden. Hier ist ein Beispiel für die Verwendung von timeit, um die Zeit zu messen, die für die Analyse eines HTML-Dokuments benötigt wird:

import timeit
 
setup = """
from bs4 import BeautifulSoup
html_doc = '''
<html><head><title>Die Geschichte des Siebenschläfers</title></head>
<body>
<p class="title"><b>Die Geschichte des Siebenschläfers</b></p>
<p class="story>Es war einmal, da waren drei kleine Schwestern; und ihre Namen waren
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> und
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
und sie lebten am Boden eines Brunnens.</p>
<p class="story">...</p>
'''
"""
 
stmt = """
soup = BeautifulSoup(html_doc, 'html.parser')
"""
 
print(timeit.timeit(stmt, setup=setup, number=1000))

Dieser Code führt den BeautifulSoup-Analysevorgang 1000 Mal aus und gibt die durchschnittliche Ausführungszeit aus. Sie können ähnliche Techniken verwenden, um die Leistung anderer Teile Ihres Beautiful Soup-Codes, wie das Durchsuchen und Navigieren im HTML-Baum, zu messen.

Strategien zur Verbesserung der Beautiful Soup-Leistung

Sobald Sie die Leistungsengpässe in Ihrem Beautiful Soup-Code identifiziert haben, können Sie Strategien zur Verbesserung der Leistung implementieren. Hier sind einige gängige Strategien:

  1. Optimieren Sie das HTML-Parsing: Wählen Sie den optimalen Parsmodus für Ihren Anwendungsfall. Beautiful Soup unterstützt mehrere Parsmodi, darunter 'html.parser', 'lxml' und 'lxml-xml'. Jeder Modus hat seine eigenen Stärken und Schwächen, daher sollten Sie verschiedene Modi testen, um herauszufinden, welcher am besten für Ihre spezifische HTML-Struktur geeignet ist.

    # Verwendung des 'lxml'-Parsers
    soup = BeautifulSoup(html_doc, 'lxml')
  2. Nutzen Sie die parallele Verarbeitung: Beautiful Soup kann langsam sein, wenn große HTML-Dokumente verarbeitet werden oder mehrere Web Scraping-Aufgaben durchgeführt werden. Sie können den Prozess beschleunigen, indem Sie Multithreading oder Multiprocessing verwenden, um die Arbeit parallel auszuführen.

    import threading
     
    def scrape_page(url):
        response = requests.get(url)
        soup = BeautifulSoup(response.content, 'html.parser')
        # Verarbeiten Sie das Soup-Objekt
        # ...
     
    urls = ['https://example.com/page1', 'https://example.com/page2', ...]
    threads = []
     
    for url in urls:
            thread = threading.Thread(target=scrape_page, args=(url,))
            thread.start()
            threads.append(thread)
     
    for thread in threads:
        thread.join()
  3. Implementieren Sie Caching und Memoization: Das Zwischenspeichern der Ergebnisse früherer Web Scraping-Vorgänge kann die Leistung signifikant verbessern, insbesondere wenn dieselben Websites wiederholt gescraped werden. Memoization, eine Technik zum Zwischenspeichern von Funktionsaufrufen, kann ebenfalls verwendet werden, um wiederholte Berechnungen in Ihrem Beautiful Soup-Code zu optimieren.

    from functools import lru_cache
     
    @lru_cache(maxsize=128)
    def scrape_page(url):
        response = requests.get(url)
        soup = BeautifulSoup(response.content, 'html.parser')
        # Verarbeiten Sie das Soup-Objekt
        # ...
        return result
  4. Integrieren Sie mit Pandas und NumPy: Wenn Sie mit tabellarischen Daten arbeiten, können Sie Beautiful Soup mit Pandas und NumPy integrieren, um deren effiziente Datenmanipulationsfähigkeiten zu nutzen. Dadurch kann die Leistung Ihrer Web Scraping-Aufgaben signifikant verbessert werden.

import pandas as pd
from bs4 import BeautifulSoup
 
html_doc = """
<table>
    <tr>
        <th>Name</th>
        <th>Alter</th>
        <th>Stadt</th>
    </tr>
    <tr>
        <td>John</td>
        <td>30</td>
        <td>New York</td>
    </tr>
    <tr>
        <td>Jane</td>
        <td>25</td>
        <td>Los Angeles</td>
    </tr>
</table>
"""
 
soup = BeautifulSoup(html_doc, 'html.parser')
table = soup.find('table')
rows = table.find_all('tr')
 
data = []
for row in rows[1:]:
    cols = row.find_all('td')
    name = cols[0].text
    age = int(cols[1].text)
    city = cols[2].text
    data.append({'Name': name, 'Age': age, 'City': city})
 
df = pd.DataFrame(data)
print(df)

Im nächsten Abschnitt werden wir uns ansehen, wie wir das parallele Verarbeiten mit Beautiful Soup nutzen können, um die Leistung weiter zu verbessern.

Paralleles Verarbeiten mit Beautiful Soup

Einführung in Multithreading und Multiprocessing

Python bietet zwei Hauptmethoden zur Parallelisierung: Multithreading und Multiprocessing. Mit Multithreading können Sie mehrere Ausführungsstränge innerhalb eines einzelnen Prozesses ausführen, während Ihnen Multiprocessing ermöglicht, mehrere Prozesse auszuführen, von denen jeder seinen eigenen Speicherplatz und seine eigenen CPU-Ressourcen hat.

Die Wahl zwischen Multithreading und Multiprocessing hängt von der Art Ihrer Web Scraping-Aufgabe und davon ab, wie Ihr Code CPU- und Speicherressourcen nutzt. Im Allgemeinen eignet sich Multithreading besser für I/O-gebundene Aufgaben (wie Netzwerkanfragen), während Multiprocessing besser für CPU-gebundene Aufgaben (wie das Parsen und Verarbeiten von HTML) geeignet ist.

Implementierung von Multithreading mit Beautiful Soup

Um Multithreading mit Beautiful Soup zu implementieren, können Sie das integrierte threading-Modul in Python verwenden. Hier ist ein Beispiel, wie Sie mehrere Webseiten gleichzeitig mit Multithreading durchsuchen können:

import requests
from bs4 import BeautifulSoup
import threading
 
def scrape_page(url):
    response = requests.get(url)
    soup = BeautifulSoup(response.content, 'html.parser')
    # Verarbeiten Sie das Beautiful Soup-Objekt
    # ...
    return result
 
urls = ['https://example.com/page1', 'https://example.com/page2', ...]
threads = []
 
for url in urls:
    thread = threading.Thread(target=scrape_page, args=(url,))
    thread.start()
    threads.append(thread)
 
for thread in threads:
    thread.join()

In diesem Beispiel definieren wir eine scrape_page-Funktion, die eine URL als Eingabe benötigt, den HTML-Inhalt abruft und das BeautifulSoup-Objekt verarbeitet. Anschließend erstellen wir für jede URL einen Thread und starten sie alle gleichzeitig. Schließlich warten wir mit der join-Methode auf das Beenden aller Threads.

Implementierung von Multiprocessing mit Beautiful Soup

Für CPU-gebundene Aufgaben wie das Parsen und Verarbeiten großer HTML-Dokumente kann Multiprocessing effektiver sein als Multithreading. Sie können das multiprocessing-Modul in Python verwenden, um dies zu erreichen. Hier ist ein Beispiel:

import requests
from bs4 import BeautifulSoup
import multiprocessing
 
def scrape_page(url):
    response = requests.get(url)
    soup = BeautifulSoup(response.content, 'html.parser')
    # Verarbeiten Sie das Beautiful Soup-Objekt
    # ...
    return result
 
urls = ['https://example.com/page1', 'https://example.com/page2', ...]
pool = multiprocessing.Pool(processes=4)
results = pool.map(scrape_page, urls)

In diesem Beispiel definieren wir dieselbe scrape_page-Funktion wie zuvor. Anschließend erstellen wir ein multiprocessing.Pool-Objekt mit 4 Worker-Prozessen und verwenden die map-Methode, um die scrape_page-Funktion auf jede URL in der Liste anzuwenden. Die Ergebnisse werden in der results-Liste gesammelt.

Vergleich der Leistung von Multithreading und Multiprocessing

Die Leistungsunterschiede zwischen Multithreading und Multiprocessing hängen von der Art Ihrer Web Scraping-Aufgaben ab. Als allgemeine Regel gilt:

  • Multithreading ist für I/O-gebundene Aufgaben, wie Netzwerkanfragen, effektiver, bei denen die Threads den Großteil ihrer Zeit auf Antworten warten.
  • Multiprocessing ist für CPU-gebundene Aufgaben, wie das Parsen und Verarbeiten großer HTML-Dokumente, effektiver, da die Prozesse mehrere CPU-Kerne nutzen können, um die Berechnungen zu beschleunigen.

Um die Leistung von Multithreading und Multiprocessing zu vergleichen, können Sie das timeit-Modul oder ein Profiling-Tool wie cProfile verwenden. Hier ist ein Beispiel:

import timeit
 
setup = """
import requests
from bs4 import BeautifulSoup
import threading
import multiprocessing
 
def scrape_page(url):
    response = requests.get(url)
    soup = BeautifulSoup(response.content, 'html.parser')
    # Verarbeiten Sie das Beautiful Soup-Objekt
    # ...
    return result
 
urls = ['https://example.com/page1', 'https://example.com/page2', ...]
"""
 
stmt_multithreading = """
threads = []
for url in urls:
    thread = threading.Thread(target=scrape_page, args=(url,))
    thread.start()
    threads.append(thread)
 
for thread in threads:
    thread.join()
"""
 
stmt_multiprocessing = """
pool = multiprocessing.Pool(processes=4)
results = pool.map(scrape_page, urls)
"""
 
print("Multithreading:", timeit.timeit(stmt_multithreading, setup=setup, number=1))
print("Multiprocessing:", timeit.timeit(stmt_multiprocessing, setup=setup, number=1))

Dieser Code misst die Ausführungsdauer.

Funktionen

Funktionen sind ein grundlegendes Konzept in Python. Sie ermöglichen es Ihnen, einen Satz Anweisungen zu kapseln und im gesamten Code wiederverwendbar zu machen. Hier ist ein Beispiel für eine einfache Funktion:

def grüße(name):
    print(f"Hallo, {name}!")
 
grüße("Alice")

Diese Funktion, grüße(), nimmt einen einzigen Parameter name entgegen und gibt eine Begrüßungsnachricht aus. Sie können diese Funktion mehrmals mit unterschiedlichen Argumenten aufrufen, um die gleiche Logik wiederzuverwenden.

Funktionen können auch Werte zurückgeben, die in Variablen gespeichert oder in anderen Teilen Ihres Codes verwendet werden können. Hier ist ein Beispiel:

def addiere_zahlen(a, b):
    return a + b
 
ergebnis = addiere_zahlen(5, 3)
print(ergebnis)  # Ausgabe: 8

In diesem Beispiel nimmt die Funktion 'add_numbers()' zwei Argumente 'a' und 'b' und gibt ihre Summe zurück.

Funktionen können mehrere Parameter haben und Sie können auch Standardwerte für diese Parameter definieren:

def greet(name, message="Hello"):
    print(f"{message}, {name}!")
 
greet("Bob")  # Ausgabe: Hello, Bob!
greet("Alice", "Hi")  # Ausgabe: Hi, Alice!

In diesem Beispiel hat die Funktion 'greet()' zwei Parameter 'name' und 'message', aber 'message' hat einen Standardwert von "Hello". Wenn Sie die Funktion nur mit einem Argument aufrufen, wird der Standardwert für 'message' verwendet.

Funktionen können auch innerhalb anderer Funktionen definiert werden, um verschachtelte Funktionen zu erstellen. Diese werden als lokale Funktionen oder innere Funktionen bezeichnet. Hier ist ein Beispiel:

def outer_function(x):
    print(f"Ausführung der outer_function mit {x}")
 
    def inner_function(y):
        print(f"Ausführung der inner_function mit {y}")
        return x + y
 
    result = inner_function(5)
    return result
 
output = outer_function(3)
print(output)  # Ausgabe: 8

In diesem Beispiel ist die Funktion 'inner_function()' in der Funktion 'outer_function()' definiert. Die 'inner_function()' hat Zugriff auf den Parameter 'x' der 'outer_function()', obwohl er kein Parameter der 'inner_function()' ist.

Module und Pakete

In Python können Sie Ihren Code in Module und Pakete organisieren, um ihn übersichtlicher und wiederverwendbarer zu machen.

Ein Modul ist eine einzelne Python-Datei, die Definitionen und Anweisungen enthält. Sie können Module in Ihren Code importieren, um die von ihnen definierten Funktionen, Klassen und Variablen zu verwenden. Hier ist ein Beispiel:

# math_utils.py
def add(a, b):
    return a + b
 
def subtract(a, b):
    return a - b
# main.py
import math_utils
 
result = math_utils.add(5, 3)
print(result)  # Ausgabe: 8

In diesem Beispiel haben wir ein Modul namens 'math_utils.py', das zwei Funktionen, 'add()' und 'subtract()', definiert. In der Datei 'main.py' importieren wir das Modul 'math_utils' und verwenden die bereitgestellten Funktionen.

Ein Paket ist eine Sammlung von verwandten Modulen. Pakete sind in einer hierarchischen Struktur organisiert, mit Verzeichnissen und Unterverzeichnissen. Hier ist ein Beispiel:

my_package/
    __init__.py
    math/
        __init__.py
        utils.py
    text/
        __init__.py
        formatting.py

In diesem Beispiel ist 'my_package' ein Paket, das zwei Unterpakete, 'math' und 'text', enthält. Jedes Verzeichnis enthält eine 'init.py'-Datei, die erforderlich ist, damit Python das Verzeichnis als Paket erkennt.

Sie können Module aus einem Paket mit der Punkt-Notation importieren:

from my_package.math.utils import add
from my_package.text.formatting import format_text
 
result = add(5, 3)
formatted_text = format_text("Hello, world!")

In diesem Beispiel importieren wir die Funktion 'add()' aus dem Modul 'utils.py' im Unterpaket 'math' und die Funktion 'format_text()' aus dem Modul 'formatting.py' im Unterpaket 'text'.

Ausnahmen

Ausnahmen sind eine Möglichkeit, Fehler und unerwartete Situationen in Ihrem Python-Code zu behandeln. Wenn eine Ausnahme auftritt, wird der normale Programmfluss unterbrochen und der Interpreter versucht, einen geeigneten Ausnahme-Handler zu finden.

Hier ist ein Beispiel, wie man eine Ausnahme behandelt:

try:
    result = 10 / 0
except ZeroDivisionError:
    print("Fehler: Division durch Null")

In diesem Beispiel versuchen wir, 10 durch 0 zu teilen, was einen 'ZeroDivisionError' verursacht. Der 'except'-Block fängt diese Ausnahme ab und gibt eine Fehlermeldung aus.

Sie können auch mehrere Ausnahmen in einem einzigen 'try-except'-Block behandeln:

try:
    x = int(input("Geben Sie eine Zahl ein: "))
    y = 10 / x
except ValueError:
    print("Fehler: Ungültige Eingabe")
except ZeroDivisionError:
    print("Fehler: Division durch Null")

In diesem Beispiel versuchen wir zuerst, die Benutzereingabe in eine Ganzzahl umzuwandeln. Wenn die Eingabe ungültig ist, wird ein 'ValueError' ausgelöst und wir fangen ihn im ersten 'except'-Block ab. Wenn die Eingabe gültig ist, aber der Benutzer 0 eingibt, wird ein 'ZeroDivisionError' ausgelöst und wir fangen ihn im zweiten 'except'-Block ab.

Sie können auch eigene benutzerdefinierte Ausnahmen definieren, indem Sie eine neue Klasse erstellen, die von der Klasse 'Exception' oder einer ihrer Unterklassen erbt:

class CustomException(Exception):
    pass
 
def divide(a, b):
    if b == 0:
        raise CustomException("Fehler: Division durch Null")
    return a / b
 
try:
    result = divide(10, 0)
except CustomException as e:
    print(e)

In diesem Beispiel definieren wir eine benutzerdefinierte Ausnahme namens 'CustomException', die wir auslösen, wenn die Funktion 'divide()' mit einem Divisor von 0 aufgerufen wird. Wir fangen diese Ausnahme dann im 'try-except'-Block ab und geben die Fehlermeldung aus.

Fazit

In diesem Tutorial haben Sie verschiedene fortgeschrittene Konzepte in Python gelernt, darunter Funktionen, Module, Pakete und Ausnahmen. Diese Funktionen sind unerlässlich für das Schreiben komplexerer und organisierterer Python-Code.

Funktionen ermöglichen es Ihnen, Logik zu encapsulieren und wiederzuverwenden, wodurch Ihr Code modulare und wartbarer wird. Module und Pakete helfen Ihnen, Ihren Code in logische Einheiten zu organisieren, was seine Verwaltung und gemeinsame Nutzung mit anderen erleichtert. Ausnahmen bieten eine Möglichkeit, Fehler und unerwartete Situationen zu behandeln und sicherzustellen, dass Ihr Programm Probleme, die während der Ausführung auftreten können, elegant bewältigen kann.

Indem Sie diese Konzepte beherrschen, sind Sie auf dem besten Weg, ein versierter Python-Entwickler zu werden, der in der Lage ist, robuste und skalierbare Anwendungen zu erstellen.

MoeNagy Dev