Nach dem Herunterladen einer Seite beginnt die eigentliche Arbeit beim Scraping: Produktname, Preis und Link unter Hunderten Tags finden und extrahieren. Welches Element Sie wollen, beschreiben Sie mit einem Selektor. Zwei Sprachen sind verbreitet: CSS-Selektoren, die Webentwickler aus Stylesheets kennen, und XPath aus der XML-Welt. Oft wählen Sie dasselbe Element mit beiden aus, doch die eine ist kurz und lesbar, während die andere manches kann, was CSS gar nicht beherrscht.
In diesem Artikel erklären wir, was die beiden Sprachen sind, stellen ihre Syntax gegenüber, zeigen, was XPath über CSS hinaus kann, und prüfen, ob der Leistungsunterschied wirklich zählt. Danach zeigen wir, wie Sie Selektoren schreiben, die bei Designänderungen nicht brechen, und wie Sie Selektoren im Browser testen. In der Mitte des Artikels finden Sie eine Kurzreferenz mit 20 Zeilen zum Speichern; auf einer Testseite haben wir geprüft, dass jedes CSS- und XPath-Paar der Tabelle dieselben Elemente auswählt.
Was ist ein CSS-Selektor?
Ein CSS-Selektor ist die Mustersprache, mit der in CSS-Stylesheets festgelegt wird, für welche Elemente ein Stil gilt. Browser unterstützen dieselbe Sprache in JavaScript über document.querySelector() und querySelectorAll(). Die aktuelle Definition ist Selectors Level 4 des W3C.
Ein CSS-Selektor wählt ein Element aus nach:
- Tag:
div,a,span - Klasse:
.product - ID:
#list - Attribut:
a[href],img[src$=".webp"] - Hierarchie:
ul > li(direktes Kind),div span(Nachfahre in beliebiger Tiefe) - Geschwister:
h2 + p(unmittelbar folgend),h2 ~ span(alle folgenden Geschwister) - Position:
li:first-of-type,li:nth-of-type(3)
Die Grundrichtung von CSS ist nach unten und nach vorne: Sie gelangen von einem Element zu seinen Nachfahren und folgenden Geschwistern. Die in Selectors Level 4 eingeführte Pseudoklasse :has() lockert diese Regel teilweise: div.product:has(> span.discount) wählt Produktkarten, die ein Rabattetikett enthalten. :has() „klettert" aber nicht von einem Element nach oben; es wählt das äußere Element anhand seines Inhalts aus.
Was ist XPath?
XPath (XML Path Language) ist eine Abfragesprache, die Knoten in XML- und HTML-Dokumenten mit einem Pfadausdruck auswählt. Sie sieht das Dokument als Baum und kann sich darin in jede Richtung bewegen: nach unten, nach oben und zu vorherigen und folgenden Geschwistern. Die aktuelle Definition des W3C ist XPath 3.1.
Ein wichtiges praktisches Detail: Browser und die Python-Bibliothek lxml unterstützen XPath 1.0. In späteren Versionen hinzugekommene Funktionen wie ends-with(), Funktionen für reguläre Ausdrücke und ein reichhaltigeres Typsystem gibt es in diesen Umgebungen nicht. Halten Sie XPath-Ausdrücke für Scraping innerhalb der Grenzen von 1.0.
Die Grundbausteine von XPath:
//beliebige Tiefe im Dokument,/direktes Kind://ul/li[...]eine Bedingung (Prädikat)://a[@href],//li[3]@Attribut://img/@srctext()Textknoten://h2/text()- Achsen:
parent::,ancestor::,following-sibling::,preceding-sibling:: - Funktionen:
contains(),starts-with(),normalize-space(),last(),string-length()
Im Browser läuft XPath über die JavaScript-Funktion document.evaluate(); ihre Nutzung beschreibt die MDN-Dokumentation.
Syntaxvergleich
| Kriterium | CSS-Selektor | XPath |
|---|---|---|
| Lesbarkeit | Kurz, Webentwicklern vertraut | Lang, steilere Lernkurve |
| Richtung | Nach unten und vorne (mit :has() teils nach außen) | Jede Richtung: Elternelement, vorheriges Geschwister, Vorfahre |
| Auswahl nach Text | Nicht im Standard | Ja: text(), contains() |
| Attributwerte zurückgeben | Nein (mit Bibliothekserweiterungen) | Ja: /@href |
| Textknoten zurückgeben | Nein (mit Bibliothekserweiterungen) | Ja: /text() |
| Klassenabgleich | .product exakt und kurz | contains(@class, ...) erfordert Sorgfalt |
| Browserunterstützung | querySelectorAll | document.evaluate (XPath 1.0) |
| Python-Unterstützung | BeautifulSoup, lxml (cssselect), parsel | lxml, parsel; BeautifulSoup unterstützt es nicht |
| Headless-Browser | Playwright, Puppeteer, Selenium | Playwright, Puppeteer, Selenium |
| Typischer Einsatz | Listen nach Klasse und Attribut extrahieren | Label-Wert-Tabellen, textbasierte Auswahl |
Kurzreferenz
Jede Zeile der folgenden Tabelle ist dieselbe Auswahl in beiden Sprachen. In Zeilen ohne CSS-Entsprechung sehen Sie, was XPath zusätzlich kann.
| # | Was wird ausgewählt? | CSS-Selektor | XPath |
|---|---|---|---|
| 1 | Alle div-Elemente | div | //div |
| 2 | Nach ID | #list | //*[@id="list"] |
| 3 | Nach Klasse | .product | //*[contains(concat(" ", normalize-space(@class), " "), " product ")] |
| 4 | Direktes Kind | ul > li | //ul/li |
| 5 | Nachfahre in beliebiger Tiefe | div span | //div//span |
| 6 | Hat das Attribut | a[href] | //a[@href] |
| 7 | Attributwert ist gleich | input[name="q"] | //input[@name="q"] |
| 8 | Attribut beginnt mit | a[href^="https"] | //a[starts-with(@href, "https")] |
| 9 | Attribut enthält | a[href*="product"] | //a[contains(@href, "product")] |
| 10 | Attribut endet mit | img[src$=".webp"] | //img[substring(@src, string-length(@src) - 4) = ".webp"] |
| 11 | Erstes Element | ul > li:first-of-type | //ul/li[1] |
| 12 | Letztes Element | ul > li:last-of-type | //ul/li[last()] |
| 13 | Drittes Element | ul > li:nth-of-type(3) | //ul/li[3] |
| 14 | Unmittelbar folgendes Geschwister | h2 + p | //h2/following-sibling::*[1][self::p] |
| 15 | Alle folgenden Geschwister | h2 ~ span | //h2/following-sibling::span |
| 16 | Mehrere Auswahlen | h1, h2 | //h1 | //h2 |
| 17 | Karte mit einem bestimmten Element | div.product:has(> span.discount) | //span[@class="discount"]/.. |
| 18 | Schaltfläche mit exaktem Text | Nicht im Standard | //button[text()="In den Warenkorb"] |
| 19 | Ein bestimmter Vorfahre eines Elements | Nicht im Standard | //span[@class="price"]/ancestor::div[@data-sku][1] |
| 20 | Wert neben einem Label | Nicht im Standard | //th[normalize-space()="Bestand"]/following-sibling::td[1] |
Einige Details der Tabelle führen oft zu Fehlern:
- Zeile 3:
//*[contains(@class, "product")]wirkt kürzer, wählt aber auch Klassen wieproductsoderold-product. Der lange Ausdruck in der Tabelle passt nur auf die exakte Klasseproduct. - Zeile 10: XPath 1.0 hat kein
ends-with(); die letzten Zeichen werden mitsubstringverglichen. Die Zahl ist um eins kleiner als die Länge der gesuchten Endung (.webphat fünf Zeichen,- 4). - Zeile 11:
li:first-childundli:first-of-typesind verschieden. Ersteres wählt einli, das erstes Kind seines Elternelements ist; ist das erste Kind ein anderes Tag, wählt es nichts. - Zeile 14:
//h2/following-sibling::p[1]bedeutet „das erste folgendep" und passt auch, wenn andere Elemente dazwischenliegen.h2 + pin CSS passt nur, wenn das Element direkt nachh2einpist.
Um Attributwerte und Textknoten zurückzugeben, nutzt XPath //a/@href und //h2/text(). Die Python-Bibliothek parsel ergänzt CSS dafür um die nicht standardisierten Erweiterungen a::attr(href) und h2::text.
Was XPath kann und CSS nicht
Auswahl nach Text
Auf einer E-Commerce-Seite können die Schaltflächen „In den Warenkorb" und „Nicht vorrätig" dieselbe Klasse tragen. Nur ihr Text unterscheidet sie:
//button[text()="In den Warenkorb"]
//span[contains(normalize-space(.), "Rabatt")]text() betrachtet nur den direkten Textknoten des Elements; ist der Text auf verschachtelte Tags verteilt, passt es nicht. normalize-space(.) fügt den gesamten Text des Elements zusammen und entfernt Leerzeichen am Anfang und Ende, daher ist es meist zuverlässiger.
Standard-CSS kennt keine Auswahl nach Text. Manche Werkzeuge bieten eigene Erweiterungen: die Option has_text und :has-text() in Playwright sowie :-soup-contains() in BeautifulSoup. Diese Erweiterungen funktionieren nur im jeweiligen Werkzeug.
Zum Elternelement und vorherigen Geschwister klettern
Hat auf einer Produktkarte nur das Preisfeld eine stabile Klasse, müssen Sie vom Preis ausgehen, um die ganze Karte zu erreichen:
//span[@class="price"]/..
//span[@class="price"]/ancestor::div[@data-sku][1].. wählt das direkte Elternelement, ancestor:: einen Vorfahren auf beliebiger Ebene darüber. [1] nimmt den nächstgelegenen Vorfahren.
Label-Wert-Strukturen
Produktspezifikationstabellen und Definitionslisten gehören zu den Strukturen, auf die Scraping am häufigsten stößt. Welche Zeile in welcher Reihenfolge kommt, variiert je Produkt; fest bleibt der Text des Labels:
//th[normalize-space()="Bestand"]/following-sibling::td[1]
//dt[normalize-space()="Garantie"]/following-sibling::dd[1]Diese Ausdrücke finden den Wert neben „Bestand", egal in welcher Tabellenzeile er steht. Mit CSS müssten Sie dafür jede Zeile holen und in Python durchlaufen.
CSS und XPath in Python
Python hat drei verbreitete Bibliotheken, und ihre Selektorunterstützung unterscheidet sich.
BeautifulSoup unterstützt nur CSS-Selektoren (select und select_one); XPath gibt es nicht:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, "html.parser")
products = []
for card in soup.select("div.product"):
products.append({
"sku": card.get("data-sku"),
"name": card.select_one("h2").get_text(strip=True),
"price": card.select_one(".price").get_text(strip=True),
"link": card.select_one("a[href]")["href"],
})lxml führt XPath 1.0 direkt aus und kann das Ergebnis mit Funktionen wie string() als Text zurückgeben:
from lxml import html as lxml_html
tree = lxml_html.fromstring(html)
stock = tree.xpath('string(//th[normalize-space()="Bestand"]/following-sibling::td[1])')
discounted_sku = tree.xpath('//span[@class="discount"]/ancestor::div[@data-sku][1]/@data-sku')parsel (die Selektorbibliothek von Scrapy) bietet beide Sprachen auf demselben Objekt, miteinander verkettbar. Sie finden Karten mit CSS und nutzen XPath innerhalb einer Karte:
from parsel import Selector
page = Selector(text=html)
for card in page.css("div.product"):
name = card.css("h2::text").get()
link = card.css("a::attr(href)").get()
available = card.xpath('.//button[text()="In den Warenkorb"]').get() is not None
print(name, link, available)Beim Verketten ist darauf zu achten, dass ein XPath-Ausdruck innerhalb einer Karte mit .// beginnt. Schreiben Sie //button, läuft die Suche über das gesamte Dokument statt innerhalb der Karte, und Sie finden für jede Karte die erste Schaltfläche der Seite.
Welche Sprache und Bibliothek zu Ihnen passt, klärt Web Scraping: JavaScript oder Python?.
Selektoren in Headless-Browsern
Auf Seiten, deren Inhalt per JavaScript lädt, werden Selektoren in einem Werkzeug zur Browserautomatisierung genutzt. Playwright unterstützt beide Sprachen und behandelt einen Ausdruck, der mit // beginnt, automatisch als XPath:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://example.com/deals")
prices = page.locator("div.product .price").all_inner_texts()
stock = page.locator('xpath=//th[normalize-space()="Bestand"]/following-sibling::td[1]').inner_text()
addable = page.locator("div.product", has_text="In den Warenkorb")
print(prices, stock, addable.count())
browser.close()In Selenium erledigen By.CSS_SELECTOR und By.XPATH dieselben Aufgaben; Einrichtung und Proxy-Konfiguration erklären wir in Proxys mit Selenium verwenden und Proxys mit SeleniumBase verwenden. Ob eine Seite wirklich einen Browser braucht, klärt Statische und dynamische Seiten.
Zählt der Leistungsunterschied wirklich?
Die Behauptung „CSS-Selektoren sind schneller als XPath" liest man online oft. Das hängt von der Umgebung ab:
- Im Browser nutzt
querySelectorAlldirekt den optimierten Selektorabgleich der Browser-Engine; XPath überdocument.evaluateist meist langsamer. Spürbar wird der Unterschied bei sehr großen Seiten und tausendfach wiederholten Abfragen. - In Python mit lxml und parsel werden CSS-Selektoren im Hintergrund meist in XPath übersetzt und als XPath ausgeführt. Beide Sprachen nutzen also dieselbe Engine, und der Unterschied verschwindet weitgehend.
- In BeautifulSoup hängt die Auswahlgeschwindigkeit eher vom genutzten Parser (
html.parseroderlxml) ab als von der Selektorsprache.
Entscheidend ist, wie der Selektor geschrieben ist. //*[contains(@class, "price")], das das ganze Dokument durchsucht, leistet weit mehr Arbeit als ein Ausdruck, der zuerst die Karte und dann den Preis darin findet. Und all das ist meist gering neben der Zeit für das Herunterladen der Seite oder das Laden im Headless-Browser. Den eigentlichen Engpass eines Scrapers behandeln wir in Concurrency und Parallelism.
Wie vermeiden Sie anfällige Selektoren?
Der häufigste Grund, warum Scraper brechen, ist nicht eine Sperre, sondern eine Designänderung der Website. Damit ein Selektor kleine Änderungen übersteht:
- Meiden Sie automatisch erzeugte Klassennamen. Klassen wie
css-1x9k2abodersc-bdVaJakönnen sich mit jedem Release ändern. - Schreiben Sie keine absoluten Pfade. Ein aus dem Browser kopierter Ausdruck wie
/html/body/div[3]/div[2]/ul/li[4]/spanbricht, sobald ein einziges Banner hinzukommt. - Binden Sie sich an Bedeutung, nicht an Position. Nicht „das dritte
span", sondern „dasspanmit der Klasseprice" oder „die Zelle neben der Überschrift Bestand". - Bevorzugen Sie stabile Attribute. Attribute wie
data-sku,data-testid,itempropundaria-labelsind unabhängig vom visuellen Design und ändern sich seltener. - Sehen Sie zuerst nach strukturierten Daten. Viele Produktseiten tragen Produktname, Preis und Bestand im schema.org-Format in
<script type="application/ld+json">. Diese Daten zu lesen, ist viel robuster als visuelles HTML zu parsen. - Wählen Sie zuerst den Container, dann das Feld. Eine Karte einmal zu finden und Felder darin zu suchen, verhindert, dass Felder vertauscht werden.
- Behandeln Sie ein leeres Ergebnis als Fehler. Statt stillschweigend einen leeren Wert zu schreiben, wenn ein Selektor nichts findet, protokollieren Sie es und lösen einen Alarm aus; so bemerken Sie die Designänderung am ersten Tag.
Leere Selektorergebnisse sind nicht immer eine Designänderung; manchmal liefert der Bot-Schutz eine andere Seite, manchmal lädt der Inhalt später per JavaScript. Zur Diagnose sehen Sie sich die Liste in Web Scraping ohne Sperren an.
Wie testen Sie einen Selektor im Browser?
Einen Selektor im Browser auszuprobieren, bevor er in den Code kommt, spart Zeit. In den Entwicklertools (F12) von Chrome, Edge und Firefox:
$$("div.product .price")in der Konsole gibt alle Elemente, die auf den CSS-Selektor passen, als Array zurück.$x('//th[normalize-space()="Bestand"]/following-sibling::td[1]')in der Konsole führt einen XPath-Ausdruck aus.Strg + Fim Elements-Panel öffnet ein Suchfeld, das Klartext, CSS-Selektoren und XPath akzeptiert und passende Elemente nacheinander hervorhebt.
Zwei Warnungen: Die Seite im Browser zeigt den Zustand nach der Ausführung von JavaScript. Erhält Ihr Scraper über einen HTTP-Client nur das HTML des Servers, kann ein im Browser funktionierender Selektor im Code leer bleiben; prüfen Sie das mit dem Quelltext (Strg + U). Zweitens erzeugt die Browserfunktion „Copy XPath" meist absolute, anfällige Pfade; nutzen Sie sie als Ausgangspunkt und vereinfachen Sie sie.
Anwendungsfälle
- Preisbeobachtung: Produktkarten mit CSS, Label-Wert-Felder wie „Bestand" mit XPath. Den Aufbau beschreibt unsere Seite zur Preisbeobachtung.
- Katalogerfassung: zuerst JSON-LD; fehlt es, CSS-Selektoren mit stabilen
data-*-Attributen. Den allgemeinen Aufbau zeigt unsere Seite zur Datenerfassung. - Vergleichsportale: eine Selektordefinitionsdatei pro Website für unterschiedliche Strukturen; XPath für textbasierte Felder.
- Testautomatisierung: CSS-Selektoren mit
data-testid-Attributen in Ihrer eigenen Anwendung; Tests, die Designänderungen nicht betreffen. - Regelmäßige Erfassung vieler Seiten: Solange Selektoren stabil bleiben, geht es nur noch um die Verteilung der Anfragen; ein Rotierender Proxy liefert über eine Adresse verschiedene Ausgangs-IPs.
Entscheidungshilfe
| Ihr Bedarf | Empfehlung |
|---|---|
| Auswahl nach Klasse, ID oder Attribut | CSS-Selektor |
| Sie nutzen BeautifulSoup | CSS-Selektor (keine XPath-Unterstützung) |
| Ein Element anhand seines Textes finden | XPath |
| Vom Element zum Elternelement oder vorherigen Geschwister | XPath |
| Label-Wert-Tabellen und Definitionslisten | XPath |
| Einen Attributwert direkt zurückgeben | XPath /@attr oder parsel ::attr() |
| Beides im selben Projekt | parsel |
| Headless-Browser | Playwright-Locators, zuerst CSS |
| Robustheit gegenüber Designänderungen | data-*-Attribute oder JSON-LD |
Häufig gestellte Fragen
Sollte ich CSS-Selektoren oder XPath lernen?
Lernen Sie zuerst CSS-Selektoren; sie sind kürzer, verbreiteter und auch in der Webentwicklung nützlich. Brauchen Sie beim Scraping Auswahl nach Text und das Klettern zu Elternelementen, genügt es, die Achsen und Funktionen von XPath zu lernen. Mit Bibliotheken wie parsel, die beides nutzen, fällt der Wechsel leicht.
Unterstützt BeautifulSoup XPath?
Nein. BeautifulSoup unterstützt nur CSS-Selektoren. Brauchen Sie XPath, parsen Sie dasselbe HTML mit lxml oder parsel.
Kann CSS nach Text auswählen?
In Standard-CSS nicht. Manche Werkzeuge bieten eigene Erweiterungen: has_text und :has-text() in Playwright sowie :-soup-contains() in BeautifulSoup. Diese Erweiterungen funktionieren nur im jeweiligen Werkzeug und sind nicht übertragbar.
Warum funktioniert der aus dem Browser kopierte XPath nicht?
Der vom Browser erzeugte Pfad ist meist absolut und beruht auf der Seite nach der Ausführung von JavaScript. Diese Struktur gibt es im ersten HTML des Servers womöglich nicht, oder eine kleine Designänderung bricht den Pfad. Vereinfachen Sie den Pfad so, dass er bei einer stabilen Klasse, ID oder einem Attribut beginnt.
Was ist der Unterschied zwischen text() und normalize-space()?
text() gibt die direkten Textknoten des Elements zurück und lässt Leerraum unverändert. normalize-space(.) fügt den gesamten Text des Elements einschließlich verschachtelter Tags zusammen, entfernt Leerzeichen am Anfang und Ende und fasst innere Leerzeichen zu einem zusammen. Für Textvergleiche ist normalize-space() meist zuverlässiger.
Warum liefern meine Selektoren manchmal nichts?
Drei Gründe sind verbreitet: Das Seitendesign hat sich geändert, der Inhalt lädt später per JavaScript, oder die Website liefert statt der erwarteten Seite eine Prüf- oder Fehlerseite. Das HTML der Antwort in eine Datei zu speichern und den Selektor auf dieser Datei auszuprobieren, trennt die drei am schnellsten.
Fazit
CSS-Selektoren sind kurz, lesbar und werden von jedem Werkzeug unterstützt; für die Auswahl nach Tag, Klasse, ID und Attribut sollten sie die Standardwahl sein. XPath braucht es für das, was CSS nicht kann, etwa Auswahl nach Text, Klettern zu Elternelementen und vorherigen Geschwistern sowie Label-Wert-Strukturen; in Browsern und lxml ist es auf Version 1.0 beschränkt. In den meisten Scraping-Aufgaben ist der Leistungsunterschied neben der Netzwerkzeit vernachlässigbar; wirklich zählt, Selektoren zu schreiben, die Designänderungen überstehen. Proxy-Typen für Ihre Datenerfassung finden Sie in unseren Proxy-Diensten.




