Geonode logo
Geonode Team

Geonode Team

Aktualisiert: 7. Oktober 2026

Veröffentlicht: 02.09.2026

XPath nach Klassen: Leitfaden mit Beispielen

Die Auswahl nach Klasse ist die häufigste Anwendung eines Selektors, und genau darin ist XPath am schlechtesten. Der Ausdruck, den jeder als Erstes schreibt – ``//div[@class='card']`` – findet nur Elemente, deren `class`-Attribut genau dieser Zeichenfolge entspricht, sodass ``class="card featured"`` nicht berücksichtigt wird. Die naheliegende Korrektur, ``contains(@class, 'card')``, findet auch ``card-large`` und ``discard``. Beides ist falsch. Hier ist die richtige Lösung sowie eine Erklärung, wann stattdessen ein CSS-Selektor verwendet werden sollte.

Unsere Position lässt sich einfach zusammenfassen: Wir sind Geonode und verkaufen Proxys, was nichts mit der Klassenauswahl zu tun hat. Die einzige relevante Beobachtung ist, dass klassenbasierte Selektoren die anfälligste Art sind, und diese Anfälligkeit sieht von außen betrachtet genau wie ein Netzwerkproblem aus – die Anfrage ist erfolgreich, die Seite wird geladen, und Ihre Datenextraktion liefert nichts oder das Falsche zurück. Wenn Sie einen Scraper debuggen, der nicht mehr funktioniert, prüfen Sie zunächst, ob sich die Klassennamen geändert haben, bevor Sie irgendetwas daran überprüfen, wie die Seite abgerufen wurde. Auf einer Website, die eine moderne Build-Pipeline verwendet, können sich diese bei jedem Deployment ändern.

Das Problem mit dem „class“-Attribut „

class“ enthält eine durch Leerzeichen getrennte Liste von Tokens, doch XPath kennt kein solches Listenkonzept. Es sieht lediglich eine einzige Zeichenkette.

<div class="card featured large">...</div>

Für XPath ist „@class“ die Zeichenkette „"card featured large"“. Es gibt keine integrierte Möglichkeit zu fragen: „Ist ‚card‘ eines der Token?“, was genau die Frage ist, die ein CSS-Selektor nativ mit „.card“ beantwortet.

Diese Lücke führt zu den beiden Fehlermodi.

Die exakte Übereinstimmung ist zu streng:

//div[@class='card']

Es wird ausschließlich class="card" gefunden, nichts anderes und keine zusätzlichen Leerzeichen. class="card featured", class="featured card" und class=" card " werden nicht gefunden. Auf einer echten Seite würde dies den Großteil dessen übersehen, was Sie eigentlich wollten.

Die Teilzeichenfolgenübereinstimmung ist zu locker:

//div[contains(@class, 'card')]

Findet korrekt „class="card"“ sowie „class="card-large"“, „class="postcard"“, „class="discard"“ und „class="card-footer"“. Auf einer Seite mit ähnlichen Bezeichnungen liefert es eine Obermenge dessen, was Sie angefordert haben – und Ihr Code nimmt stillschweigend das erste Ergebnis an.

Der zweite Fehler ist der gefährlichere, da er Ergebnisse liefert. Es wird etwas zurückgegeben, es sieht plausibel aus, und es ist das falsche Element.

Die korrekte Vorgehensweise

Die Standardlösung fügt auf beiden Seiten Leerzeichen hinzu, sodass nur ganze Token übereinstimmen können:

//div[contains(concat(' ', normalize-space(@class), ' '), ' card ')]

Lesen Sie es Schritt für Schritt durch:

**normalize-space(@class)

** entfernt führende und nachgestellte Leerzeichen und reduziert interne Leerzeichenfolgen auf einzelne Leerzeichen. Aus „" card featured "

“ wird „"card featured"

“.

**concat(' ', ..., ' ')

