Geonode logo
Geonode Team

Geonode Team

Aktualisiert: 7. Oktober 2026

Veröffentlicht: 02.09.2026

CSS-Selektoren-Spickzettel: Leitfaden mit Beispielen

CSS-Selektoren sind die kürzeste Möglichkeit, anzugeben, welche Elemente gemeint sind. Sie werden für die Gestaltung, für Abfragen in JavaScript und für die Datenextraktion in jeder seriösen Scraping-Bibliothek verwendet. Dies ist eine für den praktischen Einsatz gegliederte Referenz: zuerst die Syntax-Tabellen, dann die Pseudo-Klassen, die ihren Platz tatsächlich verdienen, anschließend die Spezifität und schließlich die Muster, die immer wieder auftauchen. Außerdem wird behandelt, was CSS noch nicht kann – eine Liste, die mittlerweile kürzer ist als früher.

Ein Hinweis dazu, wer dies geschrieben hat und warum das hier kaum eine Rolle spielt: Wir sind Geonode und verkaufen Proxys an Personen, die Datenauswertungen durchführen. Die Wahl des Selektors hat keinerlei Einfluss auf das, was wir verkaufen, und die einzige erwähnenswerte Überschneidung ist diagnostischer Natur – ein Selektor, der nichts findet, und eine blockierte Anfrage sehen von außen identisch aus, obwohl es sich um völlig unterschiedliche Probleme handelt. Wenn die Seite geladen wurde und Ihr Selektor nichts gefunden hat, ist dieser Hinweis genau richtig, und es muss nichts daran geändert werden, wie die Seite abgerufen wurde.

Grundlegende Selektoren

SelektorTrifft auf
*Jedes Element
divJedes „div“-Element
.cardElemente mit der Klasse „card“
#mainDas Element mit der ID „main“
.card.featuredElemente mit beiden Klassen
div.card„div“-Elemente mit der Klasse „card“
div, pElemente mit der Klasse „div“ und Elemente mit der Klasse „p“

Zwei davon sind es wert, näher betrachtet zu werden.

** „.card.featured“ ohne Leerzeichen bedeutet, dass beide Klassen auf einem Element vorhanden sind.** „.card .featured“ mit einem Leerzeichen bedeutet ein Element mit der Klasse „featured“ innerhalb eines Elements mit der Klasse „card“. Ein einziges Zeichen verändert die Bedeutung vollständig, und dies ist der häufigste Tippfehler in diesem gesamten Bereich.

** „div, p“ ist eine Selektorliste**, keine Beziehung. Sie passt auf alles in beiden Gruppen, und jeder Teil wird unabhängig ausgewertet – daher passt „div, p.note“ auf alle div-Elemente sowie Absätze mit der Klasse „note“, nicht jedoch auf div-Elemente mit der Klasse „note“.

Kombinatoren

Diese drücken Beziehungen zwischen Elementen aus.

SelektorÜbereinstimmungen
div pp an beliebiger Stelle innerhalb eines div (Nachkomme)
div > pp, das ein direktes Kind eines div ist
h2 + pDas p unmittelbar nach einem h2 (benachbartes Geschwisterelement)
h2 ~ pJedes p nach einem h2 mit demselben übergeordneten Element (allgemeines Geschwisterelement)

Der Nachkommen-Kombinator ist ein Leerzeichen, wodurch er leicht versehentlich eingefügt und beim Lesen leicht übersehen werden kann.

> ist präziser und in der Regel robuster. „div > p“ wird nicht mit einem Absatz übereinstimmen, den jemand nächsten Monat in ein „<section>“ einbindet – was zunächst wie ein Nachteil klingt, oft aber das Gegenteil ist, denn ein Selektor, der stillschweigend mehr Elemente als beabsichtigt abdeckt, ist schlechter als einer, der die Übereinstimmung beendet und Sie darauf hinweist.

„+“ und „~“ sind die Geschwisterkombinatoren und sie suchen nur vorwärts. Es gibt in CSS keinen Kombinator für „vorherige Geschwisterelemente“; :has() bietet diesen Effekt indirekt, worauf weiter unten eingegangen wird.

Attributselektoren

Oftmals die stabilste verfügbare Option, da Attribute in der Regel bewusst ausgewählt werden.

