Das gleiche Produkt kostet bei zwei Händlern fast nie dasselbe. Ein Laptop, der auf einem Marktplatz zu einem bestimmten Preis angeboten wird, kann im Markenshop zehn Prozent günstiger sein, und diese Differenz ändert sich täglich. Jede Website manuell zu prüfen, skaliert über ein paar Produkte hinaus nicht, was genau die Art von sich wiederholender Suche ist, die ein Skript übernehmen sollte.
Dieses Tutorial zeigt Ihnen, wie Sie in Python ein Preisvergleichs-Tool erstellen, das dasselbe Produkt von zwei oder mehr öffentlichen E-Commerce-Seiten abruft, die Daten in eine einheitliche Form normalisiert, die Einträge quellenübergreifend abgleicht, das Ergebnis speichert und den niedrigsten Preis meldet. Wir rufen jede Seite über die Crawling API ab, damit Rendering und IP-Rotation für uns erledigt werden, und beschränken die gesamte Anleitung auf öffentliche Katalogdaten: Produktname, Preis, Währung, Händler und die Produkt-URL.
Was Sie bauen werden
Ein kleines Python-Projekt, das eine Handvoll Produkt-URLs in eine übersichtliche Vergleichstabelle verwandelt. Jeder gescrapte Eintrag wird zu einem Datensatz mit einem festen Satz von Feldern, sodass zwei Händler, die ein Produkt unterschiedlich beschreiben, dennoch zusammenpassen. Am Ende werden Sie Folgendes haben:
- Name. Der Produkttitel, wie ihn der Händler auflistet.
- Preis. Ein numerischer Wert, der aus dem unübersichtlichen Seitentext geparst wurde.
- Währung. Der Währungscode, damit Sie nie Dollar mit Euro vergleichen.
- Händler. Von welcher Quelle der Eintrag stammt.
- URL. Die genaue Seite, damit ein Mensch das Angebot verifizieren kann.
- Gespeicherte Ausgabe. Jeder Datensatz in JSON und CSV geschrieben, plus eine Zusammenfassung des niedrigsten Preises.
Warum eine normale Anfrage bei Einzelhandelsseiten scheitert
Mit der requests-Bibliothek kommt man bei einfachen Seiten weit, aber moderne E-Commerce-Seiten wehren sich auf zwei Arten. Erstens rendern viele von ihnen den Preis clientseitig: Das ursprüngliche HTML ist nahezu eine leere Hülle, und JavaScript füllt den Katalog nach dem Laden aus. Ein einfaches requests.get sieht nur die Hülle, sodass der gewünschte Preis im heruntergeladenen Text schlicht nicht vorhanden ist.
Zweitens beobachten Händler automatisierten Traffic. Einige Anfragen von einer Datacenter-IP in einem erkennbaren Muster werden rate-limitiert, mit CAPTCHAs versehen oder blockiert. Um Preise über mehrere Shops hinweg zu vergleichen, muss jede Seite gerendert und jede Anfrage von einer vertrauenswürdigen, rotierenden Adresse kommen. Diese Kombination bietet die Crawling API in einem einzigen Aufruf, weshalb wir jeden Abruf über sie leiten, anstatt die Sites direkt anzusprechen. Wenn JavaScript-Rendering auf Einzelhandelsseiten neu für Sie ist, beschreibt unser Leitfaden zum Scrapen von JavaScript-Seiten mit Python den Hintergrund.
Voraussetzungen
Drei Dinge, bevor Sie Code schreiben.
Python 3.8 oder neuer. Prüfen Sie mit python --version. Jede aktuelle 3.x-Version funktioniert; laden Sie die aktuelle Version von der offiziellen Python-Website herunter, falls nötig.
Ein Crawlbase-Account und -Token. Melden Sie sich für ein kostenloses Konto an und kopieren Sie Ihren Token aus dem Dashboard. Sie erhalten einen normalen Token (für statisches HTML) und einen JavaScript-Token (für clientseitig gerenderte Seiten); halten Sie beide bereit.
Grundlegende Python-Kenntnisse. Sie sollten damit vertraut sein, ein Skript vom Terminal aus auszuführen, Pakete mit pip zu installieren und ein Dictionary zu lesen. Vertrautheit mit HTML- und CSS-Selektoren hilft, da Sie diese an die von Ihnen angesteuerten Seiten anpassen werden.
Das Projekt einrichten
Erstellen Sie einen Ordner und installieren Sie die Abhängigkeiten. Wir verwenden den offiziellen crawlbase-Client zum Abrufen, beautifulsoup4 zum Parsen von HTML und pandas zum Speichern und Anzeigen des Vergleichs. Die Module json und csv sind eingebaut, daher muss für diese nichts installiert werden.
mkdir price-comparison && cd price-comparison pip install crawlbase beautifulsoup4 pandas
Wenn Sie die Parse-Seite im Einzelnen auffrischen möchten, geht unser Leitfaden zu der Verwendung von BeautifulSoup in Python tiefer als die folgenden Ausschnitte.
Schritt 1: Die erste Quelle abrufen
Beginnen Sie mit einer einzelnen Seite von einem Händler. Der Crawling-API-Client nimmt Ihren Token, ruft die URL hinter einer rotierenden Residential-IP ab und gibt das fertige HTML zurück. Für eine JavaScript-gerenderte Seite übergeben Sie ajax_wait und page_wait, damit die API auf asynchrone Inhalte wartet und einen Moment nach dem Laden pausiert, bevor sie erfasst. Verwenden Sie Ihren JavaScript-Token für diese Seiten und Ihren normalen Token für einfache statische Seiten.
from crawlbase import CrawlingAPI api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) def fetch_page(url): response = api.get(url, {"ajax_wait": "true", "page_wait": 3000}) if response["status_code"] == 200: return response["body"].decode("utf-8") raise RuntimeError(f"Crawl failed: {response['status_code']}") html = fetch_page("https://store-a.example.com/product/smartphone-xyz") print(html[:500])
Wenn Sie dies ausführen, werden die ersten 500 Zeichen des gerenderten HTML ausgegeben. Wenn Sie das echte Seiten-Markup statt einer leeren Hülle sehen, hat der Abruf funktioniert und die API hat sowohl Rendering als auch die IP für Sie übernommen. Die Prüfung von status_code verhindert, dass ein stilles Scheitern als gültiges HTML weitergeleitet wird.
Dieser einzelne fetch_page-Aufruf, den Sie gerade ausgeführt haben, hat die beiden schweren Aufgaben für Sie erledigt: Er hat JavaScript gerendert, sodass der Preis tatsächlich im HTML vorhanden war, und er hat die Anfrage über eine rotierende Residential-IP gesendet, sodass der Händler Sie nicht blockiert hat. Sie müssen keine eigene Headless-Browser-Flotte und keinen eigenen Proxy-Pool aufbauen, was wichtig wird, sobald Sie einen zweiten und dritten Shop nach einem Zeitplan abrufen. Richten Sie es auf die Seiten, die bisher leer zurückkamen.
Schritt 2: Die zweite Quelle abrufen
Für einen Vergleich brauchen Sie mindestens zwei Händler. Die Abruf-Logik ändert sich nicht: dieselbe fetch_page-Funktion funktioniert für jede URL, da die API mit allem umgeht, was jede Website entgegensetzt. Was sich ändert, ist das Parsing, da jeder Händler seinen Titel und Preis unterschiedlich auszeichnet. Schreiben Sie einen kleinen Parser pro Quelle, der die Seite liest und einen normalisierten Datensatz zurückgibt.
import re from bs4 import BeautifulSoup def parse_price(text): # Pull a number out of strings like "$279.99" or "1,299.00 USD" match = re.search(r"[\d,]+\.?\d*", text.replace(",", "")) return float(match.group()) if match else None def parse_store_a(html, url): soup = BeautifulSoup(html, "html.parser") return { "name": soup.select_one("h1.product-title").get_text(strip=True), "price": parse_price(soup.select_one(".price").get_text()), "currency": "USD", "retailer": "Store A", "url": url, } def parse_store_b(html, url): soup = BeautifulSoup(html, "html.parser") return { "name": soup.select_one("#productName").get_text(strip=True), "price": parse_price(soup.select_one("span.amount").get_text()), "currency": "USD", "retailer": "Store B", "url": url, }
Beide Parser geben dieselben fünf Schlüssel zurück, was der eigentliche Punkt ist: name, price, currency, retailer und url. Die Selektoren (h1.product-title, .price, #productName, span.amount) sind Platzhalter. Öffnen Sie jedes Ziel in den Entwicklertools Ihres Browsers, finden Sie die Elemente, die tatsächlich den Titel und den Preis enthalten, und tauschen Sie sie aus. Der Helfer parse_price erledigt die Drecksarbeit, "$279.99" in die Zahl 279.99 umzuwandeln, damit Preise vergleichbar sind.
Händler gestalten ihr Layout häufig um, und ein Selektor, der letzten Monat funktioniert hat, kann still None zurückgeben. Behandeln Sie quellenspezifische Selektoren als etwas, das Sie pflegen müssen. Wenn ein Preis leer zurückkommt, untersuchen Sie die Live-Seite erneut und aktualisieren Sie den einen Parser, nicht den Rest der Pipeline.
Schritt 3: Normalisieren und quellenübergreifend abgleichen
Verbinden Sie jetzt Abruf und Parsing. Ordnen Sie jede URL dem Parser zu, der sie versteht, erstellen Sie eine Liste normalisierter Datensätze und gruppieren Sie Datensätze, die dasselbe Produkt beschreiben, damit Sie Gleiches mit Gleichem vergleichen können. Der zuverlässigste Abgleich ist ein expliziter Produktschlüssel, den Sie pro URL vergeben, anstatt aus leicht unterschiedlichen Titeln zu raten.
# Each entry: (product key, retailer URL, parser) sources = [ ("Smartphone XYZ", "https://store-a.example.com/product/smartphone-xyz", parse_store_a), ("Smartphone XYZ", "https://store-b.example.com/p/smartphone-xyz", parse_store_b), ] def collect(sources): products = {} for key, url, parser in sources: try: record = parser(fetch_page(url), url) except Exception as err: print(f"Skipping {url}: {err}") continue products.setdefault(key, []).append(record) return products
Das Ergebnis ist ein nach Produkt geordnetes Dictionary mit einer Liste von händlerspezifischen Datensätzen unter jedem Schlüssel. Die Form spiegelt das Datenmodell aus dem ursprünglichen Tutorial wider: ein Produktname mit einer Liste von Shops und Preisen, aber hier wird jeder Datensatz live gescrapt und normalisiert, anstatt aus einer statischen Datei gelesen zu werden. Das Einwickeln jedes Abrufs in try/except bedeutet, dass eine tote URL protokolliert wird und der Lauf weiterläuft, anstatt auf halbem Weg abzustürzen.
Schritt 4: Ergebnisse vergleichen und speichern
Mit abgeglichenen Datensätzen in der Hand ist das Finden des niedrigsten Preises eine kurze Schleife, und das Speichern der Ausgabe sind ein paar Zeilen. Der Vergleich behält die genaue Logik aus der klassischen Version: Durchlaufen Sie die Datensätze jedes Produkts und behalten Sie das günstigste.
def find_lowest_price(records): lowest_price = float("inf") best = None for record in records: if record["price"] is not None and record["price"] < lowest_price: lowest_price = record["price"] best = record return best
Dann bauen Sie das vollständige Skript zusammen. Es sammelt alle Quellen, gibt das beste Angebot pro Produkt aus und schreibt die flache Liste von Datensätzen mit pandas sowohl in JSON als auch in CSV, sodass Sie den Vergleich in einer Tabellenkalkulation öffnen oder ihn an ein anderes Tool übergeben können.
import json import pandas as pd def main(): products = collect(sources) rows = [] for name, records in products.items(): rows.extend(records) best = find_lowest_price(records) if best: print(f"Product: {name}") print(f" - Best Price: {best['currency']} {best['price']} at {best['retailer']}") # Store the full comparison as JSON and CSV with open("comparison.json", "w") as f: json.dump(rows, f, indent=2) pd.DataFrame(rows).to_csv("comparison.csv", index=False) print(f"Saved {len(rows)} records.") if __name__ == "__main__": main()
Führen Sie es mit python compare.py aus. Sie erhalten eine Bestpreis-Zeile pro Produkt im Terminal und zwei Dateien auf der Festplatte: comparison.json für den programmatischen Gebrauch und comparison.csv für eine Tabellenkalkulation. Wenn Sie lieber die Preishistorie über die Zeit speichern möchten, ersetzen Sie den CSV-Schreibvorgang durch ein SQLite-Insert mit dem eingebauten sqlite3-Modul; die Datensatzform bleibt gleich, sodass sich nichts Vorgelagertes ändert.
Wie die Ausgabe aussieht
Die JSON-Datei enthält ein Objekt pro gescrapten Eintrag, alle mit denselben fünf normalisierten Feldern. Für zwei Händler, die dasselbe Telefon verkaufen, sieht es so aus:
[ { "name": "Smartphone XYZ", "price": 299.99, "currency": "USD", "retailer": "Store A", "url": "https://store-a.example.com/product/smartphone-xyz" }, { "name": "Smartphone XYZ", "price": 279.99, "currency": "USD", "retailer": "Store B", "url": "https://store-b.example.com/p/smartphone-xyz" } ]
Und die vom find_lowest_price gesteuerte Terminal-Zusammenfassung lautet:
Product: Smartphone XYZ - Best Price: USD 279.99 at Store B Saved 2 records.
Die CSV enthält dieselben Zeilen in flacher Form, eine Kopfzeile und dann eine Zeile pro Eintrag, die sich direkt in jede Tabellenkalkulation zum Sortieren und Diagramm erstellen öffnen lässt.
Auf mehr Produkte und Quellen skalieren
Das Design skaliert durch Hinzufügen von Zeilen zur sources-Liste, nicht durch Umschreiben der Logik. Um zehn Produkte über vier Händler hinweg zu verfolgen, fügen Sie pro neuem Händler einen Parser und pro (Produkt, Händler)-Paar ein Tupel hinzu. Führen Sie das Skript nach einem Zeitplan aus (ein Cron-Job einmal oder zweimal täglich reicht für die Preisverfolgung) und fügen Sie jeden Lauf einer datierten Datei oder einer SQLite-Tabelle hinzu, um eine Preishistorie aufzubauen.
Einige Gewohnheiten halten es bei hohem Volumen gesund. Dosieren Sie Ihre Anfragen, damit Sie nicht Dutzende von Seiten in einer engen Reihe abrufen. Behandeln Sie jeden Abruf isoliert, was das try/except in collect bereits tut, sodass eine fehlschlagende Seite nie den Lauf stoppt. Und cachen Sie abgerufenes HTML auf der Festplatte, während Sie an Selektoren iterieren, damit Sie bei jeder Codeänderung keine Live-Seiten neu ansteuern. Wenn eine Seite später von statisch auf JavaScript-gerendert wechselt, absorbiert die Crawling API das für Sie, da Rendering nur eine Option desselben Aufrufs ist. Für Seiten, die einem bekannten Layout entsprechen, kann die Crawling API die Produktfelder automatisch parsen und strukturiertes JSON direkt zurückgeben, was das Schreiben eines quellenspezifischen Parsers überflüssig macht, wenn eine unterstützte Vorlage passt.
Ein Preisvergleichs-Tool verantwortungsvoll erstellen
Ein Preisvergleichs-Tool berührt nur dieselben öffentlichen Katalogseiten, die auch ein Käufer besuchen würde, aber das macht nicht alles erlaubt. Lesen Sie die Nutzungsbedingungen jedes Händlers und prüfen Sie seine robots.txt, bevor Sie eine Schleife auf ihn richten, und halten Sie Ihr Anfragevolumen moderat: Ein paar Abrufe pro Tag pro Produkt reichen zur Preisverfolgung und sind weit weniger einschneidend als ständiges Hämmern. Bleiben Sie bei öffentlichen Produktdaten (Name, Preis, Währung, URL) und meiden Sie alles hinter einem Login, persönliche Daten und urheberrechtlich geschützte Medien, die Sie weiterverbreiten würden.
Wo ein Händler eine offizielle Produkt-API oder einen Affiliate-Datenfeed anbietet, bevorzugen Sie diese. Diese Kanäle sind genau dafür gebaut, liefern in der Regel sauberere strukturierte Daten als Scraping und halten Sie auf der richtigen Seite der Beziehung. Behandeln Sie Scraping als Rückfalloption für Quellen ohne solche Option, und Sie erhalten ein Tool, das sowohl zuverlässig als auch rücksichtsvoll gegenüber den darauf angewiesenen Seiten ist. Für das breitere Anti-Block-Handbuch geht unser Leitfaden zum Scrapen von Websites ohne Blockierung ins Detail.
Wichtigste Erkenntnisse
- Vor dem Vergleich normalisieren. Ordnen Sie die Seite jedes Händlers denselben fünf Feldern zu (Name, Preis, Währung, Händler, URL), damit unterschiedliches Markup noch übereinstimmt.
- Ein Parser pro Quelle, eine gemeinsame Pipeline. Der Abruf ist über alle Seiten identisch; nur die quellenspezifischen Selektoren ändern sich, was den Rest des Codes stabil hält.
-
Die Crawling API übernimmt Rendering und IPs. Ein einzelner
fetch_page-Aufruf gibt gerendertes HTML hinter einer rotierenden Residential-IP zurück, sodass Sie auf eine Headless-Browser-Flotte und einen Proxy-Pool verzichten können. - Flach speichern, in JSON und CSV. Eine flache Datensatzliste serialisiert sauber in beides und wechselt zu SQLite, wenn Sie eine Preishistorie wollen, ohne vorgelagerte Änderungen.
- Nach Daten skalieren, nicht nach Code. Fügen Sie Produkte und Händler hinzu, indem Sie die Quellenliste erweitern und nach einem Zeitplan ausführen, nicht indem Sie die Vergleichslogik neu schreiben.
Häufig gestellte Fragen
Was ist ein Preisvergleichs-Tool?
Ein Preisvergleichs-Tool erfasst den Preis desselben Produkts von mehreren Händlern und zeigt Ihnen, welcher am günstigsten ist. In Code-Begriffen scrappt es die öffentliche Produktseite jedes Händlers, normalisiert die Daten in eine Form, gleicht Einträge ab, die dasselbe Produkt beschreiben, und meldet den niedrigsten Preis. Das Python-Projekt in diesem Tutorial tut genau das über zwei oder mehr Quellen hinweg.
Warum die Crawling API statt normaler requests verwenden?
Einfache requests laden nur das anfängliche HTML, verpassen daher Preise, die clientseitig mit JavaScript geladen werden, und eine einzelne IP wird blockiert, sobald Sie wiederholt von mehreren Shops abrufen. Die Crawling API rendert die Seite und leitet jede Anfrage über eine rotierende Residential-IP in einem Aufruf, sodass der Preis tatsächlich im geparsten HTML enthalten ist und Sie quellenübergreifend entsperrt bleiben.
Wie gleiche ich dasselbe Produkt bei verschiedenen Händlern ab?
Der zuverlässigste Ansatz ist ein expliziter Produktschlüssel, den Sie pro URL vergeben, wie die sources-Liste in diesem Tutorial, anstatt leicht unterschiedliche Produkttitel automatisch abzugleichen. Für größere Kataloge können Sie auf einer gemeinsamen Kennung wie einer Modellnummer, GTIN oder SKU abgleichen, wenn die Händler eine angeben, was verlässlicher ist als unscharfes Titelabgleichen.
Soll ich die Daten als JSON oder CSV speichern?
Verwenden Sie beides, da sie unterschiedliche Bedürfnisse erfüllen. JSON bewahrt die vollständige verschachtelte Struktur und lässt sich leicht in ein anderes Skript einlesen, während CSV flach ist und sich direkt in einer Tabellenkalkulation zum Sortieren und für Diagramme öffnet. Das Skript in diesem Tutorial schreibt beides aus derselben Datensatzliste. Für die Preisverfolgung über Zeit wechseln Sie zu SQLite, damit Sie die Historie abfragen können, ohne viele Dateien zu verwalten.
Kann ich Preise automatisch über Zeit verfolgen?
Ja. Führen Sie das Skript nach einem Zeitplan aus, zum Beispiel ein Cron-Job einmal oder zweimal täglich, und fügen Sie die Datensätze jedes Laufs einer datierten Datei oder einer SQLite-Tabelle hinzu. Moderate, regelmäßige Abrufe reichen aus, um Preissenkungen zu erkennen und eine Historie aufzubauen, ohne einen Händler zu belasten. Halten Sie das Tempo leicht und respektieren Sie die Bedingungen jeder Website.
Brauche ich einen Headless-Browser wie Selenium dafür?
Nicht wenn Sie über die Crawling API abrufen. Ein Headless-Browser wie Selenium oder Playwright kann JavaScript-Seiten rendern, aber jede Instanz ist ein vollständiger Browser, der Speicher und CPU kostet, und Sie müssen bei Skalierung dennoch selbst Proxys verwalten. Die Crawling API rendert die Seite serverseitig und rotiert IPs für Sie, sodass ein einfaches Python-Skript mit BeautifulSoup ausreicht.
Crawlen Sie jede Website im großen Maßstab, ohne gegen die Infrastruktur zu kämpfen.
Crawlbase übernimmt Proxys, Fingerprints und CAPTCHAs, damit Ihr Team Datenpipelines ausliefert, statt Crawl-Infrastruktur zu pflegen. 1.000 Anfragen kostenlos, keine Karte erforderlich.
