BeautifulSoup im Jahr 2026: Der freundlichste HTML-Parser ist auch der langsamste (um das 12- bis 17-Fache)

Zuletzt aktualisiert am July 17, 2026
BeautifulSoup im Jahr 2026: Der freundlichste HTML-Parser ist auch der langsamste (um das 12- bis 17-Fache)
KI-Zusammenfassung
Dieser Testbericht zeigt BeautifulSoup als den zugänglichsten HTML-Parser für Python und beziffert die Kosten dieser Freundlichkeit genau. Verglichen wird bs4 mit C-basierten Parsern in den Bereichen Geschwindigkeit, Toleranz gegenüber fehlerhaftem HTML, CSS-Selektor-Abdeckung, Objektverbleib im Speicher und Kodierungs-Erkennung. Der Artikel zeigt, dass BeautifulSoup deutlich langsamer ist – oft um das 12- bis 17-Fache –, erklärt aber auch, warum Entwickler es trotzdem wählen: lesbare APIs, fehlertolerantes Parsing, starke soupsieve-Selektorunterstützung und hervorragende Ergonomie für unordentliche Einmal-Extraktionen. Es ist ein praxisnaher Leitfaden dazu, wann sich der Geschwindigkeitsaufschlag lohnt und wann ein schnellerer Parser die bessere technische Wahl ist.

BeautifulSoup ist die Bibliothek, zu der fast alle beim ersten Web-Scraping in Python greifen – und tatsächlich der langsamste unter den ernstzunehmenden HTML-Parsern. Beides stimmt, und beides ist keine Kritik. Spannend ist eher, dass sich dieses „langsamste“ als eine ziemlich genaue, messbare Zahl herausstellt und nicht nur als Bauchgefühl.

Ich habe bs4 (also beautifulsoup4, Version 4.15.0, veröffentlicht im Juni 2026, unter MIT-Lizenz) mit einer Mischung aus neuen Funktionstests und wiederverwendeten Timing-Daten aus demselben Benchmark-Setup getestet. Das Ergebnis ist konsistent: Für die freundlichste API und die beste Fehlertoleranz in diesem Bereich zahlt man etwa eine Größenordnung an Geschwindigkeit. Ob sich dieser Tausch lohnt, hängt vollständig vom Einsatzzweck ab – deshalb betrachtet dieser Testbericht beide Seiten.

Was BeautifulSoup eigentlich ist – und was nicht

Die meisten Tutorials überspringen den wichtigsten Punkt: BeautifulSoup parst HTML nicht selbst. Es ist ein Wrapper. Im Hintergrund reicht es Ihr Dokument an einen von drei echten Parsern weiter – den eingebauten Python-Parser html.parser, lxml oder html5lib – und legt anschließend eine sehr angenehme Navigations- und Suchschnittstelle über den erzeugten Baum. Die Aufgabe von bs4 ist nicht das Parsen. Sondern das bequeme Arbeiten mit dem Ergebnis.

Der Autor selbst nennt es eine „screen-scraping library“, und das Versprechen war schon immer dasselbe: HTML anvisieren, das einen Browser zusammenzucken lassen würde, und trotzdem die gewünschten Daten herausziehen. Dieser Ruf ist verdient – mit einem kleinen Sternchen, auf das wir noch kommen.

Zuerst ein paar Eckdaten:

FeldWert
Paketbeautifulsoup4 (Import als bs4)
Getestete Version4.15.0 (hochgeladen am 2026-06-07)
Python-Voraussetzung>=3.7.0
LizenzMIT
Offizielle Projektseitecrummy.com/software/BeautifulSoup
Quellcode + BugtrackerLaunchpadnicht GitHub
PflegeAktiv (4.15.0 im Juni 2026, sechs Releases im letzten Jahr)

Die Zeile „nicht GitHub“ ist wichtiger, als sie zunächst wirkt. bs4 ist eine 20 Jahre alte Bibliothek, die auf crummy.com und Launchpad lebt – der übliche Blick auf GitHub-Stars ist hier also fehl am Platz. Beurteilen Sie die Gesundheit des Projekts lieber nach der Release-Frequenz, und da steht es gut da.

Noch eine Feinheit zur Lizenzierung, falls Sie einer Compliance-Abteilung Rechenschaft schulden müssen: Der Wrapper selbst ist MIT-lizenziert, aber was durch den Einsatz von bs4 tatsächlich in Ihren Abhängigkeitsbaum gelangt, hängt vom Backend ab. html.parser gehört zur Python-Standardbibliothek (PSF-Lizenz, keine zusätzlichen Abhängigkeiten). lxml ist BSD-lizenziert, baut aber auf libxml2/libxslt auf – also auf einer externen C-Abhängigkeit, die Sie entweder selbst kompilieren oder als vorgefertigtes Wheel einbinden. html5lib ist reines Python und MIT. Wenn Sie den saubersten Dependency-Footprint wollen, ist der eingebaute html.parser die einfachste Wahl – und zugleich, wie sich zeigen wird, die mit dem größten Haken. Dazu gleich mehr.

