Zurück zum Blog
Anleitungen
Mihai MaximLast updated on Mar 31, 20268 min read

Das ultimative XPath-Spickzettel. So schreibst du ganz einfach leistungsstarke Selektoren.

Das ultimative XPath-Spickzettel. So schreibst du ganz einfach leistungsstarke Selektoren.

Ein XPath-Spickzettel?

Mussten Sie schon einmal einen CSS-Selektor schreiben, der klassenunabhängig ist? Wenn Ihre Antwort „Nein“ lautet, können Sie sich glücklich schätzen. Wenn die Antwort „Ja“ lautet, dann ist unser XPath-Spickzettel genau das, was Sie brauchen. Das Web wimmelt nur so von Daten. Ganze Unternehmen sind darauf angewiesen, diese Daten zusammenzuführen, um neue Dienste auf den Markt zu bringen. APIs sind sehr nützlich, aber nicht jede Website verfügt über offene APIs. Manchmal müssen Sie das, was Sie brauchen, auf die altbewährte Art und Weise beschaffen. Sie müssen einen Scraper für die Website erstellen. Moderne Websites umgehen das Scraping, indem sie ihre CSS-Klassen umbenennen. Daher ist es besser, Selektoren zu schreiben, die sich auf etwas Stabileres stützen. In diesem Artikel lernen Sie, wie Sie Selektoren basierend auf dem DOM-Knoten-Layout der Seite schreiben.

Was ist XPath und wie probiere ich es aus?

XPath steht für XML Path Language. Es verwendet eine Pfadnotation (wie in URLs), um auf flexible Weise auf jeden beliebigen Teil eines XML-Dokuments zu verweisen. 

XPath wird hauptsächlich in XSLT verwendet, kann aber auch als wesentlich leistungsfähigere Methode zum Navigieren durch das DOM eines beliebigen XML-ähnlichen Sprachdokuments (wie HTML und SVG) mithilfe von XPathExpression genutzt werden, anstatt sich auf die Methoden Document.getElementById() oder Document.querySelectorAll(), die Node.childNodes-Eigenschaften und andere DOM-Core-Funktionen zu verlassen. XPath | MDN (mozilla.org)

Eine Pfadnotation?

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Nothing to see here</title>
</head>
<body>
    <h1>My First Heading</h1>
    <p>My first paragraph.</p>
    <div>
        <h2>My Second Heading</h2>
        <p>My second paragraph.</p>
        <div>
            <h3>My Third Heading</h3>
            <p>My third paragraph.</p>
        </div>
    </div>
</body>
</html>

There are two types of paths: relative and absolute

The unique path ( or absolute path ) to My third paragraph. is /html/body/div/div/p

A relative path to My third paragraph. is //body/div/div/p
For My Second Heading. => //body/div/h2
For My first paragraph. => //body/p

Notice that I'm using //body. Relative paths use // to skip right to the desired element.

The usage of //<path> also implies that it should look for all occurrences of <path> in the document, regardless of what came before <path>.

For example, //div/p returns both My second paragraph. and My third paragraph.

Sie können dieses Beispiel in Ihrem Browser testen, um sich einen besseren Überblick zu verschaffen!

Fügen Sie den Code in eine .html-Datei ein und öffnen Sie diese mit Ihrem Browser. Öffnen Sie die Entwicklertools und drücken Sie Strg + F. Fügen Sie den XPath-Locator in die kleine Eingabeleiste ein und drücken Sie die Eingabetaste.

Sie können den XPath eines beliebigen Tags auch abrufen, indem Sie im Reiter „Elemente“ mit der rechten Maustaste darauf klicken und „XPath kopieren“ auswählen

Beachte, wie ich zwischen „Mein zweiter Absatz“ und „Mein dritter Absatz“ wechsle.

Also, another important thing to know is that it is not necessary for a path to contain // in order to return multiple elements. Let's see what happens when I add another <p> in the last <div>.

/html/body/div/div/p is no longer an absolute path.

Wenn du bis hierher mitgekommen bist, herzlichen Glückwunsch, du bist auf dem besten Weg, XPath zu meistern. Du bist nun bereit, dich in die spannenden Dinge zu stürzen.

Die eckigen Klammern

Sie können eckige Klammern verwenden, um bestimmte Elemente auszuwählen.

In this case, //body/div/div[2]/p[3] only selects the last <p> tag.

Attribute