** umschließt das Ergebnis mit Leerzeichen, sodass " card featured "

entsteht. Nun ist jedes Token auf beiden Seiten durch ein Leerzeichen begrenzt.

**contains(..., ' card ')

** sucht nach dem Ziel, das von Leerzeichen umgeben ist.

Prüfen Sie dies anhand der Fälle, an denen die naiven Versionen gescheitert sind:

| Klassenattribut | Aufgefüllte Zeichenkette | Enthält „' card '

“? | |---|---|---| | card

| " card "

| Ja | | card featured

| " card featured "

| Ja | | featured card

| " featured card "

| Ja | | card-large

| " card-large "

| Nein | | discard

| " discard "

| Nein | | postcard footer

| " postcard footer "

| Nein | | card

| " card "

| Ja |

In jedem Fall korrekt. Der Schritt „normalize-space()

“ ist nicht nur eine Zierde – ohne ihn würde „class="card featured"

“ mit einem doppelten Leerzeichen „" card featured "

“ ergeben, was zwar immer noch „" card "

“ enthält und zufällig funktioniert, aber „class="card\nfeatured"

“ mit einem Zeilenumbruch würde nicht funktionieren.

Er ist umständlich. Packen Sie ihn ein:

def has_class(name):
    return f"contains(concat(' ', normalize-space(@class), ' '), ' {name} ')"

tree.xpath(f"//div[{has_class('card')}]")
const hasClass = name =>
  `contains(concat(' ', normalize-space(@class), ' '), ' ${name} ')`;

Jede Codebasis, die ernsthafte Extraktionen mit XPath durchführt, verfügt letztendlich über eine Variante dieses Hilfsausdrucks. Es ist besser, ihn einmal zu schreiben, als bei einem von zwanzig Ausdrücken die Auffüllung falsch zu machen.

Mehrere Klassen

Kombinieren Sie dies mit „and

“:

//div[contains(concat(' ', normalize-space(@class), ' '), ' card ')
      and contains(concat(' ', normalize-space(@class), ' '), ' featured ')]

An dieser Stelle wird die Ausführlichkeit wirklich lästig – das sind 140 Zeichen, um auszudrücken, was CSS mit „div.card.featured

“ sagt.

Für „eine von zwei Klassen“ verwenden Sie „or

“:

//div[contains(concat(' ', normalize-space(@class), ' '), ' card ')
      or contains(concat(' ', normalize-space(@class), ' '), ' tile ')]

Für „hat diese Klasse, aber nicht jene“:

//div[contains(concat(' ', normalize-space(@class), ' '), ' card ')
      and not(contains(concat(' ', normalize-space(@class), ' '), ' hidden '))]

Bei zwei oder mehr Klassenbedingungen sollten Sie ernsthaft in Erwägung ziehen, ob ein CSS-Selektor ausreichen würde. div.card.featured:not(.hidden)

ist dieselbe Logik mit nur einem Fünftel der Zeichen, und jede gängige Parsing-Bibliothek unterstützt neben XPath auch CSS-Selektoren. Es gibt keine Regel, die vorschreibt, dass Sie sich für die gesamte Datei auf eine Sprache festlegen müssen.

Generierte und gehashtete Klassennamen

Die moderne Komplikation, die die Empfehlung verändert.

Viele Frontend-Build-Tools generieren Klassennamen mit Scope, um Kollisionen zu vermeiden – CSS Modules, styled-components, verschiedene CSS-in-JS-Bibliotheken. Das Ergebnis sieht so aus:

<div class="ProductCard_container__3xK9p">...</div>
<div class="css-1x9dj2k">...</div>

Der Hash-Teil ändert sich, sobald sich die Stile der Komponente ändern, was in der Praxis bei vielen Deploys der Fall ist. Ein an den vollständigen Namen verankerter Selektor funktioniert dann ohne Vorwarnung nicht mehr.