Der Geschwindigkeitsaufschlag, quantifiziert

Setzen wir zuerst die Zahl auf den Tisch, denn sie ist die eigentliche Schlagzeile und sie zu verschweigen wäre unehrlich. Bei einer realistischen Parse-und-Extrahiere-Aufgabe – String parsen, alle <h3 class="title"> und alle <a href> herausziehen – ist BeautifulSoup der langsamste Parser in diesem Vergleich, und zwar mit deutlichem Abstand.

BeautifulSoup-Geschwindigkeitsaufschlag: 232 ms gegenüber 15 ms bei C-Parsers

Diese Messwerte stammen wiederverwendet aus dem selectolax-Benchmark-Setup (gleiche Maschine, gleiche 3-Durchläufe, Stand 2026-07-13); dieser Bericht führt selbst keinen zusätzlichen Timing-Benchmark aus, um CPU-Konkurrenz und doppelte Arbeit zu vermeiden. Median-Latenz p50 in Millisekunden:

Seitengrößebs4 (html.parser)bs4 (lxml)selectolax-Lexborlxmlbs4-hp langsamerbs4-lxml langsamer
1 KB0.3230.2810.0270.03612.0x10.5x
10 KB2.0491.7050.1600.16612.8x10.7x
100 KB20.59916.5401.4641.42314.1x11.3x
1 MB232.558181.85514.90114.17715.6x12.2x
10 MB2788.7462261.557159.935172.93317.4x14.1x

bs4(html.parser) ist damit ungefähr 12–17x langsamer als ein C-Parser wie selectolax-Lexbor. Selbst mit dem lxml-Backend bleibt es bei 10.5–14x – also weiterhin eine komplette Größenordnung dahinter. Der Grund ist strukturell und kein Bug: Unabhängig davon, welcher Backend-Parser das HTML einliest, erzeugt bs4 für jeden einzelnen Knoten ein vollständiges Python-Objekt (Tag oder NavigableString). Diese Objekt-Materialisierung ist ein Aufschlag, den C-Parser nicht bezahlen.

Auffällig ist auch: Der Multiplikator steigt mit der Seitengröße – 12.0x bei 1 KB, 17.4x bei 10 MB. Das zeigt, dass es sich nicht um einen festen Start-Overhead handelt, den man über die Zeit relativiert. Es ist ein Pro-Knoten-Aufschlag, der linear mit der Zahl der Knoten wächst.

Und nun die Einordnung, denn „10x langsamer“ klingt dramatischer, als es oft ist. Bei einer 1-MB-Seite sind das 232 ms gegenüber 15 ms. Wenn Ihre Aufgabe lautet: „ein paar hundert bis ein paar tausend Seiten mit jeweils einigen hundert KB scrapen“, dann ist dieser absolute Unterschied praktisch unsichtbar – Sie werden ihn nicht spüren, und eine Optimierung bringt nichts. Wenn Ihre Aufgabe aber eine Pipeline mit einer Million Seiten ist, entscheidet genau dieses Verhältnis darüber, ob der Job rechtzeitig fertig wird oder nicht. Dieselbe Zahl, völlig anderes Urteil. Maßgeblich ist Ihr tatsächliches Volumen, nicht der Benchmark allein.

Nein, ein anderer Backend-Parser macht bs4 nicht plötzlich schnell

Es hält sich hartnäckig der Mythos, man müsse bs4 nur das lxml-Backend geben, dann sei es genauso schnell wie lxml. Das stimmt nicht, und es lohnt sich zu verstehen, warum. Bei einer CSS-Abfrage über 100.000 Knoten (alle <a> auswählen und ihr href lesen, Baum bereits aufgebaut) sieht die Durchsatzverteilung so aus:

ParserQuery p50Knoten/Sek.
lxml33.30 ms3,002,646
selectolax-Modest34.19 ms2,924,550
selectolax-Lexbor39.46 ms2,534,027
bs4 (lxml)250.56 ms399,111

bs4(lxml) erreicht etwa 399.000 Knoten pro Sekunde – also rund 6.3–7.5x langsamer als die drei C-Engines, obwohl der eigene Backend-Parser bereits lxml ist. Das Backend beschleunigt die Baum-Erstellung. Abfragen und Traversierung laufen aber weiterhin über soupsieve in bs4-Tag-Objekte hinein, und jeder gefundene Knoten wird erneut in Python verpackt. Das Denkmodell „bs4 mit lxml = lxml-schnell“ ist also falsch: Der Backend beschleunigt nur eine Phase, und die langsamste Phase ist nicht diese.