Sie können auch Attribute verwenden, um Ihre Elemente auszuwählen.

//body//p[@class="not-important"] => select all the <p> tags that are inside a <body> tag and have the "not-important" class.

//div[@id] => select all the <div> tags that have an id attribute.

//div[@class="p-children"][@id="important"]/p[3] => select the third <p> that is within a <div> tag that has both class="p-children" and id="important"

//div[@class="p-children" and @id="important"]/p[3] => same as above

//div[@class="p-children" or @id="important"]/p[3] => select the third <p> that is within a <div> that has class="p-children" or id="important"

Notice @ marks the start of an attribute

Funktionen

XPath bietet eine Reihe nützlicher Funktionen, die Sie innerhalb der eckigen Klammern verwenden können.

position() => returns the index of the element
Ex: //body/div[position()=1] selects the first <div> in the <body>

last() => returns the last element
Ex: //div/p[last()] selects all the last <p> children of all the <div> tags

count(element) => returns the number of elements
Ex: //body/count(div) returns the number of child <div> tags inside the <body>

node() or * => returns any element
Ex: //div/node() and //div/*=> selects all the children of all the <div> tags

text() => returns the text of the element
Ex: //p/text() returns the text of all the <p> elements

concat(string1, string2) => merges string1 with string2

contains(@attribute, "value") => returns true if @attribute contains "value"
Ex:
//p[contains(text(),"I am the third child")] selects all the <p> tags that have the "I am the third child" text value.

starts-with(@attribute, "value") => returns true if @attribute starts with "value"
ends-with(@attribute, "value") => returns true if @attribute ends with "value" 

substring(@attribute,start_index,end_index)] => returns the substring of the attribute value based on two index values
Ex:
//p[substring(text(),3,12)="am the third"] => returns true if text() = "I am the third child"

normalize-space() => acts like text(), but it removes the trailing spaces
Ex: normalize-space(" example ") = "example"

string-length() => returns the length of the text
Ex: //p[string-length()=20] returns all the <p> tags that have the text length of 20

Die Funktionen sind manchmal etwas schwer zu merken. Glücklicherweise bietet „The Ultimate Xpath Cheat Sheet“ hilfreiche Beispiele:

//p[text()=concat(substring(//p[@class="not-important"]/text(),1,15), substring(text(),16,20))]

//p[text()=<expression_return_value>] will select all the <p> elements that have the text value equal to the return value of the condition.

//p[@class="not-important"]/text() returns the text values of all the <p> tags that have class="not-important".

If there is only one <p> tag that satisfies this condition, then we can pass the return_value to the substring function.

substring(return_value,1,15) will return the first 15 characters of the return_value string.

substring(text(),16,20) will return the last 5 characters of the same

text() value that we used in //p[text()=<expression_return_value>].

Finally, concat() will merge the two substrings and create the return value of <expression_return_value>.

Pfadverschachtelung

XPath unterstützt die Verschachtelung von Pfaden. Das ist cool, aber was genau meine ich damit?

Let's try something new: /html/body/div[./div[./p]]

You can read it as "Select all the <div> sons of the <body> that have a <div> child. Also, the children must also be parents to a <p> element."

If you don't care about the father of the <p> element, you can write: /html/body/div[.//p]

This now translates to "Select all the div children of the body that have a <p> descendant"