Drei Möglichkeiten, damit umzugehen, in der Reihenfolge ihrer Präferenz.

Verankere dich am stabilen Präfix, sofern das Tool eines erzeugt:

//div[starts-with(@class, 'ProductCard_container__')]

CSS-Module erzeugen üblicherweise ComponentName_elementName__hash

, sodass der Teil vor dem abschließenden doppelten Unterstrich über alle Builds hinweg stabil ist. Dies funktioniert gut, sofern es anwendbar ist.

**Suche stattdessen nach einem „data-*

“-Attribut.** Viele Anwendungen enthalten Attribute wie data-testid

, data-test

oder Ähnliches, gerade damit automatisierte Tools einen stabilen Anknüpfungspunkt haben:

//div[@data-testid='product-card']

Wenn ein solches Attribut vorhanden ist, nutze es. Es ist stabiler als jeder Klassenname, da es bewusst gewählt und nicht generiert wurde.

Verankere dich an etwas völlig anderem. Orientiere dich relativ zu einer Überschrift, einem Textinhalt oder einem Elementtyp. //h2[normalize-space()='Featured']/following-sibling::div[1]

kümmert es nicht, wie die Klassen heißen.

Und im völlig undurchsichtigen Fall – css-1x9dj2k

ohne stabile Komponente – ist eine klassenbasierte Auswahl schlichtweg nicht realisierbar, und so zu tun, als wäre es anders, führt zu einem Scraper, der wöchentlich ausfällt. Suchen Sie nach strukturierten Daten auf der Seite oder nach der API, die die Seite selbst aufruft.

Reihenfolge der Klassen und Leerzeichen

Zwei Dinge, die vielen Leuten Probleme bereiten und bei denen es sich lohnt, genau zu sein.

Die Reihenfolge der Klassen im Attribut ist bedeutungslos. „class="card featured" “ und „class="featured card" “ sind für den Browser und für CSS gleichbedeutend. Die „padded-concat“-Methode behandelt beide Fälle korrekt; eine exakte Übereinstimmung behandelt keinen der beiden Fälle zuverlässig. Wenn Sie feststellen, dass Sie einen Ausdruck schreiben, der von der Reihenfolge abhängt, ist das ein Zeichen dafür, dass etwas nicht stimmt.

Leerzeichen können beliebig sein. Tabulatoren und Zeilenumbrüche sind gültige Trennzeichen in einem Klassenattribut und kommen in manuell formatiertem HTML vor:

<div class="card
            featured">

normalize-space() fasst all das zusammen, weshalb die Schreibweise dies berücksichtigt. Ein Ausdruck, der „concat(' ', @class, ' ') “ ohne Normalisierung verwendet, schlägt bei diesem Markup fehl, und dieser Fehler ist auf einer gerenderten Seite nicht sichtbar.

Die Groß-/Kleinschreibung spielt eine Rolle. Klassennamen werden in HTML-Dokumenten, die als XHTML geparst werden, groß-/kleinschreibungsabhängig behandelt, während sie im Standardmodus von HTML für die CSS-Übereinstimmung groß-/kleinschreibungsunabhängig behandelt werden – der Zeichenfolgenvergleich in XPath ist jedoch immer groß-/kleinschreibungsabhängig. Wenn eine Seite „Card “ und „card “ vermischt, behandelt die „padded“-Konvention sie als unterschiedlich. XPath 1.0 verfügt über keine „lower-case() “-Funktion, daher ist die Umgehungslösung „translate() “ mit expliziten Buchstaben, wodurch der Ausdruck jedoch völlig unlesbar wird und ein CSS-Selektor eindeutig die bessere Wahl ist.

Den Vorfahren oder Nachkommen einer Klasse ermitteln

Die Auswahl einer Klasse ist in der Regel eher ein Mittel als ein Zweck. Zwei Muster decken den Großteil der Fälle ab.

Von einer Klasse zu einem Element innerhalb dieser Klasse:

//div[contains(concat(' ',normalize-space(@class),' '),' card ')]//span[@class='price']

Oder verwenden Sie in Code, der das card-Element bereits enthält, einen relativen Ausdruck – wobei der führende Punkt unerlässlich ist:

for card in tree.xpath(f"//div[{has_class('card')}]"):
    price = card.xpath(".//span[@class='price']/text()")

`

.//span

durchsucht diecard. ``//span

durchsucht das gesamte Dokument ausgehend von der Wurzel, wodurch für jedecard` der erste Preis auf der Seite zurückgegeben wird. Dies führt zu einheitlichen, plausiblen, aber falschen Ergebnissen und ist einer der häufigsten Fehler im Extraktionscode.

Von einem Element bis zu seinem Container:

//span[@class='price']/ancestor::div[contains(concat(' ',normalize-space(@class),' '),' card ')][1]

Die Option „[1]

“ ist entscheidend: ancestor::

ist eine umgekehrte Achse, sodass Position 1 den nächstgelegenen passenden Vorfahren angibt und nicht den äußersten. Ohne diese Option erhält man jeden passenden Vorfahren, und bei verschachtelten Containern ist das selten das, was man möchte.

Bibliotheksspezifische Hinweise

Die Syntax ist überall gleich; die umgebende API hingegen nicht, und einige Unterschiede zwischen den Bibliotheken führen zu vermeidbarer Verwirrung.

lxml (Python). Unterstützt beide Sprachen, und „cssselect “ ist die pragmatische Wahl für den Unterricht:

from lxml import html
tree = html.fromstring(source)

tree.cssselect('div.card.featured')                  # clear
tree.xpath(f"//div[{has_class('card')}]")            # when inside a larger expression

„

cssselect “ übersetzt CSS intern in XPath, sodass beide für die unterstützten Selektoren die gleichen Funktionen bieten. Beachten Sie, dass es sich um ein von lxml selbst getrenntes Paket handelt, das installiert werden muss.

BeautifulSoup (Python). Bietet keinerlei XPath-Unterstützung – eine Tatsache, die Nutzer aus anderen Ökosystemen oft überrascht. Es bietet select() für CSS-Selektoren und eine eigene find_all(class_='card') -API, die nativ korrekte Token-Übereinstimmungen durchführt. Wenn Sie speziell XPath benötigen, benötigen Sie lxml.

Selenium. Akzeptiert sowohl By.XPATH als auch By.CSS_SELECTOR und nutzt für beide die Engine des jeweiligen Browsers. Das bedeutet: ausschließlich XPath 1.0, und die CSS-Unterstützung entspricht dem, was der Browser unterstützt – einschließlich :has() .

Playwright. Erkennt den Locator-Typ automatisch anhand der Zeichenkette, sodass sowohl page.locator('div.card') als auch page.locator('//div[@id="x"]') ohne Präfix funktionieren. Außerdem bietet es textbasierte Locators – page.getByText() , page.getByRole() –, die einen Großteil der Anwendungsfälle abdecken, für die man zuvor den Textabgleich von XPath benötigte, und die lesbarer sind als beide Sprachen.

Scrapy. Bietet .css() und .xpath() für Selektoren, und diese lassen sich verketten, was wirklich nützlich ist:

for card in response.css('div.card'):
    price = card.xpath(".//dt[normalize-space()='Price']/following-sibling::dd[1]/text()").get()

CSS für den Strukturdurchlauf, XPath für die Label-Suche, in derselben Ausdruckskette. Beachten Sie das vorangestellte „. “ im XPath – Scrapys verkettete Selektoren weisen dieselbe „Root-versus-Relative“-Falle auf wie alles andere auch.