Auch beim Speicher und beim Kaltstart fallen Kosten an. Auf einem 10-MB-Dokument braucht bs4 etwa 1.5–1.75x so viel Resident Memory wie selectolax oder lxml (218–226 MB gegenüber 129–145 MB) – gleiche Ursache, ein Python-Objekt pro Knoten. Und bs4 zu importieren dauert rund 33.4 ms gegenüber 14.1 ms für lxml.html, ist also 2.36x langsamer. Das ist für einen lang laufenden Prozess fast egal, für ein CLI-Tool oder eine Serverless-Funktion mit ständigem Kaltstart aber ein kleiner, echter Kostenpunkt, den man kennen sollte.

Warum mehr Threads hier nicht helfen

Wenn Ihr erster Reflex bei einer langsamen CPU-lastigen Aufgabe lautet: „Dann nehme ich eben Threads“, wird bs4 Sie dafür bestrafen. Bei einer 1-MB-Seite, 48-mal geparst, ergibt der Vergleich Einzelthread gegen vier Threads folgendes Bild:

Parser1 Thread4 ThreadsBeschleunigung
selectolax-Lexbor0.563 s0.159 s3.54x
lxml0.459 s0.378 s1.21x
bs4 (lxml)6.945 s26.842 s0.26x

Lesen Sie die unterste Zeile ruhig zweimal. Vier Threads machen bs4 etwa 3.9x langsamer, nicht schneller. Das deutet stark darauf hin, dass der GIL hält: Der Baumaufbau von bs4 ist reines Python und damit unter dem Global Interpreter Lock serialisiert. Mehr Threads fügen nur Scheduling-Overhead zu einer Aufgabe hinzu, die nicht parallel laufen kann. selectolax profitiert von seiner C-Kern-Implementierung und wird etwa 3.5x schneller; bs4 hat diesen Spielraum nicht.

Für das Free-Threading-Zeitalter lautet die praktische Konsequenz: Wenn Sie BeautifulSoup parallelisieren müssen, greifen Sie zu Multiprocessing (ProcessPoolExecutor) und nicht zu Threads. selectolax und lxml können von Threads profitieren; bs4 nicht. Eine Einschränkung zur Methodik: Das ist eine einzelne Beobachtung bei genau einer Threadzahl (4) und einer Seitengröße (1 MB), und der Mechanismus „hält den GIL“ ist eine aus dem Laufzeitverhalten abgeleitete Hypothese – nicht etwas, das ich durch Instrumentierung des konkreten Lock-Pfads direkt bestätigt habe. Die Tendenz ist klar, der exakte Mechanismus bleibt vorläufig.

Das Standard-Backend ist die Falle. Erst das hier lesen.

Wenn Sie aus diesem Testbericht nur eine Sache mitnehmen, dann diese. Ein einfaches BeautifulSoup(html) ohne zweites Argument verwendet html.parser, und html.parser implementiert die optionalen End-Tag-Regeln von HTML5 nicht. Das klingt akademisch – bis Ihre Daten still und leise beschädigt werden.

BeautifulSoup-Backend-Toleranzmatrix: html.parser 12/15, lxml und html5lib 15/15

Ich habe 15 absichtlich fehlerhafte HTML-Beispiele durch alle drei Backends laufen lassen und für jedes Beispiel vorab eine backend-unabhängige strukturelle Erwartung hinterlegt (damit niemand im Nachhinein den Gewinner auswählen kann). Das Ergebnis:

BackendErwartung erfüllt / 15
lxml15
html5lib15
html.parser12

Die drei Fehler haben dieselbe Ursache. Nehmen wir eine nicht geschlossene Tabelle: <table><tr><td>a<td>b<tr><td>c<td>d</table>. Unter html.parser kommt der extrahierte Zelltext als ['abcd','bcd','cd','d'] zurück – jede <td>-Zelle schluckt alles danach, weil der Parser die Zellen verschachtelt statt sie zu schließen. lxml und html5lib liefern korrekt ['a','b','c','d']. Das Gleiche passiert bei einfachen Listenpunkten: <li>a<li>b<li>c ergibt unter html.parser die verschachtelte Form ['abc','bc','c'], während die anderen beiden ['a','b','c'] liefern. Auch doppelte Attribute kippen: <div id="first" id="second"> behält unter html.parser "second", unter lxml/html5lib aber "first" – und laut HTML5-Spezifikation soll das erste Attribut gelten.