SelektorTrifft auf
[href]Elemente mit dem Attribut „href“
[href="/about"]Exakter Wert
[href^="/docs"]Wert beginnt mit
[href$=".pdf"]Wert endet mit
[href*="download"]Wert enthält
[class~="card"]Wert ist eine durch Leerzeichen getrennte Liste, die das Wort card enthält
[lang|="en"]Wert ist en oder beginnt mit en-
[data-state="open" i]Übereinstimmung ohne Berücksichtigung der Groß-/Kleinschreibung

Zwei davon werden zu selten genutzt.

[class~="card"] entspricht genau .card – der Operator „~=“ führt eine durch Leerzeichen getrennte Wortübereinstimmung durch, was genau den Anforderungen des „class“-Attributs entspricht. Zu wissen, dass es existiert, ist vor allem nützlich, um zu verstehen, was .card wirklich tut, und um Selektoren programmgesteuert zu erstellen, wenn man einen Attributnamen in einer Variablen hat.

Das Flag „i“ sorgt dafür, dass bei der Übereinstimmung die Groß-/Kleinschreibung ignoriert wird, was besonders praktisch ist, wenn Attributwerte auf einer Website unterschiedliche Schreibweisen aufweisen.

Speziell für die Extraktion sind „data-*“-Attribute den Klassen deutlich vorzuziehen. Ein „[data-testid="product-card"]“ wurde vom Entwickler als stabiler Anker gewählt; ein Klassenname hingegen kann bei jedem Deployment von einem Build-Tool neu generiert werden.

Pseudoklassen: Strukturell

SelektorTrifft auf
:first-childEin Element, das das erste untergeordnete Element seines übergeordneten Elements ist
:last-childDas letzte untergeordnete Element
:only-childEin Element ohne gleichrangige Elemente
:nth-child(3)Das dritte untergeordnete Element
:nth-child(2n)Untergeordnete Elemente mit gerader Nummer
:nth-child(2n+1)Untergeordnete Elemente mit ungerader Nummer
:nth-child(-n+3)Die ersten drei
:nth-last-child(2)Das vorletzte
:first-of-typeDas erste Element seines Typs unter den Geschwistern
:nth-of-type(2)Das zweite Element seines Typs
:emptyEin Element ohne untergeordnete Elemente, einschließlich Text
:rootDie Dokumentwurzel, in der Regel html

Die Varianten von „-of-type“ sind wichtiger, als man denkt. p:first-child findet nur dann einen Absatz, wenn dieser das erste untergeordnete Element seines übergeordneten Elements ist – wenn also eine Überschrift davor steht, wird nichts gefunden. p:first-of-type findet den ersten Absatz, unabhängig davon, was davor steht. Für die Extraktion ist fast immer die zweite Variante die beabsichtigte.

:nth-child(-n+3) für „die ersten drei“ ist die Redewendung aus der „An+B“-Syntax, die man sich merken sollte, ebenso wie :nth-child(n+4) für „ab dem vierten“. Kombiniert man beide, ergibt sich ein Bereich: :nth-child(n+2):nth-child(-n+5) wählt die Kinder zwei bis fünf aus.

Pseudoklassen: Logisch

Die Gruppe, die sich in den letzten Jahren am stärksten verändert hat.

SelektorTrifft auf
:not(.hidden)Elemente ohne die Klasse „hidden“
:is(h1, h2, h3)Jeder der aufgeführten Selektoren
:where(h1, h2, h3)Entspricht „:is()“, jedoch mit Spezifität Null
:has(> img)Elemente, die ein passendes untergeordnetes oder gleichrangiges Element enthalten

** „:is()“ und „:where()“** verkürzen sich wiederholende Listen. „:is(article, section) > h2“ ersetzt „article > h2, section > h2“, und die Einsparung summiert sich bei längeren Listen schnell. Der einzige Unterschied zwischen ihnen liegt in der Spezifität: :is() übernimmt die Spezifität seines spezifischsten Arguments, während :where() immer den Wert Null beiträgt.

:not() akzeptiert in modernen Browsern eine Selektorliste, sodass :not(.a, .b) funktioniert und „keines von beiden“ bedeutet. Das ältere Verhalten bei einem einzigen Argument erforderte die Verkettung von :not(.a):not(.b).