Browser-Konsole. $x('//div[@class="card"]') wertet XPath in den Entwicklertools von Chrome und Firefox aus, und document.querySelectorAll('div.card') verarbeitet CSS. Es lohnt sich, einen Ausdruck hier zu testen, bevor man ihn in den Code einfügt – allerdings mit der Einschränkung, dass das DOM des Browsers nach der Ausführung von JavaScript vorliegt, das Ihres Parsers jedoch nicht: Ein Ausdruck, der in der Konsole funktioniert, findet im rohen HTML möglicherweise nichts.

Wann man stattdessen CSS verwenden sollte

Ganz einfach: Weil die Antwort für diese spezielle Aufgabe in der Regel „Ja“ lautet.

Verwenden Sie CSS, wenn Ihre Kriterien Klassen sind. div.card, div.card.featured, div.card:not(.hidden) – alles klarer, kürzer und standardmäßig korrekt. CSS interpretiert das class-Attribut als eine Liste von Tokens, genau das, was XPath fehlt.

Verwenden Sie XPath, wenn die Klasse nebensächlich ist und das eigentliche Kriterium etwas ist, das CSS nicht ausdrücken kann – vor allem Textinhalt. //div[contains(@class,'card')][.//span[contains(., 'Sold out')]] benötigt XPath für die Textbedingung, und der Klassenteil kommt einfach mit.

Kombinieren Sie beides. Jede gängige Parsing-Bibliothek unterstützt beides. lxml bietet neben xpath auch cssselect an; Selenium akzeptiert beide Locator-Strategien; Playwright akzeptiert ebenfalls beide. Die Verwendung von CSS für die strukturelle Auswahl und von XPath für die Textbedingungen ist kein Kompromiss, sondern die Vorgehensweise, die den kürzesten lesbaren Code ergibt.

Der einzige Fall, in dem eine XPath-Klassenauswahl sinnvoll ist, ist, wenn man sie innerhalb eines größeren XPath-Ausdrucks benötigt und ein Wechsel der Sprache mitten im Ausdruck nicht möglich ist. Das ist eine echte Einschränkung, und deshalb gibt es diese Vorgehensweise – aber sie ist seltener anzutreffen als die Vielzahl von XPath-Klassenabgleichen, die man in der Praxis vorfindet.

Häufig gestellte Fragen

Wie wähle ich in XPath ein Element anhand seiner Klasse aus?

Verwenden Sie „//div[contains(concat(' ', normalize-space(@class), ' '), ' card ')]“. Das „padding“ stellt sicher, dass nur ganze Klassennamen übereinstimmen. Bei der einfachen Form „@class='card'“ werden Elemente mit zusätzlichen Klassen übersehen, und „contains(@class,'card')“ findet auch „card-large“ und „discard“.

Warum findet „contains(@class, 'name')“ die falschen Elemente?

Weil es sich um einen einfachen Teilzeichenfolgen-Test ohne Berücksichtigung von Wortgrenzen handelt. Das „class“-Attribut ist eine durch Leerzeichen getrennte Liste, aber XPath sieht eine einzige Zeichenkette, sodass „contains(@class,'card')“ jeden Klassennamen findet, der diese vier Zeichen an beliebiger Stelle enthält.

Wie wähle ich in XPath ein Element mit zwei Klassen aus?

Kombinieren Sie zwei „padded-concat“-Bedingungen mit „and“. Das funktioniert und ist etwa 140 Zeichen lang. Bei zwei oder mehr Klassenbedingungen ist ein CSS-Selektor – „div.card.featured“ – deutlich übersichtlicher, und die meisten Parsing-Bibliotheken ermöglichen dessen Verwendung.

Spielt die Reihenfolge der Klassen in XPath eine Rolle?

Nicht beim „padded-concat“-Idiom, das ein Token unabhängig von seiner Position im Attribut findet. Bei exakten Vergleichen spielt sie jedoch eine Rolle, was einer von mehreren Gründen ist, warum „@class='card featured'“ eine schlechte Wahl ist. Die Reihenfolge der Klassen ist in HTML bedeutungslos, daher ist ein Selektor, der davon abhängt, ein potenzieller Fehler.