Warum das gefährlich und nicht bloß nervig ist: Es passiert ohne Fehlermeldung. Ein Scraper, der schlicht BeautifulSoup(html) aufruft und auf eine ungeschlossene Tabelle oder Liste trifft – was auf alten Seiten, handgeschriebenem HTML und Vorlagen mit vergessenem Closing-Tag erstaunlich häufig vorkommt – vermischt benachbarte Zelltexte zu einem Feld, liefert Ihnen unsaubere Daten und beschwert sich nicht einmal. Die Lösung ist ein einziges Argument: BeautifulSoup(html, "lxml") oder BeautifulSoup(html, "html5lib").

Fairerweise für html.parser: Die übrigen 12 von 15 fehlerhaften Beispielen waren über alle drei Backends hinweg identisch – falsch verschachtelte Tags wie <b><i></b></i>, fehlende html/body-Struktur, nicht zitierte Attribute, verwaiste schließende Tags, nicht geschlossene Kommentare, verschachtelte Formulare, Mischschreibweisen und mehr. Die Fehlertoleranz von bs4 ist also insgesamt wirklich stark; die Unterschiede konzentrieren sich fast vollständig auf die Familie der optionalen End-Tags. Und nichts davon ist neu: Die eigene Dokumentation von bs4 erklärt bei den „Differences between parsers“ bereits, dass html.parser in einfachen Worten „weniger nachsichtig“ ist. Die hier getestete Matrix ergänzt nur die konkreten, reproduzierbaren Fälle, in denen „weniger nachsichtig“ zu falschen Ergebnissen führt.

Worauf Sie nicht verzichten: Die API und CSS sind die eigentlichen Stärken

bs4 ist also langsam, einthreadig und hat eine Falle beim Standard-Backend. Trotzdem greifen viele weiterhin dazu, weil der „freundliche“ Teil des Kompromisses absolut real ist – und sich im Test klar bestätigt.

BeautifulSoup gewinnt bei soupsieve-CSS-Abdeckung mit 41/41 plus 20/20

Ich habe 29 API-Prüfpunkte für Suche, CSS, Baumnavigation, Textausgabe und DOM-Modifikation getestet. Alle 29 bestanden, und jeder Einzelwert wurde durch Vergleich des tatsächlichen Rückgabewerts mit einem erwarteten Wert ermittelt – nicht per Augenmaß. Zwei dieser Fähigkeiten sind besonders angenehm und bieten die C-Parser so nicht:

  • Funktionsprädikate in find / find_all. Sie können soup.find(lambda t: t.name == "a" and "btn" in t.get("class", [])) schreiben und eine komplexe Bedingung in einer einzigen Python-Zeile ausdrücken – ohne den Umweg „erst alles auswählen, dann filtern“.
  • Benannte, bidirektionale Baumnavigation. .parent, .next_sibling, .find_parent, .stripped_strings, .descendants – die Traversierungen lesen sich fast wie Englisch und funktionieren in beide Richtungen. selectolax braucht für manche Fälle mehrere Schritte oder bietet sie gar nicht erst an.

Das ist der Teil „spart Entwicklerzeit“, ganz konkret. Kein Marketing, sondern 29 grüne Häkchen.

Zwei Fallstricke sollte man fairerweise ebenfalls nennen. Erstens bei booleschen Attributen: <input disabled> liefert in bs4 für disabled einen leeren String "" (selectolax liefert None). Beides ist „falsy“, daher übersieht if node.get("disabled") ein tatsächlich vorhandenes boolesches Attribut stillschweigend in beiden Bibliotheken – die sichere Prüfung lautet "disabled" in tag.attrs. Zweitens verkettet get_text(strip=True) Textknoten nach dem Strippen ohne Trennzeichen, sodass aus "...with " + "link1" "withlink1" wird. Wenn Sie Wortgrenzen brauchen, setzen Sie separator=" ". Beide Fallstricke sind nicht bs4-spezifisch; sie treten bibliotheksübergreifend auf.

Und jetzt der Punkt, der viele überrascht: Wer bs4 wählt, verzichtet nicht auf CSS-Abdeckung. Die CSS-Engine soupsieve ist die vollständigste Implementierung in diesem gesamten Vergleich. Auf der 41-Fälle-Basismatrix (wiederverwendet aus dem selectolax-Setup) erreichte soupsieve 41/41 – der einzige perfekte Wert im Feld, vor selectolax-Lexbor mit 39/41 und cssselect (lxml/parsel) mit 37/41. Danach habe ich 20 zusätzliche erweiterte Fälle aus der Dokumentation von soupsieve getestet, und auch dort gab es 20/20, inklusive Selektoren, die Lexbor direkt ablehnt: :lang(en), das nur in soupsieve vorhandene :-soup-contains('featured'), :is(), :where() und :has(> a). Die eigentlichen Lücken sind nur XPath (soupsieve ist CSS-only) und die ::text- bzw. ::attr()-Pseudoelemente von parsel, die Scrapy-Erweiterungen sind. Wer in XPath lebt, wird bei dieser Migration spürbar leiden.

