Ein Großteil der Daten, die es wert sind, gesammelt zu werden, erscheint nie im rohen HTML. Produktraster, Kommentar-Threads, Infinite-Scroll-Feeds und Dashboard-Widgets kommen alle an, nachdem JavaScript im Browser ausgeführt wurde, sodass eine einfache HTTP-Anfrage Ihnen ein Gerüst mit den fehlenden interessanten Teilen gibt. Die klassische Antwort ist, die Seite in einem echten Browser zu rendern und dann das fertige Markup zu analysieren, und die häufigste Kombination dafür in Python ist Selenium plus BeautifulSoup.
Diese Anleitung zeigt Ihnen, wie Sie dynamische Inhalte mit Selenium und BeautifulSoup scrapen: Selenium steuert eine Headless-Chrome-Instanz, um das JavaScript auszuführen, auf Elemente zu warten, Lazy Loading durch Scrollen auszulösen und durch Interaktionen zu klicken, während BeautifulSoup den gerenderten page_source in saubere, strukturierte Daten analysiert. Wir erstellen ein kleines, lauffähiges Beispiel und schauen dann ehrlich an, wo dieser Stack teuer wird und wo eine serverseitige Rendering-API die leichtere Wahl ist.
Warum eine einfache Anfrage dynamische Inhalte verpasst
Wenn eine Seite serverseitig gerendert wird, enthält das heruntergeladene HTML bereits die Daten. Wenn sie clientseitig gerendert wird, schickt der Server eine fast leere Hülle plus ein JavaScript-Bundle; der Browser führt dieses JavaScript aus, ruft eine API zurück und fügt den echten Inhalt danach in den DOM ein. Eine Bibliothek wie requests sieht immer nur die erste Antwort, sodass sie nie den Inhalt sieht, den JavaScript später hinzufügt.
Das ist das gesamte Problem, das dynamisches Scraping löst: Sie brauchen etwas, das das JavaScript der Seite tatsächlich ausführt, bevor Sie den DOM lesen. Selenium erledigt genau das, indem es einen echten Browser automatisiert. Sobald der Browser alles gerendert hat, ist das resultierende HTML einfach HTML, und BeautifulSoup ist die schnelle, ergonomische Methode, Felder daraus zu extrahieren. Die zwei Tools teilen die Arbeit sauber auf: Selenium übernimmt Interaktion und Rendering, BeautifulSoup übernimmt die Extraktion.
BeautifulSoup führt kein JavaScript aus. Für sich allein analysiert es das HTML, das Sie ihm geben, sodass das Einspeisen der rohen Antwort einer clientseitig gerenderten Seite dieselbe leere Hülle gibt, die auch ein einfacher Abruf zurückgibt. Der Rendering-Schritt muss zuerst stattfinden, sei es ein lokaler Browser über Selenium oder eine Rendering-API, die fertiges HTML zurückgibt.
Die Umgebung einrichten
Sie benötigen Python 3.8 oder neuer und pip. Erstellen Sie eine virtuelle Umgebung, damit die Abhängigkeiten isoliert bleiben, und installieren Sie dann Selenium und BeautifulSoup.
python -m venv scraper_env source scraper_env/bin/activate pip install selenium beautifulsoup4
Unter Windows aktivieren Sie mit scraper_env\Scripts\activate anstelle der source-Zeile. Sie müssen keine Treiber-Binärdatei manuell herunterladen: seit Selenium 4.10 löst der Selenium Manager den passenden ChromeDriver beim ersten Start von Chrome automatisch auf und lädt ihn herunter, sodass eine aktuelle Chrome-Installation plus das selenium-Paket ausreicht, um zu beginnen.
Schritt 1: Einen Headless-Chrome-WebDriver starten
Beginnen Sie damit, Chrome für den Headless-Betrieb zu konfigurieren, was bedeutet: kein sichtbares Fenster. Der Headless-Modus ist schneller und das, was Sie auf einem Server wollen, obwohl das Ausführen im sichtbaren Modus während der Entwicklung das Debuggen von Selektoren viel einfacher macht. Ein paar zusätzliche Flags halten den Browser in Containern stabil und reduzieren die Oberfläche, auf die einfache Bot-Checks abzielen.
from selenium import webdriver from selenium.webdriver.chrome.options import Options def build_driver(): options = Options() options.add_argument("--headless=new") options.add_argument("--no-sandbox") options.add_argument("--disable-dev-shm-usage") options.add_argument("--window-size=1920,1080") return webdriver.Chrome(options=options)
Eine feste Fenstergröße ist wichtiger, als sie aussieht: Viele Seiten rendern bei verschiedenen Viewport-Breiten unterschiedliche Layouts, sodass das Festlegen der Größe Ihre Selektoren über Durchläufe hinweg stabil hält. Um zu debuggen, was der Browser tatsächlich sieht, entfernen Sie die --headless=new-Zeile und beobachten Sie das Laden der Seite live.
Schritt 2: Navigieren und explizit auf Elemente warten
Der größte Fehler beim dynamischen Scraping ist das Lesen des DOM, bevor der Inhalt angekommen ist. Ein festes time.sleep() ist die falsche Lösung: zu kurz und Sie verpassen Daten, zu lang und jeder Durchlauf ist langsam. Das richtige Werkzeug ist ein explizites Warten, das die Seite abfragt, bis eine bestimmte Bedingung wahr ist (oder ein Timeout auslöst), und dann sofort zurückkehrt, sobald sie es ist. Selenium liefert dies als WebDriverWait zusammen mit expected_conditions.
from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def load_page(driver, url, wait_for): driver.get(url) WebDriverWait(driver, 15).until( EC.presence_of_element_located((By.CSS_SELECTOR, wait_for)) )
Hier navigiert load_page zur URL und blockiert nur, bis mindestens ein Element, das wait_for entspricht, erscheint, bis zu 15 Sekunden. Verwenden Sie visibility_of_element_located, wenn Sie auch das Element gezeichnet benötigen (nicht nur im DOM vorhanden), und element_to_be_clickable, bevor Sie auf etwas klicken. Das Binden Ihres Wartens an das Element, das Sie tatsächlich brauchen, ist es, was einen Selenium-Durchlauf sowohl schnell als auch zuverlässig macht.
Schritt 3: Scrollen, um Lazy Loading auszulösen
Viele Feeds und Produktraster laden mehr Elemente nur, wenn Sie scrollen. Um alle zu erfassen, müssen Sie das Scrollen selbst steuern und dann warten, bis der neue Batch gerendert wird, bevor Sie erneut scrollen. Das Muster ist eine Schleife: bis zum Ende scrollen, warten, die Seitenhöhe messen und anhalten, wenn sie aufhört zu wachsen.
import time def scroll_to_bottom(driver, pause=2.0, max_rounds=10): last_height = driver.execute_script("return document.body.scrollHeight") for _ in range(max_rounds): driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") time.sleep(pause) new_height = driver.execute_script("return document.body.scrollHeight") if new_height == last_height: break last_height = new_height
Die kurze sleep-Pause hier ist eine pragmatische Wartezeit für den nächsten Batch zum Abrufen und Rendern; sie ist der eine Ort, an dem eine feste Verzögerung schwer zu vermeiden ist, weil der Auslöser das Netzwerk ist, nicht ein bekanntes Element. Begrenzen Sie die Schleife mit max_rounds, sodass eine Seite, die nie aufhört zu wachsen, nicht ewig laufen kann. Wenn die Seite stattdessen einen "Mehr laden"-Button verwendet, ist das Äquivalent: Den Button finden, klicken, auf neue Zeilen warten und wiederholen, bis der Button verschwindet.
Schritt 4: Das gerenderte HTML an BeautifulSoup übergeben
Sobald die Seite gerendert und vollständig gescrollt ist, ist der Rest einfaches Parsen. Lesen Sie driver.page_source, was der live DOM als HTML serialisiert ist, und laden Sie es in BeautifulSoup. Von dort wählen Sie Elemente per CSS-Selektor genau so aus, wie Sie es mit jeder statischen Seite würden.
from bs4 import BeautifulSoup def parse_items(html): soup = BeautifulSoup(html, "html.parser") items = [] for card in soup.select("div.product-card"): title = card.select_one("h2.title") price = card.select_one("span.price") items.append({ "title": title.get_text(strip=True) if title else None, "price": price.get_text(strip=True) if price else None, }) return items
Die Absicherungen um jedes Feld (title.get_text(...) if title else None) verhindern, dass ein fehlendes Element den gesamten Durchlauf zum Absturz bringt, was es wert ist, von Anfang an zu tun, weil echte Angebote inkonsistent sind. Sie könnten Elemente stattdessen über Seleniums eigene find_elements abfragen, aber BeautifulSoup ist für die Massenextraktion schneller und seine Selektor- und Navigations-API ist freundlicher, sobald der DOM gesetzt ist.
Schritt 5: Die Teile zusammensetzen
Verbinden Sie die vier Schritte in einem einzigen Skript: Treiber erstellen, laden und warten, scrollen, analysieren und dann immer den Treiber beenden, damit Sie keine Browser-Prozesse auslaufen lassen.
import json def main(): url = "https://example.com/products" driver = build_driver() try: load_page(driver, url, "div.product-card") scroll_to_bottom(driver) data = parse_items(driver.page_source) finally: driver.quit() print(json.dumps(data, indent=2)) if __name__ == "__main__": main()
Das try/finally ist in der Produktion nicht optional. Eine Scroll-Schleife oder ein Warten kann eine Ausnahme auslösen, und wenn driver.quit() nie ausgeführt wird, hinterlassen Sie bei jedem Fehler einen Zombie-Chrome-Prozess. Über einen langen Job erschöpft das schnell den Speicher. Für eine tiefere Abdeckung des Ausführens von Browsern auf diese Weise, siehe Headless Browser für Web Scraping, und für das umfassendere Landschaft des Renderings von JavaScript mit Python, JavaScript-Seiten mit Python scrapen.
Die ehrlichen Kosten des Selenium-Ansatzes
Dieser Stack funktioniert, und für ein einmaliges Scraping von ein paar Seiten ist er schwer zu übertreffen. Im Maßstab summieren sich die Kosten, und es lohnt sich, sie zu benennen, bevor Sie sich zum Betrieb einer Browser-Flotte verpflichten.
- Browser-Overhead. Jede Seite startet eine vollständige Chrome-Instanz mit ihrem Speicher- und CPU-Bedarf. Ein Dutzend gleichzeitige Treiber können einen kleinen Server sättigen, sodass der Durchsatz durch Hardware begrenzt ist, nicht nur durch das Netzwerk.
-
Instabile Wartezeiten. Explizite Wartezeiten sind weit besser als
sleep, aber sie brechen immer noch, wenn eine Seite ihr Markup oder ihr Timing ändert, und eine Wartezeit, die lokal funktioniert, kann auf einem langsameren Rechner ablaufen. Warte-Logik wird zu laufender Pflege. - Anti-Bot-Erkennung. Ein Headless-Browser leckt immer noch Signale (Treiber-Fingerabdrücke, fehlende Header, Datacenter-IPs), die moderne Abwehrsysteme lesen. Bei Volumen treffen Sie auf CAPTCHAs und IP-Sperren, die kein Warten behebt, was bedeutet, dass Sie Proxy-Rotation und Fingerabdruck-Patches darüber hinaus einschichten müssen.
Nichts davon macht Selenium zum falschen Werkzeug; es macht es zu einem schweren Werkzeug. Wenn der Job "viele Seiten zuverlässig von einem Server aus rendern, ohne eine Browser-Flotte zu betreuen" ist, sind das Rendering und das Entsperren die schwierigen Teile, und das sind genau die Dinge, die eine verwaltete API Ihnen abnehmen kann.
Wenn Sie gerendertes HTML ohne den Betrieb einer Browser-Flotte wollen, rendert die Crawling API die Seite in einem echten Browser serverseitig und rotiert Residential IPs für Sie, dann gibt sie fertiges HTML zurück, das Sie mit demselben BeautifulSoup-Code analysieren. Sie übergeben einen JS-Token und Warte-Optionen anstatt Treiber, Scroll-Schleifen und einen Proxy-Pool zu verwalten. Starten Sie im kostenlosen Kontingent und richten Sie es zuerst auf eine dynamische Seite aus.
Die leichtere Alternative: serverseitig rendern, lokal analysieren
Die Crawling API behält die Hälfte dieses Workflows, die Ihnen tatsächlich gefällt (BeautifulSoup-Extraktion), und entfernt die Hälfte, die wehtut (Browser ausführen und entsperren). Sie senden die URL mit einem JavaScript-Token, die API rendert sie hinter einer vertrauenswürdigen IP und Sie analysieren das zurückgegebene HTML genau wie zuvor. Dieselbe parse_items-Funktion aus Schritt 4 funktioniert unverändert.
from crawlbase import CrawlingAPI from bs4 import BeautifulSoup import json api = CrawlingAPI({"token": "YOUR_CRAWLBASE_JS_TOKEN"}) def fetch_rendered(url): options = {"ajax_wait": "true", "page_wait": 5000} response = api.get(url, options) if response["status_code"] == 200: return response["body"].decode("utf-8") print(f"Request failed: {response['status_code']}") return None html = fetch_rendered("https://example.com/products") if html: print(json.dumps(parse_items(html), indent=2))
Die ajax_wait-Option weist die API an, auf das Beruhigen asynchroner Inhalte zu warten, und page_wait hält für eine feste Anzahl von Millisekunden nach dem Laden, damit spät rendernde Elemente vor der Aufzeichnung erscheinen. Erhöhen Sie page_wait, wenn Felder leer zurückkommen. Beachten Sie, was weg ist: kein Treiber-Lebenszyklus, keine Scroll-Schleife, kein try/finally-Cleanup und kein Proxy-Management. Das Rendering und die IP-Rotation geschehen serverseitig, sodass tausend Seiten tausend HTTP-Aufrufe sind und nicht tausend Browser-Starts.
Wenn Ihre Aufgabe echte mehrstufige Interaktion erfordert (einloggen, ein Formular ausfüllen und absenden, durch einen Assistenten klicken und dann Zustand lesen, der von diesen Aktionen abhängt), ist Selenioms zustandsbehaftete Browser-Sitzung das richtige Werkzeug. Die Crawling API glänzt, wenn das Ziel "diese URL rendern und das fertige HTML geben" im Volumen ist. Viele Projekte verwenden beides: einen Browser für die wenigen interaktiven Abläufe, die API für das Massenabrufen.
Für andere Browser-Automatisierungs-Stacks zum Vergleich deckt Playwright für Web Scraping eine moderne Alternative zu Selenium mit ähnlichen Abwägungen ab. Wenn Sie lieber Ihren eigenen Browser-Traffic durch rotierende IPs leiten möchten, anstatt die verwaltete API zu nutzen, gibt Ihnen der Smart AI Proxy Residential-Rotation als Drop-in-Proxy-Endpunkt, und für Fire-and-Forget-Jobs überträgt der asynchrone Crawler gerenderte Ergebnisse an einen Callback, anstatt auf jede Anfrage zu blockieren.
Wichtigste Erkenntnisse
- Dynamische Inhalte benötigen Rendering. JavaScript fügt die Daten nach der ersten Antwort ein, also müssen Sie die Seite ausführen, bevor Sie sie analysieren; ein einfacher Abruf gibt eine Hülle zurück.
-
Selenium rendert, BeautifulSoup extrahiert. Steuern Sie einen Headless-Chrome-WebDriver zum Rendern und Interagieren, dann übergeben Sie
driver.page_sourcefür schnelle, selektor-basierte Extraktion an BeautifulSoup. -
Explizite Wartezeiten verwenden, keine Sleeps.
WebDriverWaitmitexpected_conditions, gebunden an das Element, das Sie brauchen, ist sowohl schneller als auch zuverlässiger als eine feste Verzögerung. - Scrollen, um Lazy Loading auszulösen. Scroll-Warte-Mess-Schleife, bis die Seitenhöhe aufhört zu wachsen, mit einer Rundenbegrenzung, damit sie nicht ewig laufen kann.
- Selenium ist schwer im Maßstab. Browser-Overhead, instabile Wartezeiten und Anti-Bot-Sperrung summieren sich; eine Rendering-API wie die Crawling API gibt fertiges HTML mit gehandhabter IP-Rotation zurück, und Ihr BeautifulSoup-Code bleibt gleich.
Häufig gestellte Fragen
Warum kann BeautifulSoup dynamische Inhalte nicht allein scrapen?
BeautifulSoup ist ein Parser, kein Browser: Es liest das HTML, das Sie ihm geben, führt aber nie JavaScript aus. Auf einer clientseitig gerenderten Seite ist das rohe HTML eine leere Hülle, sodass BeautifulSoup nichts zu extrahieren hat, bis etwas die Seite zuerst rendert. Dieser Rendering-Schritt ist das, was Selenium lokal bereitstellt, oder was eine JS-Token-Rendering-API serverseitig bereitstellt, bevor BeautifulSoup überhaupt ausgeführt wird.
Wie warte ich auf dynamische Elemente, anstatt mit Sleep zu raten?
Verwenden Sie ein explizites Warten. WebDriverWait(driver, timeout).until(EC.presence_of_element_located((By.CSS_SELECTOR, sel))) fragt die Seite ab und gibt zurück, sobald das Element erscheint, bis zum Timeout. Das ist schneller als ein festes time.sleep(), weil es nicht länger als nötig wartet, und zuverlässiger, weil es an das tatsächliche Element gebunden ist, das Sie brauchen, und nicht an eine geraten Duration.
Muss ich ChromeDriver noch manuell herunterladen?
Nicht seit Selenium 4.10. Der Selenium Manager löst auf und lädt die ChromeDriver-Version herunter, die zu Ihrem installierten Chrome passt, automatisch beim ersten Start des Browsers herunter. Sie verwalten den Treiber nur manuell in abgeschlossenen Umgebungen, in denen automatische Downloads blockiert sind; in diesem Fall zeigen Sie Selenium auf eine Treiber-Binärdatei, die Sie bereitstellen.
Bringt mich Selenium an Anti-Bot-Systemen vorbei?
Nicht allein im Maßstab. Ein Headless-Browser legt immer noch Signale offen (Automatisierungs-Fingerabdrücke, Datacenter-IPs, fehlende oder inkonsistente Header), die moderne Abwehrsysteme erkennen, sodass Sie bei steigendem Volumen auf CAPTCHAs und IP-Sperren stoßen werden. Das Abmildern bedeutet, Proxy-Rotation und Fingerabdruck-Patches einzuschichten, weshalb eine verwaltete Crawling API, die Rendering und IP-Rotation zusammen übernimmt, oft weniger Arbeit ist als die Härtung einer Browser-Flotte.
Wann sollte ich die Crawling API statt Selenium und BeautifulSoup verwenden?
Verwenden Sie die Crawling API, wenn der Job darin besteht, viele Seiten zuverlässig von einem Server aus zu rendern und Sie keine Browser-Flotte betreiben oder entsperren wollen. Sie rendert serverseitig, rotiert Residential IPs und gibt fertiges HTML zurück, das Ihr vorhandener BeautifulSoup-Code unverändert analysiert. Behalten Sie Selenium, wenn Sie echte zustandsbehaftete Interaktion benötigen, zum Beispiel Einloggen, Formulare absenden oder durch einen mehrstufigen Ablauf klicken.
Kann ich meinen BeautifulSoup-Analyse-Code mit der Crawling API wiederverwenden?
Ja, und das ist der Hauptreiz. Die Crawling API gibt das gerenderte HTML als Zeichenkette zurück, sodass derselbe BeautifulSoup(html, "html.parser")-Aufruf und dieselben Selektoren ohne Änderungen funktionieren. Sie tauschen nur aus, wie das HTML erhalten wird: Anstatt driver.page_source von einem lokalen Browser, lesen Sie response["body"] vom API-Aufruf.
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.