Wie gehe ich mit zufällig generierten Klassennamen um?

Verankere dich am stabilen Präfix mit „starts-with()“, sofern das Tool eines generiert, verwende ein „data-testid“-Attribut, falls die Anwendung eines bereitstellt, oder verankere dich an etwas völlig anderem wie Text oder Struktur. Bei vollständig undurchsichtigen, gehashten Namen ohne stabilen Teil ist eine klassenbasierte Auswahl nicht praktikabel.

Ist XPath oder CSS besser für die Auswahl nach Klasse geeignet?

Ganz klar CSS. Es behandelt das „class“-Attribut als Token-Liste – was es ja auch ist –, sodass „div.card“ sowohl kürzer als auch korrekt ist. XPath benötigt eine 70 Zeichen lange Syntax, um dasselbe auszudrücken. Verwenden Sie XPath, wenn Sie zusätzlich etwas benötigen, was CSS nicht leisten kann, wie zum Beispiel das Abgleichen von Text.

Wie finde ich in XPath ein übergeordnetes Element anhand der Klasse?

//span[@class='price']/ancestor::div[contains(concat(' ',normalize-space(@class),' '),' card ')][1]. Das „[1]“ wählt den nächstgelegenen passenden Vorfahren aus, da „ancestor::“ eine umgekehrte Achse ist, bei der Position 1 „nächstgelegen“ statt „äußerst“ bedeutet.

Warum gibt mein relativer XPath für jedes Element denselben Wert zurück?

Weil Sie innerhalb der Schleife „//“ anstelle von „.//“ verwendet haben. Ein vorangestelltes „//“ sucht unabhängig vom Kontext ab der Dokumentwurzel, sodass bei jeder Iteration die erste Übereinstimmung auf der gesamten Seite gefunden wird. „.//“ sucht innerhalb des aktuellen Elements, was genau Ihrer Absicht entspricht.

Fazit

Bei der Klassenauswahl zeigt sich, dass XPath in die Jahre gekommen ist. Das „class“-Attribut ist eine Token-Liste, und da XPath keine Token-Operationen kennt, muss man, um „hat diese Klasse“ auszudrücken, die Zeichenkette mit Leerzeichen auffüllen und nach einem aufgefüllten Token suchen – eine 70 Zeichen lange Konstruktion für etwas, das CSS in neun Zeichen ausdrückt.

Lerne diese Schreibweise, denn du wirst sie in größeren XPath-Ausdrücken benötigen, und verpacke sie in eine Hilfsfunktion, damit du sie nur einmal statt zwanzig Mal schreiben musst. Der Schritt „normalize-space()“ ist nicht optional: Echtes Markup enthält Zeilenumbrüche und Tabulatoren in Klassenattributen, und ein nicht normalisierter Ausdruck scheitert daran unbemerkt.

Die nützlichere Schlussfolgerung ist jedoch die, die auf diese Schreibweise verzichtet. Wenn deine Auswahlkriterien ausschließlich Klassen sind, schreibe einen CSS-Selektor. Jede gängige Bibliothek unterstützt beides; die Kombination in einer Datei ist üblich, und die Wahl des richtigen Werkzeugs pro Ausdruck führt zu kürzerem Code, den ein Kollege lesen kann.

Und betrachte Klassennamen als den instabilsten verfügbaren Anker. Generierte und gehashtete Namen ändern sich bei der Bereitstellung; selbst handgeschriebene Namen ändern sich, sobald jemand das Design einer Komponente überarbeitet. Ein „data-testid“, der Text einer Überschrift oder die Beziehung eines Elements zu etwas Identifizierbarem überdauern den Klassennamen – und der Fehlerfall, wenn eine Klasse verschwindet, ist kein Fehler, sondern eine stille, wohlgeformte, leere Ausgabe.