Die Bilanz dieses Abschnitts ist klar: Wer BeautifulSoup wählt, gibt Geschwindigkeit auf – nicht aber API-Komfort und auch nicht CSS-Abdeckung.

Zwei Produktionsfallen, für die Sie Budget einplanen sollten

Abgesehen vom Standard-Backend gibt es zwei Verhaltensweisen, die Ihnen speziell in langlebigen Prozessen oder bei Nicht-UTF-8-Workloads auf die Füße fallen können.

Referenzzyklen: In langen Schleifen decompose() aufrufen

Jedes bs4-Tag hält eine Referenz auf seinen Parent und auf seine Kinder – daraus entsteht ein Referenzzyklus. CPython kann einen solchen Zyklus nicht allein per Referenzzählung freigeben; dafür ist der generationsbasierte Garbage Collector zuständig. Um zu sehen, wie relevant das ist, habe ich einen Baum 300-mal erzeugt und gelöscht, während der GC deaktiviert war, und anschließend gezählt, wie viele Tag-Objekte noch im Speicher lagen:

BeautifulSoup-Referenzzyklen halten 120.900 Objekte bei deaktiviertem GC und 0 bei aktiviertem GC zurück

SzenarioNach del verbliebene Tags
GC aus120,900 (300 Zyklen, nichts freigegeben)
GC an26,598 (generational GC lief während der Schleife an)
Nach erzwungenem gc.collect()0 (alles freigegeben)
Kontrollgruppe ohne Zyklus (Liste von Strings, GC aus)Delta 0

Mit ausgeschaltetem GC gab del soup nichts frei – alle 120.900 Objekte blieben resident, weil der Referenzzyklus die Referenzzählung aushebelt. Ein einzelnes gc.collect() hat alles beseitigt. Die Kontrollgruppe ohne Zyklus (eine einfache Liste von Strings, die bekanntermaßen keinen Zyklus hat) zeigte ein Delta von null, was belegt, dass der Aufbau von bs4s Zyklus kam und nicht aus Messrauschen. Die eigene Dokumentation von bs4 sagt bereits, die Objekte seien „dicht miteinander verknüpft ... genau die Art, mit der ein Garbage Collector Schwierigkeiten haben würde“ – das ist also dokumentiertes Verhalten. Der Test liefert zusätzlich die Zahl der zurückgehaltenen Objekte und den Beweis, dass collect() alles auf null setzt.

Die praktische Regel lautet: In einer Pipeline, die viele große Seiten in einer engen Schleife verarbeitet, sollten Sie – falls Ihr Code oder eine Performance-Konfiguration den GC deaktiviert oder zu selten laufen lässt – soup.decompose() nach jeder Seite aufrufen. Genau dafür stellt bs4 diese Methode bereit: Sie bricht den Zyklus und gibt Speicher frühzeitig frei. Die C-Bäume von selectolax und lxml haben dieses Problem überhaupt nicht.

Kodierung: UnicodeDammit ist bs4s stiller Vorteil

bs4 bringt eine Komponente mit, die schnelle Parser nicht haben: UnicodeDammit. Sie erkennt die Kodierung eines Dokuments und wandelt sie automatisch in Unicode um. Ich habe sie mit einer 8-Fälle-Matrix aus „deklarierter vs. tatsächlicher Zeichencodierung“ getestet:

BeautifulSoup UnicodeDammit stellt 5 von 8 Kodierungsfällen wieder her

FallTatsächliche KodierungUnicodeDammit geschätztWiederhergestellt?
utf8_no_declutf-8utf-8Ja
utf16_bomutf-16utf-16leJa
gbk_chinesegbkgb18030Ja (Superset)
shiftjisshift_jiscp932Ja (Superset)
latin1_declared_utf8latin-1 (deklariert als utf-8)iso-8859-1Ja (die Lüge ignoriert)
latin1_no_decllatin-1cp720Nein
cp1252_no_declcp1252cp862Nein
utf8_declared_latin1utf-8 (deklariert als latin-1)iso-8859-1Nein (der Lüge gefolgt)