In this particular example, /html/body/div[./div[./p]] and /html/body/div[.//p] yield the same result.

By now, I'm sure that you are wondering what is up with those dots in ./ and .// 

The dot represents the self element. When used in a pair of brackets, it references the specific tag that opened them. Let's dive a little deeper.

In our example, /html/body/div returns two divs:
<div class="no-content"> and <div class="content">

/html/body/div[.//p] translates to:

    /html/body/div[1][/html/body/div[1]//p]
and /html/body/div[2][/html/body/div[2]//p]

/html/body/div[2][/html/body/div[2]//p] is true, so it returns /html/body/div[2] 

In our case, the dot ensures that /html/body/div and /html/body/div//p refer to the same <div>

Now let's look at what would have happened if it didn't.

/html/body/div[/html/body/div//p] would return both
<div class="no-content">  and <div class="content">

Why? Because /html/body/div//p is true for both /html/body/div[1] and /html/body/div[2].

/html/body/div[/html/body/div//p] actually translates to "Select all the div children of the <body> if /html/body/div//p is true. 

/html/body/div//p is true if the body has a <div> child, and that child has a <p> descendent". In our case, this statement is always true.

Es ist schade, dass andere XPath-Spickzettel nichts über Verschachtelung erwähnen. Ich finde es großartig. Damit kannst du das Dokument nach verschiedenen Mustern durchsuchen und später zurückkommen, um etwas anderes zurückzugeben. Der einzige Nachteil ist, dass es schwierig werden kann, Abfragen, die auf diese Weise geschrieben sind, nachzuvollziehen. Die gute Nachricht ist, dass es andere Möglichkeiten gibt, dies zu tun.

Die Achsen

Sie können Achsen verwenden, um Knoten relativ zu anderen Kontextknoten zu lokalisieren.

Schauen wir uns einige davon an.

Die vier Hauptachsen

//p/ancestor::div => selects all the divs that are ancestors of <p>

How I read it: Get all the <p> tags, for each <p> look through its ancestors. If you find <div> tags, select them.

//p/parent::div => selects all the <div> tags that are parents of <p> 

How I read it: Get all the <p> tags and of all their parents, if the parent is a <div>, select it.

//div/child::p=> selects all the <p> tags that are children of <div> tags.

How I read it: Get all the <div> tags and their children, if the child is a <p>, select it.

//div/descendant::p => selects all the <p> tags that are descendants of <div> tags.

How I read it: Get all the <div> tags and their descendants, if the descendant is a <p>, select it.

Nun ist es an der Zeit, den vorherigen Ausdruck umzuschreiben:

/html/body/div[./div[./p]] is equivalent to /html/body/div/div/p/parent::div/parent::div

But /html/body/div[.//p] is NOT equivalent to /html/body/div//p/ancestor::div

The good news is that we can tweak it a little bit.

/html/body/div//p/ancestor::div[last()] is equivalent to /html/body/div[.//p]

Weitere wichtige Achsen

//p/following-sibling::span => for each <p> tag, select its following <span> siblings.

//p/preceding-sibling::span => for each <p> tag, select its preceding <span> siblings.

//title/following::span => selects all the <span> tags that appear in the DOM after the <title>.

In our example, //title/following::span selects all the <span> tags in the document.

//p/preceding::div => selects all the <div> tags that appear in the DOM before any <p> tag. But it ignores ancestors, attribute nodes and namespace nodes.

In our case, //p/preceding::div only selects <div class="p-children"> and <div class="no_content">.

Most of the <p> tags are in <div class="content">, but this <div> is not selected because it is a common ancestor for them. As I mentioned, the
preceding axe ignores ancestors.

<div class="p-children"> is selected because it is not an ancestor for the <p> tags inside <div class="p-children" id="important">

Zusammenfassung

Herzlichen Glückwunsch, Sie haben es geschafft. Sie haben Ihre Selektor-Toolbox um ein brandneues Werkzeug erweitert! Wenn Sie einen Web-Scraper entwickeln oder Web-Tests automatisieren, wird Ihnen dieses XPath-Spickzettel sehr nützlich sein! Wenn Sie nach einer eleganteren Methode suchen, das DOM zu durchlaufen, sind Sie hier genau richtig. Auf jeden Fall lohnt es sich, XPath einmal auszuprobieren. Wer weiß, vielleicht entdecken Sie noch weitere Anwendungsfälle dafür. Findet ihr das Konzept des Web-Scrapings interessant? Ihr könnt uns hier kontaktieren: WebScrapingAPI – Kontakt. Wenn ihr das Web scrapen möchtet, unterstützen wir euch gerne dabei. Probiert in der Zwischenzeit doch einfach WebScrapingAPI – Produkt kostenlos aus.

Über den Autor
Mihai Maxim, Full-Stack-Entwickler @ WebScrapingAPI
Mihai MaximFull-Stack-Entwickler

Mihai Maxim ist Full-Stack-Entwickler bei WebScrapingAPI, wo er in verschiedenen Bereichen des Produkts mitwirkt und an der Entwicklung zuverlässiger Tools und Funktionen für die Plattform mitarbeitet.

Los geht’s

Sind Sie bereit, Ihre Datenerfassung zu erweitern?

Schließen Sie sich den über 2.000 Unternehmen an, die WebScrapingAPI nutzen, um Webdaten im Unternehmensmaßstab ohne zusätzlichen Infrastrukturaufwand zu extrahieren.