:has() ist das entscheidende Element. Die MDN-Dokumentation beschreibt es als Darstellung „eines Elements, wenn einer der als Argument übergebenen relativen Selektoren mindestens ein Element trifft, wenn er an diesem Element verankert ist“, und bietet „eine Möglichkeit, ein übergeordnetes Element oder ein vorhergehendes Geschwisterelement in Bezug auf ein Referenzelement auszuwählen“. Sein Status lautet „Baseline widely available“; er wird seit Dezember 2023 browserübergreifend unterstützt.

div.card:has(span.sold-out)     /* a card containing a sold-out marker */
h1:has(+ p)                     /* an h1 immediately followed by a p */
li:has(~ li.active)             /* an li with a later active sibling */
label:has(input:checked)        /* a label wrapping a checked input */

Drei dokumentierte Einschränkungen: Er „kann nicht“ innerhalb eines anderen :has() verschachtelt werden, Pseudo-Elemente sind darin oder als Anker dafür nicht zulässig, und seine Spezifität folgt derselben Regel wie bei :is() und :not() – dem spezifischsten Argument.

Pseudoklassen: „state“ und „form“

SelektorTrifft auf
:hover, :focus, :activeInteraktionszustände
:focus-visibleFokus, der einen sichtbaren Ring anzeigen sollte
:focus-withinEin Element, das ein fokussiertes untergeordnetes Element enthält
:checkedAktiviertes Kontrollkästchen, Radiobutton oder ausgewählte Option
:disabled, :enabledZustand von Formularsteuerelementen
:required, :optionalAttribute zur Formularvalidierung
:valid, :invalidZustand der Validierung von Einschränkungen
:placeholder-shownEin Eingabefeld, das seinen Platzhalter anzeigt
:targetDas Element, das dem URL-Fragment entspricht
:visited, :linkLink-Zustände

Bei den meisten davon handelt es sich eher um Stilfragen als um Fragen der Datenextraktion, mit zwei Ausnahmen. :checked ist für das Auslesen des Formularzustands in der Testautomatisierung wirklich nützlich, und :disabled gibt Auskunft darüber, ob ein Steuerelement interaktiv ist – was häufig erklärt, warum ein Klick keine Wirkung gezeigt hat.

Beachten Sie, dass :hover und :focus im Rahmen von Scraping nicht sinnvoll mit querySelectorAll abgefragt werden können, da keiner dieser Zustände in einem Dokument vorhanden ist, das Sie lediglich geparst haben.

Spezifität, kurz erklärt

Relevant, wenn Selektoren um die Stilzuweisung konkurrieren, und irrelevant für Abfragen – wissenswert, da es häufig zu Verwechslungen zwischen diesen beiden Kontexten kommt.

Die Spezifität wird anhand von drei Zahlen berechnet:

KomponenteBeitrag von
IDs#main
Klassen, Attribute, Pseudoklassen.card, [href], :hover
Elemente, Pseudoelementediv, ::before

Von links nach rechts verglichen: Eine beliebige Anzahl von Klassen wiegt niemals schwerer als eine einzelne ID. Inline-Stile übertrumpfen all das, und „!important“ übertrumpft wiederum diese, weshalb beide eher ein letzter Ausweg als ein Werkzeug sind.