Fünf von acht Fällen wurden korrekt erkannt. UTF-8, UTF-16 mit BOM, GBK, Shift-JIS und sogar falsch ausgezeichnetes latin-1 kamen richtig zurück; die Superset-Treffer (GBK→gb18030, Shift-JIS→cp932) dekodieren ebenfalls sauber. Zwei Fehlermodi sollte man kennen: Kurze latin-1-/cp1252-Bytefolgen werden gern als DOS-Codepages fehlgedeutet, weil der statistische Detektor bei kurzen Inputs unzuverlässig ist und DOS-Zeichen für Liniengrafiken mit lateinischen Codepunkten überlappen; und wenn ein <meta charset> schlicht falsch deklariert ist, vertraut UnicodeDammit dieser Angabe. bs4s Dokumentation weist auf beides hin – ein Sample kann „so kurz sein, dass Unicode, Dammit keinen Halt findet“, und mehr Daten führen zu besseren Schätzungen.

Gegenüber selectolax, das Nicht-UTF-8-Bytes stillschweigend falsch interpretieren kann und erwartet, dass Sie selbst vorab dekodieren, ist das ein echter Vorteil: bs4 versucht zumindest zu erraten und liegt oft richtig. Garantiert ist das aber nicht. Bei bekannter Kodierung gilt deshalb: nicht raten lassen, sondern explizit angeben – BeautifulSoup(bytes, from_encoding="...").

Stimmen die Backends in der Praxis überhaupt jemals nicht überein?

Die Matrix mit den fehlerhaften Beispielen zeigt Unterschiede zwischen den Backends bei absichtlich kaputtem Input. Die naheliegende nächste Frage lautet, ob das in echten Seiten überhaupt relevant ist. Deshalb habe ich alle drei Backends auf 11 reale, abgerufene Seiten losgelassen – BBC, Wikipedia, Craigslist, MDN, old.reddit, Python Docs, Hacker News, Books to Scrape, webscraper.io, whitehouse.gov und eine per JavaScript gerenderte Quotes-Seite – und Link-, Überschrift- und Bildanzahlen verglichen.

Alle drei waren sich auf allen 11 Seiten einig. Keine Abweichung. Das bedeutet: Die Unterschiede aus dem Fall „Standard-Backend-Falle“ treten nur bei absichtlich fehlerhaftem HTML auf. Wenn eine moderne Produktionsseite ordentlich genug strukturiert ist – selbst wenn sie „unordentlich“ wirkt –, ändert die Backend-Wahl nicht, was Sie extrahieren. Praktisch heißt das: Für gängige, sauber strukturierte Sites ist html.parser völlig in Ordnung und spart eine Abhängigkeit. Nur wenn Sie sichtbar unstandardisiertes, handgeschriebenes oder sehr altes HTML scrapen, beginnt die Backend-Wahl Ihre Ergebnisse zu verändern – und genau dann wechseln Sie zu lxml oder html5lib.

Ein Randfall aus diesem Lauf ist erwähnenswert. Die MDN-Seite enthält ein <template>-Element, und alle bs4-Backends lieferten 508 Links – bs4 flacht den Inhalt von <template> also in den Hauptbaum ein. Das stellt bs4 auf dieselbe Seite wie lxml und im Gegensatz zu selectolax-Lexbor, das der HTML5-Spezifikation strikt folgt (<template> ist dort ein inaktives DocumentFragment) und 497 Links zurückgibt, wobei die 11 Links im Template stillschweigend verschwinden. bs4 erfasst also Daten innerhalb von <template> – nützlich, aber auch eine Möglichkeit, „Phantom“-Inhalte mitzunehmen, die ein Browser nie rendern würde. Keines der beiden Verhaltensmuster ist falsch; es sind unterschiedliche Spezifikationsauslegungen, und man sollte wissen, welche man bekommt.

Wo BeautifulSoup passt – und wo nicht

Statt all das in eine einzige 0–100-Punktzahl zu pressen – was genau die relevanten Kompromisse verschleiern würde –, hier die Bewertung nach Dimensionen, jeweils mit einem kleinen Vorbehalt:

DimensionErgebnis der TestsHinweis für Leser
Installation / erster LaufReiner Wrapper, kein Browser/Setup; html.parser ohne Zusatzabhängigkeiten; alle Wheels vorgefertigtlxml-Backend braucht eine C-Abhängigkeit
Geschwindigkeit vs. C-Parser12–17x langsamer (html.parser) / 10.5–14x (lxml-Backend), alle GrößenEin einziges Setup; wiederverwendete selectolax-Daten
CSS-Abfragedurchsatz~6–7.5x langsamer bei 100k Knoten; lxml-Backend rettet es nichtWiederverwendet; zahlt den Python-Tag-Aufschlag
Speicher1.5–1.75x selectolax/lxml; am schwerstenWiederverwendet; per RSS gemessen
Import-Kaltstart2.36x langsamer (33.4 vs. 14.1 ms)Wiederverwendet; kleiner Einzelwert
Thread-Skalierungbs4-lxml bei 4 Threads ~3.9x langsamer (hält GIL)Einzelbeobachtung; Multiprocessing nutzen
API-Ergonomie29/29 Prüfpunkte; Funktionsprädikat in find + bidirektionale NavigationFallen bei leeren Bool-Attributen und Strip-Wortgrenzen
CSS-Abdeckungsoupsieve am stärksten: 41/41 Basis + 20/20 erweitert; unterstützt :langKein XPath, kein ::text
Toleranz der 3 Backendslxml/html5lib 15/15; html.parser 12/15Unterschiede nur bei fehlerhaftem HTML
Konsistenz auf echten Seiten3 Backends 11/11 gleich; alle flachen <template> ab (508)Bei gut strukturierten Seiten ist das Backend egal
Referenzzyklus-GCBaum ist ein Zyklus; 300 Schleifen hielten 120.900 Objekte zurück, collect setzte auf nullIn langen Schleifen decompose() nötig
KodierungUnicodeDammit findet 5/8; irrt bei kurzen Samples, folgt falschen DeklarationenEinzelbeobachtung
WartungAktiv (4.15.0, Juni 2026); MITHeimstatt auf crummy/Launchpad, nicht GitHub

Also: Für wen ist BeautifulSoup? Für alle, die eine lesbare API und fehlerverzeihendes Parsing vor roher Durchsatzleistung bevorzugen und nur in moderatem Umfang arbeiten – Prototypen, einmalige Scrapes, interne Tools, Teams, bei denen Entwicklerzeit teurer ist als Laufzeit. Wer sollte sich eher anders orientieren? Pipelines mit Millionen Seiten, bei denen sich der Geschwindigkeitsaufschlag in echten Kosten niederschlägt, Workloads mit Bedarf an Thread-Parallelität und alle, die an XPath hängen.

Noch ein Hinweis dazu, wo BeautifulSoup in einem realen Scraping-Stack sitzt – und wo unser eigenes Tool ins Spiel kommt. BeautifulSoup setzt voraus, dass Sie das HTML bereits haben. Es lädt keine Seiten, rendert kein JavaScript und löst keine Anti-Bot-Schranken oder CAPTCHAs – das ist ein eigener, deutlich schwierigerer Arbeitsschritt im modernen Web. Genau an dieser anderen Stelle sitzt eine AI-Scraping-API: Der Entwickler-Stack von Thunderbit – REST API, MCP-Server und CLI – übernimmt das Abrufen, das JavaScript-Rendering und das Anti-Bot-Problem und gibt Ihnen anschließend entweder sauberes Markdown (POST /distill) oder schema-matching strukturiertes JSON (POST /extract) zurück, ohne dass Sie Selektoren schreiben müssen. Die beiden Werkzeuge konkurrieren nicht miteinander, sie ergänzen sich. bs4 parst HTML, das Sie bereits besitzen; die API, MCP und CLI von Thunderbit verschaffen Ihnen erst das HTML, das Sie auf anderem Weg nur schwer erreichen. Wenn Ihr Engpass das Parsen ist, ist bs4 eine gute Antwort. Wenn Ihr Engpass die Beschaffung ist, dann liegt die Lösung auf einer anderen Ebene.

Thunderbit für die Extraktion von Webdaten testen

Fazit

BeautifulSoup bietet die freundlichste API, die beste Fehlertoleranz bei kaputtem HTML und die vollständigste CSS-Engine in diesem Vergleich – erkauft mit ungefähr einer Größenordnung an Geschwindigkeitsverlust und dem höchsten Speicherbedarf. Genau das ist der Deal, klar ausgesprochen. Das Standard-Backend html.parser ist die einzige echte Falle: Es verstümmelt ungeschlossene Tabellen und Listen stillschweigend, also übergeben Sie bei unordentlichen Eingaben besser "lxml" oder "html5lib". Threads beschleunigen bs4 nicht – Multiprocessing schon. Und in langen Schleifen sollten Sie jede Seite mit decompose() bereinigen, damit sich die Referenzzyklen nicht aufstauen.