Zwei Anmerkungen zur modernen Praxis: „:where()“ trägt null Spezifität bei, was es zur richtigen Wahl für Bibliotheksstandardwerte macht, die Nutzer problemlos überschreiben können sollten. Und „:is()“, „:not()“ sowie „:has()“ übernehmen alle die Spezifität ihres spezifischsten Arguments, sodass „:is(#main, div)“ genauso spezifisch ist wie „#main“.

Bei „querySelectorAll“ und „scraping“ spielt die Spezifität überhaupt keine Rolle. Der Selektor passt entweder auf ein Element oder nicht; es gibt keine Konkurrenz.

Muster für die Datenextraktion

Die Selektoren, die in einem Scraper die eigentliche Arbeit leisten.

Produktkarten in einem Raster:

div.product-grid > article.product-card

Links zu PDFs:

a[href$=".pdf"]

Externe Links:

a[href^="http"]:not([href*="example.com"])

Ein stabiler Test-Hook statt einer Klasse:

[data-testid="price"]

Ein Container, der etwas enthält:

tr:has(td.error)
article:has(img)
form:has(input:invalid)

Der erste Absatz eines Artikels, unabhängig davon, was davor steht:

article p:first-of-type

Eine Kopfzeile überspringen:

tbody tr:not(:first-child)

Die Zeilen zwei bis elf übernehmen:

tbody tr:nth-child(n+2):nth-child(-n+11)

Elemente mit einem bestimmten Klassen-Token, programmgesteuert erstellt:

[class~="card"]

Leere Zellen, die Sie ausschließen möchten:

td:not(:empty)

Selektoren schreiben, die nicht kaputtgehen

Eine Referenz zeigt Ihnen, was verfügbar ist. Die Wahl zwischen den Optionen ist entscheidend für die Langlebigkeit, und der Unterschied zwischen einem Selektor, der eine Neugestaltung übersteht, und einem, der schon nächste Woche nicht mehr funktioniert, hängt ganz davon ab, wovon er abhängt.

Ordnen Sie Ihre Anker danach, wie bewusst sie ausgewählt wurden. Ein „data-testid“ existiert, weil ein Entwickler ihn zur Automatisierung dort platziert hat; daher ist er das Stabilste auf der Seite. An zweiter Stelle steht eine ID – meist absichtlich gesetzt, gelegentlich generiert. Ein semantisches Element oder eine ARIA-Rolle ist stabil, weil es Bedeutung trägt. Ein manuell geschriebener Klassenname ist mäßig stabil. Ein generierter Klassenname wie „css-1x9dj2k“ ändert sich bei jedem Build und ist wertlos. Positionsselektoren sind am wenigsten stabil, da sie die exakte Form der Seite zu einem bestimmten Zeitpunkt kodieren.

Sei so spezifisch, wie es die Bedeutung erfordert, und nicht mehr. „body > div > div > div.content > p“ ist präzise und anfällig: Jeder an beliebiger Stelle in dieser Kette hinzugefügte Wrapper bricht die Kette. „.content p“ drückt die tatsächliche Anforderung aus und übersteht Umstrukturierungen. Der Instinkt, der Sicherheit halber die Spezifität zu erhöhen, führt zum Gegenteil von Sicherheit.

Bevorzuge Selektoren, die lautstark fehlschlagen. Ein Selektor, der auf null Elemente passt, wirft ein offensichtliches Problem auf. Ein Selektor, der auf zu viele Elemente passt, gibt stillschweigend das erste zurück – was zwar plausibel, aber möglicherweise falsch ist. Wenn du die Wahl hast, wähle die Version, die fehlschlägt, statt der Version, die sich verschiebt – div > p statt div p, wenn die Beziehung tatsächlich direkt ist.

Bewahre Selektoren an einem Ort auf. Vierzig über den gesamten Code verstreute Selektor-Strings sind vierzig unabhängige Schwachstellen. Sammelt man sie in einem einzigen Modul mit aussagekräftigen Namen, werden sie zu einer Übersicht über das, worauf man angewiesen ist, und die Aktualisierung nach einer Änderung an der Website ist dann eine Stunde Arbeit statt eines ganzen Tages archäologischer Suche.

Legen Sie die Form dessen fest, was Sie extrahieren. Das ist die Gewohnheit, die stillschweigende Abweichungen aufdeckt. Wenn ein Preis einem Währungsmuster entsprechen soll, überprüfe dies. Wenn eine Liste noch nie weniger als zwanzig Einträge hatte, behandle weniger als zwanzig als Fehler. Ohne diese Maßnahme erzeugt ein Selektor, der plötzlich das falsche Element auswählt, auf unbestimmte Zeit wohlgeformte, aber falsche Daten, und zu keinem Zeitpunkt wird eine Ausnahme ausgelöst.

Speichere den von dir geparsten HTML-Code. Wenn ein Selektor nicht mehr funktioniert, zeigt dir der Vergleich des alten Markups mit dem neuen innerhalb von Sekunden genau, was sich geändert hat. Ein erneutes Abrufen zur Untersuchung ist langsamer, kostet Bandbreite und liefert dir möglicherweise eine andere Seite als die, bei der der Fehler tatsächlich aufgetreten ist.

Was CSS immer noch nicht kann

Eine kurze Liste – und jeder Punkt ist ein triftiger Grund, auf XPath zurückzugreifen.

Abgleich anhand von Textinhalt. Es gibt kein Standard-:contains(). Es wurde vorgeschlagen und wieder verworfen, und kein Browser implementiert es. Dies ist die größte Lücke und der Grund, warum das Muster „einen Wert anhand seiner Bezeichnung finden“ in den Bereich von XPath fällt – //dt[normalize-space()='Price']/following-sibling::dd[1] hat kein CSS-Äquivalent.

Zu einem beliebigen Vorfahren navigieren. :has() ermöglicht die bedingte Auswahl von übergeordneten Elementen – „das div, das dieses span enthält“ –, aber es gibt keine allgemeine Vorfahrenachse. Um mehrere Ebenen zu einem bestimmten Container hinaufzugehen, ist XPath erforderlich.

Zeichenfolgenfunktionen. Kein Trimmen, keine Teilzeichenfolgen, keine Groß-/Kleinschreibung, keine Verkettung. Was auch immer Sie abgleichen, bearbeiten Sie anschließend in Ihrer Host-Sprache.

XML mit Namespaces abfragen. CSS bietet keine nennenswerte Namespace-Unterstützung. Verwenden Sie für Sitemaps, RSS oder SOAP XPath.

Direkte Auswahl vorhergehender Geschwisterelemente. „:has()“ bietet diesen Effekt – „h2:has(+ p)“ wählt die Überschrift aus –, aber es gibt keinen Kombinator „-“, der „+“ entspricht.

Das ist die vollständige Liste für praktische Zwecke. Alles andere, wofür man bisher auf XPath zurückgegriffen hat, erledigt CSS mittlerweile selbst – und das meist lesbarer.

Umweltunterschiede, die es zu prüfen gilt

Die häufigste Überraschung bei Selektoren ist, dass eine Funktion an einer anderen Stelle vorhanden ist als dort, wo man sie ausführt.

Browser unterstützen alle hier aufgeführten Funktionen, einschließlich :has(), :is(), :where() und :not() mit mehreren Argumenten.

Serverseitige Parser unterscheiden sich erheblich voneinander. Pythons „cssselect“, das von lxml und Scrapy verwendet wird, übersetzt CSS in XPath – was bei den klassischen Selektoren gut funktioniert, bei neueren jedoch weniger gut. Insbesondere die Unterstützung für „:has()“ ist nicht universell, und ein Selektor, der in einer Browserkonsole funktioniert, kann in einem Parser einen Fehler auslösen oder stillschweigend nichts zurückgeben.

BeautifulSoup nutzt soupsieve für seine Methode „select()“, die zwar eine gute, aber nicht vollständige Abdeckung bietet und keinerlei XPath-Unterstützung enthält.

Playwright und Selenium nutzen die Engine des Browsers, sodass die Unterstützung der des Browsers entspricht – wobei Playwright zusätzlich Text- und Rollen-Locators bietet, die einen Großteil dessen abdecken, was mit CSS nicht ausgedrückt werden kann.

Die bewährte Vorgehensweise: Testen Sie Selektoren in der Umgebung, in der sie ausgeführt werden sollen. Eine Browserkonsole ist zwar praktisch, testet jedoch eine andere Implementierung anhand eines Post-JavaScript-DOMs – beides sind Faktoren, die zu Fehlinterpretationen führen können.

Häufig gestellte Fragen

Was ist der Unterschied zwischen „.card.featured“ und „.card .featured“?

Die erste Variante ohne Leerzeichen findet ein Element, das beide Klassen trägt. Die zweite Variante mit einem Leerzeichen findet ein Element mit der Klasse „featured“ innerhalb eines Elements mit der Klasse „card“. Ein einziges Leerzeichen verändert die Bedeutung komplett und ist der häufigste Fehler bei CSS-Selektoren.

Wie wähle ich ein übergeordnetes Element in CSS aus?

Mit dem „:has()“ — div:has(> span.price) wählt das div anstelle des span aus. Dieser Selektor ist seit Dezember 2023 browserübergreifend weit verbreitet. Es gibt keinen allgemeinen „Ancestor-Selektor“, daher ist für den Zugriff auf einen bestimmten Container, der mehrere Ebenen höher liegt, weiterhin XPath erforderlich.

Können CSS-Selektoren Text abgleichen?

Nein. Es gibt keine Standard-Pseudoklasse „:contains()“; sie wurde vorgeschlagen, aber nie übernommen. Der Abgleich anhand von Textinhalt erfordert XPath oder einen bibliotheksspezifischen Locator wie „getByText()“ von Playwright.

Was ist der Unterschied zwischen „:nth-child“ und „:nth-of-type“? „

:nth-child“ zählt alle Kinder des übergeordneten Elements; „:nth-of-type“ zählt nur Elemente desselben Typs. Daher findet „p:first-child“ nichts, wenn eine Überschrift an erster Stelle steht, während „p:first-of-type“ unabhängig davon den ersten Absatz findet.

Was bewirkt „:is()“ und wie unterscheidet es sich von „:where()“?

Beide nehmen eine Selektorliste entgegen und finden einen beliebigen davon, wodurch sich wiederholende Selektoren verkürzt werden. Der einzige Unterschied liegt in der Spezifität: :is() übernimmt die Spezifität seines spezifischsten Arguments, während :where() immer den Wert Null beiträgt – was :where() ideal für Standardwerte macht, die leicht überschrieben werden sollen.

Wie funktioniert die CSS-Spezifität?

IDs haben Vorrang vor Klassen, Attributen und Pseudoklassen, die wiederum Vorrang vor Element- und Pseudoelement-Selektoren haben, wenn man die Komponenten einzeln vergleicht. Inline-Stile haben Vorrang vor Selektoren, und „!important“ hat wiederum Vorrang vor diesen. Nichts davon gilt, wenn Sie Selektoren zum Abfragen statt zum Gestalten verwenden.

Sind CSS-Selektoren besser als XPath zum Scraping?

Bei der Auswahl nach Klasse, ID, Attribut und Struktur ja – sie sind kürzer und übersichtlicher. Zum Abgleichen von Textinhalten oder zum Zurückverfolgen zu einem beliebigen übergeordneten Element ist XPath erforderlich. Die meisten Scraping-Bibliotheken unterstützen beides, und es ist üblich, jedes dort einzusetzen, wo es am besten passt.

Warum funktioniert mein Selektor im Browser, aber nicht in meinem Scraper?

Dafür gibt es zwei häufige Ursachen. Das DOM des Browsers wird nach der Ausführung von JavaScript aufgebaut, während Ihr Parser rohes HTML sieht; daher existiert das Element möglicherweise nicht in den von Ihnen abgerufenen Daten. Außerdem decken serverseitige CSS-Implementierungen nicht alle modernen Selektoren ab – insbesondere „:has()“ wird außerhalb von Browsern uneinheitlich unterstützt.

Fazit

Der Kern der CSS-Selektoren ist überschaubar: Typ, Klasse und ID zur Identifizierung, die vier Kombinatoren für Beziehungen, Attributselektoren für alles, was gezielt ausgewählt wird, sowie eine Handvoll Pseudoklassen für Position und Zustand.

Drei Gewohnheiten verbessern echte Selektoren mehr als die Kenntnis zusätzlicher Syntax. Bevorzugen Sie „>“ gegenüber einem bloßen Leerzeichen, wenn die Beziehung wirklich direkt ist, denn ein Selektor, der nicht mehr passt, ist nützlicher als einer, der zu viel abdeckt. Bevorzugen Sie „data-*“-Attribute gegenüber Klassennamen, da Klassen von Build-Tools neu generiert werden, während Attribute von Menschen ausgewählt werden. Und bevorzugen Sie „:nth-of-type“ gegenüber „:nth-child“, wenn Sie „den zweiten Absatz“ und nicht „das zweite Kind“ meinen.

:has() ist eine Ergänzung, deren bewusste Übernahme sich lohnt. Die Auswahl von übergeordneten Elementen und Bedingungen für gleichrangige Elemente im reinen CSS beseitigt einen großen Teil dessen, was Nutzer zuvor zum Einsatz von XPath gezwungen hat, und ist bereits lange genug weit verbreitet, um sich im Browser darauf verlassen zu können. Außerhalb von Browsern sollten Sie zunächst Ihren Parser überprüfen – dort lauern die verbleibenden Überraschungen.