Zwei Einschränkungen zum Schluss. Alles hier wurde auf einer einzigen Plattform gemessen (macOS arm64, Python 3.14, vorgefertigte Wheels), und die Timing-Multiplikatoren wurden aus dem selectolax-Setup übernommen (gleicher Bench, Stand 2026-07-13) statt neu gemessen – sie tragen also diese Einplattform-Einschränkung mit sich, und eine Linux-x86_64- oder aus Quellcode kompilierte Umgebung könnte die exakten Werte verschieben. Und nichts davon ist eine neue Entdeckung: bs4 ist eine 20 Jahre alte Bibliothek, also ist jedes getestete Verhalten entweder dokumentiert oder öffentlich bekannt. Der Wert dieser Analyse liegt nicht in einem Scoop, sondern darin, reale Zahlen für Abwägungen zu liefern, die in der Doku nur qualitativ beschrieben werden.

Häufig gestellte Fragen

Ist BeautifulSoup langsam? Ja, messbar. Bei einer Parse-plus-Extraktions-Aufgabe läuft es mit dem Standard-Backend html.parser etwa 12–17x langsamer als ein C-Parser wie selectolax-Lexbor und mit dem lxml-Backend 10.5–14x langsamer, weil für jeden Knoten ein Python-Objekt erzeugt wird. Ob das relevant ist, hängt vom Maßstab ab: Auf einer 1-MB-Seite sind das 232 ms gegenüber 15 ms – für ein paar tausend Seiten kaum spürbar, für eine Million-Seiten-Pipeline entscheidend.

Welchen BeautifulSoup-Parser soll ich verwenden – html.parser, lxml oder html5lib? Für gut strukturierte Mainstream-Seiten ist der Standard html.parser in Ordnung und braucht keine Zusatzabhängigkeiten. Er implementiert jedoch die optionalen HTML5-End-Tags nicht, sodass er bei ungeschlossenen Tabellen oder Listen benachbarte Texte ohne Fehlermeldung zusammenführt. Wenn Ihre Eingaben fehlerhaft, handgeschrieben oder alt sein könnten, übergeben Sie explizit "lxml" oder "html5lib" – beide erzielten in einer Matrix fehlerhafter HTML-Beispiele ein sauberes 15/15, während html.parser nur 12/15 erreichte.

Kann BeautifulSoup mit Threads parallel parsen? Nein. Der Baumaufbau von bs4 ist reines Python und hält den GIL; mehr Threads machen es also langsamer statt schneller – im Test war ein 1-MB-Parse mit vier Threads etwa 3.9x langsamer als mit einem Thread. Wenn Sie bs4 parallelisieren müssen, nutzen Sie Multiprocessing (ProcessPoolExecutor). C-basierte Parser wie selectolax und lxml sind die Kandidaten, die von Thread-Parallelität profitieren.

Kommt BeautifulSoup gut mit kaputtem HTML zurecht? Im Großen und Ganzen ja – über eine Reihe fehlerhafter Beispiele hinweg (falsch verschachtelte Tags, fehlende Grundstruktur, nicht zitierte Attribute und mehr) kamen alle drei Backends sauber zurecht. Die Schwachstelle ist das Standard-Backend html.parser bei optionalen End-Tags: Nicht geschlossene <td>/<li>-Elemente werden verschachtelt statt geschlossen und verfälschen den extrahierten Text. Mit dem lxml- oder html5lib-Backend verschwindet diese Fehlerklasse.

BeautifulSoup vs. lxml – was ist besser? Das sind unterschiedliche Werkzeuge. lxml ist beim Baumaufbau und bei Abfragen deutlich schneller und unterstützt XPath. BeautifulSoup kapselt lxml (unter anderem) in einer viel freundlicheren API und bietet über soupsieve sogar eine breitere CSS-Abdeckung. Man sollte nur nicht erwarten, dass das lxml-Backend bs4 lxml-schnell macht – das Backend beschleunigt nur das Parsen, während Abfragen und Traversierung weiterhin den Python-Objekt-Aufschlag pro Knoten zahlen. Auf großen Batch-Selektionen bleibt bs4 dadurch etwa 6–7.5x langsamer.

Thunderbit für die Extraktion von Webdaten testen Get Started Free

Ke
Ke
CTO bei Thunderbit | Senior Data Scientist & ML-Experte Mit fast zehn Jahren Erfahrung in Machine Learning und Data Science ist Ke Shen Absolvent der Columbia University und ehemaliger Senior Data Scientist bei Walmart Labs. Mit tiefgreifender, von Fachkollegen anerkannter Expertise in Python, R, Java und Statistik teilt er praxiserprobte Einblicke dazu, wie sich komplexe KI-Algorithmen von der Theorie in eine produktionsreife Architektur überführen lassen.
Inhaltsverzeichnis
Thunderbit · KI-Webdaten-Agent

Daten von jeder Seite in 1 Klick extrahieren

Vertrauen von über 250.000 Nutzern
Kostenloser Plan verfügbar
Daten mit KI extrahieren
Daten einfach in Google Sheets, Airtable oder Notion übertragen
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week